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

@@ -8,8 +8,8 @@
"name": "generate_routes",
"kind": "function",
"path": "openapi_first.codegen.generate_routes",
"signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False)",
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``<resource>/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``<resource>.py`` modules.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``."
"signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False, template_style: bool = False)",
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``<resource>/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``<resource>.py`` modules.\n template_style (bool, optional):\n If ``True``, emit ``async def`` handlers with a JSON body parameter\n named ``body`` (the standards-template convention) plus a\n service-resolution hint comment, instead of sync ``def`` handlers\n taking ``payload``.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``."
},
"generate_models": {
"name": "generate_models",

View File

@@ -2,7 +2,7 @@
"module": "openapi_first.codegen_routes",
"content": {
"path": "openapi_first.codegen_routes",
"docstring": "# Summary\n\nRoute handler code generation from OpenAPI specifications.\n\nThis module generates Python route handler stubs from an OpenAPI 3.x\nspecification. Each resource (derived from the first path segment)\ngets its own file under the output directory. Every OpenAPI operation\nmust define an ``operationId``, which becomes the handler function name.\n\nNotes:\n **Design constraints:**\n\n - ``operationId`` is required on every operation (matching\n ``binder.bind_routes``).\n - Handlers are stubs raising ``NotImplementedError``.\n - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n parent resource (``pets``).\n - Parameter types and defaults are inferred from the spec.\n - ``response: Response`` is injected for non-200 success codes.\n - Resource names are sanitized to valid Python module identifiers,\n so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.\n - Passing ``inject_request=True`` prepends ``request: Request`` to\n every handler (the composition-root / DI convention).\n - ``multipart/form-data`` and\n ``application/x-www-form-urlencoded`` request bodies expand to one\n parameter per schema property (``UploadFile`` / ``File`` / ``Form``)\n instead of an opaque ``payload: dict``.\n - Passing ``package=True`` writes ``<resource>/routes.py`` (with\n ``__init__.py`` sidecars) instead of a flat ``<resource>.py``.",
"docstring": "# Summary\n\nRoute handler code generation from OpenAPI specifications.\n\nThis module generates Python route handler stubs from an OpenAPI 3.x\nspecification. Each resource (derived from the first path segment)\ngets its own file under the output directory. Every OpenAPI operation\nmust define an ``operationId``, which becomes the handler function name.\n\nNotes:\n **Design constraints:**\n\n - ``operationId`` is required on every operation (matching\n ``binder.bind_routes``).\n - Handlers are stubs raising ``NotImplementedError``.\n - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n parent resource (``pets``).\n - Parameter types and defaults are inferred from the spec.\n - ``response: Response`` is injected for non-200 success codes.\n - Resource names are sanitized to valid Python module identifiers,\n so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.\n - Passing ``inject_request=True`` prepends ``request: Request`` to\n every handler (the composition-root / DI convention).\n - ``multipart/form-data`` and\n ``application/x-www-form-urlencoded`` request bodies expand to one\n parameter per schema property (``UploadFile`` / ``File`` / ``Form``)\n instead of an opaque ``payload: dict``.\n - Passing ``package=True`` writes ``<resource>/routes.py`` (with\n ``__init__.py`` sidecars) instead of a flat ``<resource>.py``.\n - Passing ``template_style=True`` emits ``async def`` handlers whose\n JSON body parameter is named ``body`` (matching the standards\n template) and adds a service-resolution hint comment.",
"objects": {
"load_openapi": {
"name": "load_openapi",
@@ -15,8 +15,8 @@
"name": "generate_routes",
"kind": "function",
"path": "openapi_first.codegen_routes.generate_routes",
"signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False) -> list[Path]",
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``<resource>/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``<resource>.py`` modules.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``."
"signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False, template_style: bool = False) -> list[Path]",
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``<resource>/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``<resource>.py`` modules.\n template_style (bool, optional):\n If ``True``, emit ``async def`` handlers with a JSON body parameter\n named ``body`` (the standards-template convention) plus a\n service-resolution hint comment, instead of sync ``def`` handlers\n taking ``payload``.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``."
}
}
}

View File

@@ -188,8 +188,8 @@
"name": "generate_routes",
"kind": "function",
"path": "openapi_first.codegen.generate_routes",
"signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False)",
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``<resource>/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``<resource>.py`` modules.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``."
"signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False, template_style: bool = False)",
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``<resource>/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``<resource>.py`` modules.\n template_style (bool, optional):\n If ``True``, emit ``async def`` handlers with a JSON body parameter\n named ``body`` (the standards-template convention) plus a\n service-resolution hint comment, instead of sync ``def`` handlers\n taking ``payload``.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``."
},
"generate_models": {
"name": "generate_models",
@@ -205,7 +205,7 @@
"kind": "module",
"path": "openapi_first.codegen_routes",
"signature": null,
"docstring": "# Summary\n\nRoute handler code generation from OpenAPI specifications.\n\nThis module generates Python route handler stubs from an OpenAPI 3.x\nspecification. Each resource (derived from the first path segment)\ngets its own file under the output directory. Every OpenAPI operation\nmust define an ``operationId``, which becomes the handler function name.\n\nNotes:\n **Design constraints:**\n\n - ``operationId`` is required on every operation (matching\n ``binder.bind_routes``).\n - Handlers are stubs raising ``NotImplementedError``.\n - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n parent resource (``pets``).\n - Parameter types and defaults are inferred from the spec.\n - ``response: Response`` is injected for non-200 success codes.\n - Resource names are sanitized to valid Python module identifiers,\n so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.\n - Passing ``inject_request=True`` prepends ``request: Request`` to\n every handler (the composition-root / DI convention).\n - ``multipart/form-data`` and\n ``application/x-www-form-urlencoded`` request bodies expand to one\n parameter per schema property (``UploadFile`` / ``File`` / ``Form``)\n instead of an opaque ``payload: dict``.\n - Passing ``package=True`` writes ``<resource>/routes.py`` (with\n ``__init__.py`` sidecars) instead of a flat ``<resource>.py``.",
"docstring": "# Summary\n\nRoute handler code generation from OpenAPI specifications.\n\nThis module generates Python route handler stubs from an OpenAPI 3.x\nspecification. Each resource (derived from the first path segment)\ngets its own file under the output directory. Every OpenAPI operation\nmust define an ``operationId``, which becomes the handler function name.\n\nNotes:\n **Design constraints:**\n\n - ``operationId`` is required on every operation (matching\n ``binder.bind_routes``).\n - Handlers are stubs raising ``NotImplementedError``.\n - Sub-resources (e.g. ``/pets/{id}/photo``) are grouped with their\n parent resource (``pets``).\n - Parameter types and defaults are inferred from the spec.\n - ``response: Response`` is injected for non-200 success codes.\n - Resource names are sanitized to valid Python module identifiers,\n so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.\n - Passing ``inject_request=True`` prepends ``request: Request`` to\n every handler (the composition-root / DI convention).\n - ``multipart/form-data`` and\n ``application/x-www-form-urlencoded`` request bodies expand to one\n parameter per schema property (``UploadFile`` / ``File`` / ``Form``)\n instead of an opaque ``payload: dict``.\n - Passing ``package=True`` writes ``<resource>/routes.py`` (with\n ``__init__.py`` sidecars) instead of a flat ``<resource>.py``.\n - Passing ``template_style=True`` emits ``async def`` handlers whose\n JSON body parameter is named ``body`` (matching the standards\n template) and adds a service-resolution hint comment.",
"members": {
"load_openapi": {
"name": "load_openapi",
@@ -218,8 +218,8 @@
"name": "generate_routes",
"kind": "function",
"path": "openapi_first.codegen_routes.generate_routes",
"signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False) -> list[Path]",
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``<resource>/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``<resource>.py`` modules.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``."
"signature": "generate_routes(spec_path: Path, output_dir: Path, *, use_models: bool = False, models_module: str = 'models', inject_request: bool = False, package: bool = False, template_style: bool = False) -> list[Path]",
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one route module per resource in *output_dir*. Resources are\nderived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``\nboth group under ``pets``) and sanitized to valid module identifiers.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_dir (Path):\n Directory where the generated route files are written.\n Created automatically if it does not exist.\n use_models (bool, optional):\n If ``True``, import Pydantic models from *models_module*\n for request-body schemas referenced via ``$ref``.\n models_module (str, optional):\n Dotted Python module path from which to import models\n (e.g. ``\"models\"``, ``\"app.models\"``).\n inject_request (bool, optional):\n If ``True``, prepend ``request: Request`` to every handler so the\n stub can later resolve services from ``request.app.state``.\n package (bool, optional):\n If ``True``, emit ``<resource>/routes.py`` packages (each with an\n ``__init__.py``) instead of flat ``<resource>.py`` modules.\n template_style (bool, optional):\n If ``True``, emit ``async def`` handlers with a JSON body parameter\n named ``body`` (the standards-template convention) plus a\n service-resolution hint comment, instead of sync ``def`` handlers\n taking ``payload``.\n\nReturns:\n list[Path]:\n Absolute paths of every generated route file.\n\nRaises:\n OpenAPISpecLoadError:\n If the spec cannot be loaded or validated.\n ValueError:\n If any operation is missing ``operationId``."
}
}
},

View File

@@ -55,6 +55,7 @@ One file per resource (derived from the first path segment). Flags:
| `--models-module MOD` | Dotted module for those imports (default `models`) |
| `--inject-request` | Prepend `request: Request` to every handler (DI convention) |
| `--package` | Emit `<resource>/routes.py` packages (with `__init__.py`) instead of flat `<resource>.py` |
| `--template-style` | Emit `async def` handlers with a JSON body parameter named `body` (the standards-template convention) plus a service-resolution hint, instead of sync `def` handlers taking `payload` |
Why bother? Because `bind_routes` (in [02 – Components](../02_components.md)) needs an `operationId` → handler **exactly** matching the spec. Codegen guarantees you never type a handler name wrong — it wears the same `operationId` the spec declares.
@@ -65,6 +66,15 @@ Why bother? Because `bind_routes` (in [02 – Components](../02_components.md))
The stubs `raise NotImplementedError`; fill each body in (typically by resolving a service from `request.app.state`).
### Handler shapes
Both shapes bind equally — the router resolves the handler by `operationId` and introspects the FastAPI signature, so it never inspects the request body:
- **Default (sync stub):** `def create_item(payload: ItemInput, response: Response):` — the body parameter is `payload`.
- **`--template-style`:** `async def create_item(request: Request, body: ItemInput, response: Response):` — matches the standards template (`async def`, `body` parameter, a `request.app.state.db_resources["<resource>_svc"]` hint).
Use `--template-style` when generating against the standards template so the scaffold matches the hand-written handlers you will keep.
> **Note:** `models` / `routes` are build-time tooling. They do **not** run at runtime, and templates (the Bake-your-stuff section of [02 – Templates](02_templates.md)) scaffold a working server out-of-the-box for greenfield projects.
---

View File

@@ -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:

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

View File

@@ -8,4 +8,5 @@ def generate_routes(
models_module: str = ...,
inject_request: bool = ...,
package: bool = ...,
template_style: bool = ...,
) -> list[Path]: ...

View File

@@ -197,3 +197,72 @@ def test_missing_operation_id_raises(tmp_path):
)
with pytest.raises(ValueError):
generate_routes(spec, tmp_path / "routes")
def test_template_style_async_body_model(tmp_path):
spec = _write_spec(
tmp_path,
_spec(
{
"/items": {
"post": {
"operationId": "create_item",
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
},
"responses": {"201": {"description": "Created"}},
}
}
},
{"ItemInput": {"type": "object", "properties": {"name": {"type": "string"}}}},
),
)
files = generate_routes(
spec,
tmp_path / "routes",
use_models=True,
models_module="_gen.models",
inject_request=True,
template_style=True,
)
source = files[0].read_text(encoding="utf-8")
assert "async def create_item(request: Request, body: ItemInput, response: Response):" in source
assert "payload" not in source
assert "db_resources" in source
compile(source, str(files[0]), "exec")
def test_template_style_dict_body_without_models(tmp_path):
spec = _write_spec(
tmp_path,
_spec(
{
"/items": {
"post": {
"operationId": "create_item",
"requestBody": {
"required": True,
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/ItemInput"}
}
},
},
"responses": {"201": {"description": "Created"}},
}
}
}
),
)
files = generate_routes(
spec, tmp_path / "routes", inject_request=True, template_style=True
)
source = files[0].read_text(encoding="utf-8")
assert "async def create_item(request: Request, body: dict, response: Response):" in source
assert "payload" not in source
compile(source, str(files[0]), "exec")