feat: emit template-style route stubs (async, body param, service hint)

Add generate_routes(template_style=...) and CLI --template-style. When on,
stubs use async def, a JSON body parameter named body (dict or Model), and
with --inject-request a service-lookup hint comment before NotImplementedError.
Default behaviour (def/payload) unchanged. +2 tests (32 passed), wiki + MCP
bundle regenerated.
This commit is contained in:
2026-10-13 19:35:04 +05:30
parent 51ffb4aae9
commit 9cb8bf8f1c
8 changed files with 125 additions and 16 deletions

View File

@@ -28,6 +28,9 @@ Notes:
instead of an opaque ``payload: dict``.
- Passing ``package=True`` writes ``<resource>/routes.py`` (with
``__init__.py`` sidecars) instead of a flat ``<resource>.py``.
- Passing ``template_style=True`` emits ``async def`` handlers whose
JSON body parameter is named ``body`` (matching the standards
template) and adds a service-resolution hint comment.
"""
import re
@@ -66,6 +69,7 @@ def generate_routes(
models_module: str = "models",
inject_request: bool = False,
package: bool = False,
template_style: bool = False,
) -> list[Path]:
"""
Generate route handler stubs from an OpenAPI specification.
@@ -92,6 +96,11 @@ def generate_routes(
package (bool, optional):
If ``True``, emit ``<resource>/routes.py`` packages (each with an
``__init__.py``) instead of flat ``<resource>.py`` modules.
template_style (bool, optional):
If ``True``, emit ``async def`` handlers with a JSON body parameter
named ``body`` (the standards-template convention) plus a
service-resolution hint comment, instead of sync ``def`` handlers
taking ``payload``.
Returns:
list[Path]:
@@ -149,6 +158,7 @@ def generate_routes(
use_models=use_models,
models_module=models_module,
inject_request=inject_request,
template_style=template_style,
)
file_path.write_text(content, encoding="utf-8")
generated_files.append(file_path)
@@ -293,6 +303,7 @@ def _generate_resource_file(
use_models: bool,
models_module: str,
inject_request: bool = False,
template_style: bool = False,
) -> str:
"""Assemble the full Python source for a single resource file."""
op_ids = [op.get("operationId", "") for _, _, op in operations]
@@ -359,7 +370,8 @@ def _generate_resource_file(
lines.append("")
lines.extend(
_generate_handler(
path, http_method, operation, use_models, inject_request
path, http_method, operation, use_models, inject_request,
resource, template_style,
)
)
@@ -373,10 +385,13 @@ def _generate_handler(
operation: dict[str, Any],
use_models: bool,
inject_request: bool = False,
resource: str = "",
template_style: bool = False,
) -> list[str]:
"""Build the source lines for a single handler function."""
operation_id = operation.get("operationId", "")
summary = operation.get("summary", f"{http_method.upper()} {path}")
body_param = "body" if template_style else "payload"
params: list[str] = []
path_params: list[str] = []
@@ -424,9 +439,9 @@ def _generate_handler(
params.append(f"{field_name}: {annotation} = {factory}(None)")
elif operation.get("requestBody"):
if use_models and schema_name:
params.append(f"payload: {schema_name}")
params.append(f"{body_param}: {schema_name}")
else:
params.append("payload: dict")
params.append(f"{body_param}: dict")
# Inject Response for non-200 success codes
success_status = _get_success_status(operation)
@@ -436,7 +451,8 @@ def _generate_handler(
# Build function body
lines: list[str] = []
param_str = ", ".join(params)
lines.append(f"def {operation_id}({param_str}):")
prefix = "async def" if template_style else "def"
lines.append(f"{prefix} {operation_id}({param_str}):")
lines.append(f' """{summary}')
# Document parameters
@@ -472,14 +488,20 @@ def _generate_handler(
elif operation.get("requestBody"):
if use_models and schema_name:
lines.append("")
lines.append(f" payload : {schema_name}")
lines.append(f" {body_param} : {schema_name}")
lines.append(" Request body.")
else:
lines.append("")
lines.append(" payload : dict")
lines.append(f" {body_param} : dict")
lines.append(" Request body.")
lines.append(' """')
if template_style and inject_request:
svc_key = f"{_sanitize_identifier(resource)}_svc"
lines.append(
f' # Resolve the service: '
f'request.app.state.db_resources["{svc_key}"]'
)
lines.append(" raise NotImplementedError")
return lines