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:
@@ -143,6 +143,12 @@ def main() -> None:
|
||||
default=False,
|
||||
help="Emit one `<resource>/routes.py` package per resource",
|
||||
)
|
||||
routes_parser.add_argument(
|
||||
"--template-style",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Emit async handlers with a `body` parameter (template convention)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -167,6 +173,7 @@ def main() -> None:
|
||||
models_module=args.models_module,
|
||||
inject_request=args.inject_request,
|
||||
package=args.package,
|
||||
template_style=args.template_style,
|
||||
)
|
||||
print(f"Generated {len(files)} route file(s):")
|
||||
for f in files:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -8,4 +8,5 @@ def generate_routes(
|
||||
models_module: str = ...,
|
||||
inject_request: bool = ...,
|
||||
package: bool = ...,
|
||||
template_style: bool = ...,
|
||||
) -> list[Path]: ...
|
||||
|
||||
Reference in New Issue
Block a user