Skip to content

Commit 976c60c

Browse files
committed
cleanup folders
1 parent 675bcd7 commit 976c60c

14 files changed

Lines changed: 522 additions & 327 deletions

File tree

Makefile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.PHONY: proto run build install test clean
2+
3+
proto:
4+
poetry run python -m grpc_tools.protoc -I./proto \
5+
--python_out=./src/boot_python/generated \
6+
--grpc_python_out=./src/boot_python/generated \
7+
./proto/plugin.proto
8+
# Patch absolute import to relative so package imports work
9+
sed -i '' 's/^import plugin_pb2 as plugin__pb2/from . import plugin_pb2 as plugin__pb2/' \
10+
src/boot_python/generated/plugin_pb2_grpc.py
11+
12+
run:
13+
poetry run boot-python 2>/dev/null | head -1
14+
15+
build: proto
16+
poetry build
17+
18+
install: build
19+
pipx install dist/boot_python-*.whl
20+
21+
test:
22+
poetry run pytest -q
23+
24+
clean:
25+
rm -rf dist build .pytest_cache .ruff_cache

poetry.lock

Lines changed: 229 additions & 73 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
1-
# boot-python/pyproject.toml
21
[tool.poetry]
32
name = "boot-python"
4-
version = "0.1.0-alpha"
5-
description = "A prompt-provider plugin for generating Python projects with boot-code."
3+
version = "0.1.1-alpha"
4+
description = "Prompt-provider plugin for boot-code (Python)."
65
authors = ["Bordumb <bordumbb@gmail.com>"]
76
readme = "README.md"
8-
# Add this line to tell Poetry where to find the package source code.
9-
packages = [{include = "src"}]
7+
packages = [
8+
{ include = "boot_python", from = "src" }
9+
]
10+
11+
# Include runtime assets (prompts) and optionally proto (dev)
12+
include = [
13+
{ path = "prompts/**/*", format = "sdist" },
14+
{ path = "prompts/**/*", format = "wheel" },
15+
# proto is not needed at runtime; include only if you want it in sdists:
16+
{ path = "proto/plugin.proto", format = "sdist" }
17+
]
1018

1119
[tool.poetry.dependencies]
12-
# This section lists the libraries your plugin needs to run.
13-
python = ">=3.11"
14-
# Core libraries for the gRPC server functionality.
20+
python = "^3.9"
1521
grpcio = "^1.74.0"
1622
protobuf = "==6.31.1"
17-
# Library for parsing the spec.toml file content.
18-
toml = "^0.10.2"
23+
tomli = { version = "^2.0.1", python = "<3.11" } # for TOML parsing on Py<=3.10
1924

2025
[tool.poetry.group.dev.dependencies]
21-
# This section lists tools for development, like compiling protobuf files.
2226
grpcio-tools = "^1.74.0"
23-
pytest = ">=7.0.0"
24-
pytest-asyncio = ">=0.21.0"
25-
black = ">=24.0.0"
26-
mypy = ">=1.0.0"
27-
ruff = ">=0.1.0"
27+
pytest = "^8.0.0"
2828

2929
[tool.poetry.scripts]
30-
# This defines the entry point for the executable. When a user runs `boot-python`,
31-
# it will execute the `main` function inside the `src/main.py` file.
32-
# This is how `boot-code` will start your plugin.
33-
boot-python = "src.main:main"
30+
boot-python = "boot_python.main:main"
3431

3532
[build-system]
36-
requires = ["poetry-core"]
33+
requires = ["poetry-core>=1.9.0"]
3734
build-backend = "poetry.core.masonry.api"
35+
36+
[tool.pytest.ini_options]
37+
pythonpath = ["src"]

src/boot_python/__init__.py

Whitespace-only changes.

src/boot_python/generated/__init__.py

Whitespace-only changes.
Lines changed: 14 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/generated/plugin_pb2_grpc.py renamed to src/boot_python/generated/plugin_pb2_grpc.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
11
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
22
"""Client and server classes corresponding to protobuf-defined services."""
33
import grpc
4+
import warnings
45

5-
import plugin_pb2 as plugin__pb2
6+
from . import plugin_pb2 as plugin__pb2
7+
8+
GRPC_GENERATED_VERSION = '1.74.0'
9+
GRPC_VERSION = grpc.__version__
10+
_version_not_supported = False
11+
12+
try:
13+
from grpc._utilities import first_version_is_lower
14+
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
15+
except ImportError:
16+
_version_not_supported = True
17+
18+
if _version_not_supported:
19+
raise RuntimeError(
20+
f'The grpc package installed is at version {GRPC_VERSION},'
21+
+ f' but the generated code in plugin_pb2_grpc.py depends on'
22+
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
23+
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
24+
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
25+
)
626

727

828
class BootCodePluginStub(object):
@@ -18,7 +38,7 @@ def __init__(self, channel):
1838
'/plugin.BootCodePlugin/GetPromptComponents',
1939
request_serializer=plugin__pb2.GetPromptComponentsRequest.SerializeToString,
2040
response_deserializer=plugin__pb2.GetPromptComponentsResponse.FromString,
21-
)
41+
_registered_method=True)
2242

2343

2444
class BootCodePluginServicer(object):
@@ -42,6 +62,7 @@ def add_BootCodePluginServicer_to_server(servicer, server):
4262
generic_handler = grpc.method_handlers_generic_handler(
4363
'plugin.BootCodePlugin', rpc_method_handlers)
4464
server.add_generic_rpc_handlers((generic_handler,))
65+
server.add_registered_method_handlers('plugin.BootCodePlugin', rpc_method_handlers)
4566

4667

4768
# This class is part of an EXPERIMENTAL API.
@@ -59,8 +80,18 @@ def GetPromptComponents(request,
5980
wait_for_ready=None,
6081
timeout=None,
6182
metadata=None):
62-
return grpc.experimental.unary_unary(request, target, '/plugin.BootCodePlugin/GetPromptComponents',
83+
return grpc.experimental.unary_unary(
84+
request,
85+
target,
86+
'/plugin.BootCodePlugin/GetPromptComponents',
6387
plugin__pb2.GetPromptComponentsRequest.SerializeToString,
6488
plugin__pb2.GetPromptComponentsResponse.FromString,
65-
options, channel_credentials,
66-
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
89+
options,
90+
channel_credentials,
91+
insecure,
92+
call_credentials,
93+
compression,
94+
wait_for_ready,
95+
timeout,
96+
metadata,
97+
_registered_method=True)

src/boot_python/main.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import logging
2+
import os
3+
import signal
4+
import socket
5+
import sys
6+
import time
7+
from concurrent import futures
8+
9+
import grpc
10+
11+
from boot_python.generated import plugin_pb2_grpc
12+
from boot_python.server import BootPluginServicer
13+
14+
# All logs -> STDERR; keep STDOUT pristine for handshake
15+
logging.basicConfig(stream=sys.stderr, level=logging.INFO, format="%(levelname)s %(message)s")
16+
17+
18+
def _pick_loopback_port() -> tuple[str, int]:
19+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
20+
s.bind(("127.0.0.1", 0))
21+
host, port = s.getsockname()
22+
s.close()
23+
return host, port
24+
25+
26+
def main() -> None:
27+
# Start gRPC server
28+
host, port = _pick_loopback_port()
29+
server = grpc.server(futures.ThreadPoolExecutor(max_workers=4))
30+
plugin_pb2_grpc.add_BootCodePluginServicer_to_server(BootPluginServicer(), server)
31+
server.add_insecure_port(f"{host}:{port}")
32+
33+
# Print handshake to STDOUT only (no extra whitespace/newlines!)
34+
# Format: 1|1|tcp|HOST:PORT|grpc
35+
print(f"1|1|tcp|{host}:{port}|grpc", end="", flush=True)
36+
37+
server.start()
38+
logging.info("boot-python started on %s:%d", host, port)
39+
40+
# Test fast-exit: if set, skip the blocking loop so imports/patches won't hang.
41+
if os.environ.get("BOOT_PYTHON_TEST_EXIT_AFTER_HANDSHAKE") == "1":
42+
logging.info("Exiting immediately due to BOOT_PYTHON_TEST_EXIT_AFTER_HANDSHAKE=1")
43+
server.stop(grace=None)
44+
logging.info("boot-python stopped")
45+
return
46+
47+
# Graceful shutdown
48+
stop_event = {"stop": False}
49+
50+
def _sigterm(_sig, _frm):
51+
logging.info("Received shutdown signal")
52+
stop_event["stop"] = True
53+
server.stop(grace=None)
54+
55+
signal.signal(signal.SIGINT, _sigterm)
56+
signal.signal(signal.SIGTERM, _sigterm)
57+
58+
try:
59+
while not stop_event["stop"]:
60+
time.sleep(0.2)
61+
finally:
62+
logging.info("boot-python stopped")
63+
64+
65+
if __name__ == "__main__":
66+
main()

src/boot_python/server.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from __future__ import annotations
2+
3+
import io
4+
import logging
5+
import os
6+
from pathlib import Path
7+
from typing import Dict, Tuple
8+
9+
import grpc
10+
11+
try:
12+
import tomllib # Py>=3.11
13+
except ModuleNotFoundError: # pragma: no cover
14+
import tomli as tomllib # Py<=3.10
15+
16+
from boot_python.generated import plugin_pb2, plugin_pb2_grpc
17+
18+
PROMPTS_DIR = Path(__file__).resolve().parent.parent.parent / "prompts"
19+
20+
21+
def _read_text(p: Path) -> str:
22+
with p.open("r", encoding="utf-8") as f:
23+
return f.read()
24+
25+
26+
def _load_prompts() -> Dict[str, str]:
27+
files = ["base_instructions.txt", "language_rules.txt", "review_instructions.txt", "README.md.template"]
28+
components: Dict[str, str] = {}
29+
for name in files:
30+
fp = PROMPTS_DIR / name
31+
if fp.exists():
32+
components[name] = _read_text(fp)
33+
else:
34+
logging.warning("Prompt file missing: %s", fp)
35+
return components
36+
37+
38+
def _derive_user_spec_prompt(spec_toml: str) -> str:
39+
"""
40+
Minimal, safe TOML decode to craft a user-specific prompt.
41+
Keep it defensive: if TOML parse fails, fall back gracefully.
42+
"""
43+
try:
44+
data = tomllib.loads(spec_toml)
45+
project = data.get("project", {})
46+
name = project.get("name", "unknown-project")
47+
descr = project.get("description", "")
48+
lang = project.get("language", "python")
49+
return f"User requests a {lang} project named '{name}'. Description: {descr}"
50+
except Exception as e: # don’t fail plugin on TOML issues
51+
logging.warning("Failed to parse spec TOML: %s", e)
52+
return "User requests a python project. Description: (unavailable)"
53+
54+
55+
class BootPluginServicer(plugin_pb2_grpc.BootCodePluginServicer):
56+
def GetPromptComponents(
57+
self,
58+
request: plugin_pb2.GetPromptComponentsRequest,
59+
context: grpc.ServicerContext,
60+
) -> plugin_pb2.GetPromptComponentsResponse:
61+
components = _load_prompts()
62+
user_spec_prompt = _derive_user_spec_prompt(request.spec_toml_content or "")
63+
return plugin_pb2.GetPromptComponentsResponse(
64+
components=components,
65+
user_spec_prompt=user_spec_prompt,
66+
)

0 commit comments

Comments
 (0)