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:
@@ -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",
|
||||
|
||||
@@ -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``."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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``."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
@@ -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]: ...
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user