Compare commits
8 Commits
0.0.7
...
adeb02e162
| Author | SHA1 | Date | |
|---|---|---|---|
| adeb02e162 | |||
| 48fd6627a7 | |||
| 06c45249fc | |||
| 294f3d7a0c | |||
| cb231a9c7e | |||
| 6e3939d68c | |||
| 658a98cf19 | |||
| 0efe68b052 |
46
.drone.yml
46
.drone.yml
@@ -23,18 +23,13 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
|
- PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
|
||||||
- VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
|
- VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
|
||||||
|
- echo "🔍 Checking if $PACKAGE_NAME==$VERSION exists on $PIP_REPO_URL ..."
|
||||||
- |
|
- |
|
||||||
if [ "$DRONE_TAG" != "$VERSION" ]; then
|
if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -q "$VERSION"; then
|
||||||
echo "Tag '$DRONE_TAG' does not match pyproject version '$VERSION'; refusing to publish."
|
echo "✅ $PACKAGE_NAME==$VERSION already exists — skipping build."
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- echo "Checking if $PACKAGE_NAME==$VERSION exists on $PIP_REPO_URL ..."
|
|
||||||
- |
|
|
||||||
if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" "$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then
|
|
||||||
echo "$PACKAGE_NAME==$VERSION already exists; skipping build."
|
|
||||||
exit 78
|
exit 78
|
||||||
else
|
else
|
||||||
echo "New version detected: $PACKAGE_NAME==$VERSION"
|
echo "🆕 New version detected: $PACKAGE_NAME==$VERSION"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: quality-gate
|
- name: quality-gate
|
||||||
@@ -49,24 +44,19 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- pip install --upgrade pip build
|
- pip install --upgrade pip build
|
||||||
- |
|
- |
|
||||||
if [ -z "$PIP_REPO_URL" ] || [ -z "$PIP_USERNAME" ] || [ -z "$PIP_PASSWORD" ]; then
|
AUTH_URL="https://${PIP_USERNAME}:${PIP_PASSWORD}@$(echo "${PIP_REPO_URL#*://}" | sed 's:/*$::')/simple"
|
||||||
echo "PIP_REPO_URL / PIP_USERNAME / PIP_PASSWORD are not set" >&2
|
pip install --index-url "$AUTH_URL" --extra-index-url https://pypi.org/simple/ -U ".[dev]"
|
||||||
exit 1
|
- echo "🛡️ Running quality gate..."
|
||||||
fi
|
|
||||||
- AUTH_URL="https://$PIP_USERNAME:$PIP_PASSWORD@$(echo "$PIP_REPO_URL" | sed -E 's|^[a-zA-Z]+://||; s|/*$||')/simple"
|
|
||||||
- pip install --index-url "$AUTH_URL" --extra-index-url https://pypi.org/simple/ -U ".[dev]" "pydoclint>=0.9.1"
|
|
||||||
- echo "Running quality gate..."
|
|
||||||
- python -m black --check .
|
- python -m black --check .
|
||||||
- python -m ruff check .
|
- python -m ruff check .
|
||||||
- python -m mypy
|
- python -m mypy
|
||||||
- pydoclint .
|
|
||||||
- python -m pytest
|
- python -m pytest
|
||||||
|
|
||||||
- name: build-package
|
- name: build-package
|
||||||
image: python:3.13-slim
|
image: python:3.13-slim
|
||||||
commands:
|
commands:
|
||||||
- pip install --upgrade pip build
|
- pip install --upgrade pip build
|
||||||
- echo "Building Python package..."
|
- echo "📦 Building Python package..."
|
||||||
- python -m build
|
- python -m build
|
||||||
- ls -l dist
|
- ls -l dist
|
||||||
|
|
||||||
@@ -81,7 +71,7 @@ steps:
|
|||||||
from_secret: PIP_PASSWORD
|
from_secret: PIP_PASSWORD
|
||||||
commands:
|
commands:
|
||||||
- pip install --upgrade twine
|
- pip install --upgrade twine
|
||||||
- echo "Uploading to private PyPI at $PIP_REPO_URL ..."
|
- echo "🚀 Uploading to private PyPI at $PIP_REPO_URL ..."
|
||||||
- |
|
- |
|
||||||
twine upload \
|
twine upload \
|
||||||
--repository-url "$PIP_REPO_URL" \
|
--repository-url "$PIP_REPO_URL" \
|
||||||
@@ -128,31 +118,25 @@ steps:
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | cut -d'"' -f2)
|
PACKAGE_NAME=$(grep -E '^name\s*=' pyproject.toml | cut -d'"' -f2)
|
||||||
echo "Package: $PACKAGE_NAME"
|
echo "📦 Package: $PACKAGE_NAME"
|
||||||
|
|
||||||
for TAG in $(git tag --sort=version:refname); do
|
for TAG in $(git tag --sort=version:refname); do
|
||||||
VERSION="$TAG"
|
VERSION="$TAG"
|
||||||
echo "Version: $VERSION"
|
echo "🔁 Version: $VERSION"
|
||||||
|
|
||||||
if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" \
|
if curl -fsSL -u "$PIP_USERNAME:$PIP_PASSWORD" \
|
||||||
"$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -qE "${PACKAGE_NAME}-${VERSION}(-|\.tar\.gz|\.whl)"; then
|
"$PIP_REPO_URL/simple/$PACKAGE_NAME/" | grep -q "$VERSION"; then
|
||||||
echo "Exists, skipping"
|
echo "⏭️ Exists, skipping"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git checkout --force "$TAG"
|
git checkout --force "$TAG"
|
||||||
|
|
||||||
TAG_VERSION=$(grep -E '^version\s*=' pyproject.toml | head -1 | cut -d'"' -f2)
|
echo "🏗️ Building $VERSION"
|
||||||
if [ "$TAG" != "$TAG_VERSION" ]; then
|
|
||||||
echo "Tag $TAG != pyproject version $TAG_VERSION at that commit; skipping"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Building $VERSION"
|
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
python -m build
|
python -m build
|
||||||
|
|
||||||
echo "Uploading $VERSION"
|
echo "⬆️ Uploading $VERSION"
|
||||||
twine upload \
|
twine upload \
|
||||||
--repository-url "$PIP_REPO_URL" \
|
--repository-url "$PIP_REPO_URL" \
|
||||||
-u "$PIP_USERNAME" \
|
-u "$PIP_USERNAME" \
|
||||||
|
|||||||
20
CHANGELOG.md
20
CHANGELOG.md
@@ -7,29 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [0.0.7] - 2026-10-17
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `py.typed` marker for PEP 561 type information.
|
- `py.typed` marker for PEP 561 type information.
|
||||||
- Route code generation: template-style async route stubs with a body parameter
|
- `.drone.yml` CI with a quality-gate step (black, ruff, mypy, pytest).
|
||||||
and service hint; resource-name sanitisation; `--inject-request` /
|
- Canonical `docforge.nav.yml`, generated `mkdocs.yml` and `mcp_docs/` via doc-forge.
|
||||||
`--package` options; expanded form bodies.
|
|
||||||
- Type stubs for the `cli`, `codegen`, and `security` modules.
|
|
||||||
- Hand-written wiki, plus doc-forge-generated API reference under `docs/lib`
|
|
||||||
and MCP bundle under `docs/mcp` (per-kind MkDocs configs).
|
|
||||||
- MIT `LICENSE`.
|
- MIT `LICENSE`.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Adopted the standard Type A PyPI CI pipeline: a `tag == version` guard and a
|
- Standardized `pyproject.toml` (canonical packaging, lint tool config, extras).
|
||||||
quality gate (black, ruff, mypy, pydoclint, pytest).
|
|
||||||
- Standardized `pyproject.toml` (canonical packaging, lint tool config, extras)
|
|
||||||
with a shared Google-style `[tool.pydoclint]` configuration.
|
|
||||||
- `templates/` excluded from black/ruff scans.
|
- `templates/` excluded from black/ruff scans.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Binder/app route annotations tightened (FastAPI / `ModuleType`);
|
|
||||||
`binder.pyi` synced with the signature.
|
|
||||||
- Stub fixes for typed API surfaces.
|
- Stub fixes for typed API surfaces.
|
||||||
|
|
||||||
[Unreleased]: https://git.aetoskia.com/aetos/openapi-first/compare/0.0.7...HEAD
|
|
||||||
[0.0.7]: https://git.aetoskia.com/aetos/openapi-first/compare/0.0.6...0.0.7
|
|
||||||
|
|||||||
@@ -4,40 +4,75 @@
|
|||||||
"path": "openapi_first.app",
|
"path": "openapi_first.app",
|
||||||
"docstring": "# Summary\n\nOpenAPI-first application bootstrap for FastAPI.\n\nThis module provides `OpenAPIFirstApp`, a thin but strict abstraction\nthat enforces OpenAPI as the single source of truth for a FastAPI service.\n\nNotes:\n **Core Principles:**\n\n - The OpenAPI specification (JSON or YAML) defines the entire API surface.\n - Every `operationId` in the OpenAPI spec must have a corresponding\n Python handler function.\n - Handlers are plain Python callables (no FastAPI decorators).\n - FastAPI route registration is derived exclusively from the spec.\n - FastAPI's autogenerated OpenAPI schema is fully overridden.\n\n **Responsibilities:**\n\n - Loads and validates an OpenAPI 3.x specification.\n - Dynamically binds HTTP routes to handler functions using `operationId`.\n - Registers routes with FastAPI at application startup.\n - Ensures runtime behavior matches the OpenAPI contract exactly.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Generate OpenAPI specs.\n - Generate client code.\n - Introduce a new framework or lifecycle.\n - Alter FastAPI dependency injection semantics.",
|
"docstring": "# Summary\n\nOpenAPI-first application bootstrap for FastAPI.\n\nThis module provides `OpenAPIFirstApp`, a thin but strict abstraction\nthat enforces OpenAPI as the single source of truth for a FastAPI service.\n\nNotes:\n **Core Principles:**\n\n - The OpenAPI specification (JSON or YAML) defines the entire API surface.\n - Every `operationId` in the OpenAPI spec must have a corresponding\n Python handler function.\n - Handlers are plain Python callables (no FastAPI decorators).\n - FastAPI route registration is derived exclusively from the spec.\n - FastAPI's autogenerated OpenAPI schema is fully overridden.\n\n **Responsibilities:**\n\n - Loads and validates an OpenAPI 3.x specification.\n - Dynamically binds HTTP routes to handler functions using `operationId`.\n - Registers routes with FastAPI at application startup.\n - Ensures runtime behavior matches the OpenAPI contract exactly.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Generate OpenAPI specs.\n - Generate client code.\n - Introduce a new framework or lifecycle.\n - Alter FastAPI dependency injection semantics.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"os": {
|
||||||
|
"name": "os",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.app.os",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('os', 'os')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"re": {
|
||||||
|
"name": "re",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.app.re",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('re', 're')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"ModuleType": {
|
||||||
|
"name": "ModuleType",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.app.ModuleType",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('ModuleType', 'types.ModuleType')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Any": {
|
||||||
|
"name": "Any",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.app.Any",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Any', 'typing.Any')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"FastAPI": {
|
||||||
|
"name": "FastAPI",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.app.FastAPI",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('FastAPI', 'fastapi.FastAPI')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"bind_routes": {
|
"bind_routes": {
|
||||||
"name": "bind_routes",
|
"name": "bind_routes",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.app.bind_routes",
|
"path": "openapi_first.app.bind_routes",
|
||||||
"signature": "bind_routes(app: FastAPI, spec: dict[str, Any], routes_module: ModuleType, security_deps: dict[str, list[Any]] | None = None)",
|
"signature": "<bound method Alias.signature of Alias('bind_routes', 'openapi_first.binder.bind_routes')>",
|
||||||
"docstring": "Bind OpenAPI operations to FastAPI routes.\n\nArgs:\n app (FastAPI):\n The FastAPI application instance to which routes will be added.\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification dictionary.\n routes_module (ModuleType):\n Python module containing handler functions. Each handler's name MUST\n exactly match an OpenAPI `operationId`.\n security_deps (dict[str, list[Any]] | None):\n Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``\n generated from the spec's ``securitySchemes`` and per-operation\n ``security`` fields.\n\nRaises:\n MissingOperationHandler:\n If an ``operationId`` is missing from the spec or if no corresponding\n handler function exists in the routes module.\n\nNotes:\n **Responsibilities:**\n\n - Iterates through the OpenAPI specification paths and methods.\n - Resolves each ``operationId`` to a handler function, and registers\n a corresponding ``APIRoute`` on the FastAPI application.\n - Injects FastAPI ``Depends()`` for each security requirement found\n on the operation or inherited from the top-level ``security`` field.\n\n **Guarantees:**\n\n - Route registration is deterministic and spec-driven. No route\n decorators are required or supported. Handler resolution errors\n surface at application startup."
|
"docstring": "Bind OpenAPI operations to FastAPI routes.\n\nArgs:\n app (FastAPI):\n The FastAPI application instance to which routes will be added.\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification dictionary.\n routes_module (ModuleType):\n Python module containing handler functions. Each handler's name MUST\n exactly match an OpenAPI `operationId`.\n security_deps (dict[str, list[Any]] | None):\n Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``\n generated from the spec's ``securitySchemes`` and per-operation\n ``security`` fields.\n\nRaises:\n MissingOperationHandler:\n If an ``operationId`` is missing from the spec or if no corresponding\n handler function exists in the routes module.\n\nNotes:\n **Responsibilities:**\n\n - Iterates through the OpenAPI specification paths and methods.\n - Resolves each ``operationId`` to a handler function, and registers\n a corresponding ``APIRoute`` on the FastAPI application.\n - Injects FastAPI ``Depends()`` for each security requirement found\n on the operation or inherited from the top-level ``security`` field.\n\n **Guarantees:**\n\n - Route registration is deterministic and spec-driven. No route\n decorators are required or supported. Handler resolution errors\n surface at application startup."
|
||||||
},
|
},
|
||||||
"load_openapi": {
|
"load_openapi": {
|
||||||
"name": "load_openapi",
|
"name": "load_openapi",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.app.load_openapi",
|
"path": "openapi_first.app.load_openapi",
|
||||||
"signature": "load_openapi(path: str | Path)",
|
"signature": "<bound method Alias.signature of Alias('load_openapi', 'openapi_first.loader.load_openapi')>",
|
||||||
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
||||||
},
|
},
|
||||||
"make_security_dependencies": {
|
"make_security_dependencies": {
|
||||||
"name": "make_security_dependencies",
|
"name": "make_security_dependencies",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.app.make_security_dependencies",
|
"path": "openapi_first.app.make_security_dependencies",
|
||||||
"signature": "make_security_dependencies(spec: dict[str, Any], security_schemes: dict[str, dict[str, Any]])",
|
"signature": "<bound method Alias.signature of Alias('make_security_dependencies', 'openapi_first.security.make_security_dependencies')>",
|
||||||
"docstring": "Build a mapping of ``METHOD:/path`` → list of ``Depends(...)``.\n\nThe effective security for each operation is resolved by:\n\n1. Using the operation-level ``security`` field if present.\n2. Falling back to the top-level ``security`` field.\n3. An empty list means *no auth required* for that operation."
|
"docstring": "Build a mapping of ``METHOD:/path`` → list of ``Depends(...)``.\n\nThe effective security for each operation is resolved by:\n\n1. Using the operation-level ``security`` field if present.\n2. Falling back to the top-level ``security`` field.\n3. An empty list means *no auth required* for that operation."
|
||||||
},
|
},
|
||||||
"parse_security_schemes": {
|
"parse_security_schemes": {
|
||||||
"name": "parse_security_schemes",
|
"name": "parse_security_schemes",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.app.parse_security_schemes",
|
"path": "openapi_first.app.parse_security_schemes",
|
||||||
"signature": "parse_security_schemes(spec: dict[str, Any])",
|
"signature": "<bound method Alias.signature of Alias('parse_security_schemes', 'openapi_first.security.parse_security_schemes')>",
|
||||||
"docstring": "Extract and resolve environment variables in security schemes."
|
"docstring": "Extract and resolve environment variables in security schemes."
|
||||||
},
|
},
|
||||||
"OpenAPIFirstApp": {
|
"OpenAPIFirstApp": {
|
||||||
"name": "OpenAPIFirstApp",
|
"name": "OpenAPIFirstApp",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.app.OpenAPIFirstApp",
|
"path": "openapi_first.app.OpenAPIFirstApp",
|
||||||
"signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)",
|
"signature": "<bound method Class.signature of Class('OpenAPIFirstApp', 69, 152)>",
|
||||||
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"openapi": {
|
"openapi": {
|
||||||
"name": "openapi",
|
"name": "openapi",
|
||||||
|
|||||||
@@ -4,18 +4,46 @@
|
|||||||
"path": "openapi_first.binder",
|
"path": "openapi_first.binder",
|
||||||
"docstring": "# Summary\n\nOpenAPI-driven route binding for FastAPI.\n\nThis module is responsible for translating an OpenAPI 3.x specification\ninto concrete FastAPI routes. It enforces a strict one-to-one mapping\nbetween OpenAPI operations and Python handler functions using `operationId`.\n\nNotes:\n **Core Responsibility:**\n\n - Read path + method definitions from an OpenAPI specification.\n - Resolve each `operationId` to a Python callable.\n - Register routes with FastAPI using `APIRoute`.\n - Fail fast when contract violations are detected.\n\n **Design Constraints:**\n\n - All routes MUST be declared in the OpenAPI specification.\n - All OpenAPI operations MUST define an `operationId`.\n - Every `operationId` MUST resolve to a handler function.\n - Handlers are plain Python callables (no decorators required).\n - No implicit route creation or inference is allowed.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Perform request or response validation.\n - Generate Pydantic models.\n - Modify FastAPI dependency injection.\n - Interpret OpenAPI semantics beyond routing metadata.",
|
"docstring": "# Summary\n\nOpenAPI-driven route binding for FastAPI.\n\nThis module is responsible for translating an OpenAPI 3.x specification\ninto concrete FastAPI routes. It enforces a strict one-to-one mapping\nbetween OpenAPI operations and Python handler functions using `operationId`.\n\nNotes:\n **Core Responsibility:**\n\n - Read path + method definitions from an OpenAPI specification.\n - Resolve each `operationId` to a Python callable.\n - Register routes with FastAPI using `APIRoute`.\n - Fail fast when contract violations are detected.\n\n **Design Constraints:**\n\n - All routes MUST be declared in the OpenAPI specification.\n - All OpenAPI operations MUST define an `operationId`.\n - Every `operationId` MUST resolve to a handler function.\n - Handlers are plain Python callables (no decorators required).\n - No implicit route creation or inference is allowed.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Perform request or response validation.\n - Generate Pydantic models.\n - Modify FastAPI dependency injection.\n - Interpret OpenAPI semantics beyond routing metadata.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"ModuleType": {
|
||||||
|
"name": "ModuleType",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.binder.ModuleType",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('ModuleType', 'types.ModuleType')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Any": {
|
||||||
|
"name": "Any",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.binder.Any",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Any', 'typing.Any')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"FastAPI": {
|
||||||
|
"name": "FastAPI",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.binder.FastAPI",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('FastAPI', 'fastapi.FastAPI')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"APIRoute": {
|
||||||
|
"name": "APIRoute",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.binder.APIRoute",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('APIRoute', 'fastapi.routing.APIRoute')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"MissingOperationHandler": {
|
"MissingOperationHandler": {
|
||||||
"name": "MissingOperationHandler",
|
"name": "MissingOperationHandler",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.binder.MissingOperationHandler",
|
"path": "openapi_first.binder.MissingOperationHandler",
|
||||||
"signature": "MissingOperationHandler(*, path: str, method: str, operation_id: str | None = None)",
|
"signature": "<bound method Alias.signature of Alias('MissingOperationHandler', 'openapi_first.errors.MissingOperationHandler')>",
|
||||||
"docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nArgs:\n path (str):\n The HTTP path declared in the OpenAPI specification.\n method (str):\n The HTTP method (as declared in the OpenAPI spec).\n operation_id (str | None):\n The operationId declared in the OpenAPI spec, if present.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync."
|
"docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync."
|
||||||
},
|
},
|
||||||
"bind_routes": {
|
"bind_routes": {
|
||||||
"name": "bind_routes",
|
"name": "bind_routes",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.binder.bind_routes",
|
"path": "openapi_first.binder.bind_routes",
|
||||||
"signature": "bind_routes(app: FastAPI, spec: dict[str, Any], routes_module: ModuleType, security_deps: dict[str, list[Any]] | None = None) -> None",
|
"signature": "<bound method Function.signature of Function('bind_routes', 44, 126)>",
|
||||||
"docstring": "Bind OpenAPI operations to FastAPI routes.\n\nArgs:\n app (FastAPI):\n The FastAPI application instance to which routes will be added.\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification dictionary.\n routes_module (ModuleType):\n Python module containing handler functions. Each handler's name MUST\n exactly match an OpenAPI `operationId`.\n security_deps (dict[str, list[Any]] | None):\n Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``\n generated from the spec's ``securitySchemes`` and per-operation\n ``security`` fields.\n\nRaises:\n MissingOperationHandler:\n If an ``operationId`` is missing from the spec or if no corresponding\n handler function exists in the routes module.\n\nNotes:\n **Responsibilities:**\n\n - Iterates through the OpenAPI specification paths and methods.\n - Resolves each ``operationId`` to a handler function, and registers\n a corresponding ``APIRoute`` on the FastAPI application.\n - Injects FastAPI ``Depends()`` for each security requirement found\n on the operation or inherited from the top-level ``security`` field.\n\n **Guarantees:**\n\n - Route registration is deterministic and spec-driven. No route\n decorators are required or supported. Handler resolution errors\n surface at application startup."
|
"docstring": "Bind OpenAPI operations to FastAPI routes.\n\nArgs:\n app (FastAPI):\n The FastAPI application instance to which routes will be added.\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification dictionary.\n routes_module (ModuleType):\n Python module containing handler functions. Each handler's name MUST\n exactly match an OpenAPI `operationId`.\n security_deps (dict[str, list[Any]] | None):\n Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``\n generated from the spec's ``securitySchemes`` and per-operation\n ``security`` fields.\n\nRaises:\n MissingOperationHandler:\n If an ``operationId`` is missing from the spec or if no corresponding\n handler function exists in the routes module.\n\nNotes:\n **Responsibilities:**\n\n - Iterates through the OpenAPI specification paths and methods.\n - Resolves each ``operationId`` to a handler function, and registers\n a corresponding ``APIRoute`` on the FastAPI application.\n - Injects FastAPI ``Depends()`` for each security requirement found\n on the operation or inherited from the top-level ``security`` field.\n\n **Guarantees:**\n\n - Route registration is deterministic and spec-driven. No route\n decorators are required or supported. Handler resolution errors\n surface at application startup."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,34 @@
|
|||||||
"path": "openapi_first.cli",
|
"path": "openapi_first.cli",
|
||||||
"docstring": "# Summary\n\nCommand-line interface for FastAPI OpenAPI-first scaffolding utilities.\n\nThis CLI bootstraps OpenAPI-first FastAPI applications from versioned,\nbundled templates packaged with the library.",
|
"docstring": "# Summary\n\nCommand-line interface for FastAPI OpenAPI-first scaffolding utilities.\n\nThis CLI bootstraps OpenAPI-first FastAPI applications from versioned,\nbundled templates packaged with the library.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"argparse": {
|
||||||
|
"name": "argparse",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.cli.argparse",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('argparse', 'argparse')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"shutil": {
|
||||||
|
"name": "shutil",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.cli.shutil",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('shutil', 'shutil')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"resources": {
|
||||||
|
"name": "resources",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.cli.resources",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('resources', 'importlib.resources')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Path": {
|
||||||
|
"name": "Path",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.cli.Path",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Path', 'pathlib.Path')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"DEFAULT_TEMPLATE": {
|
"DEFAULT_TEMPLATE": {
|
||||||
"name": "DEFAULT_TEMPLATE",
|
"name": "DEFAULT_TEMPLATE",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
@@ -15,21 +43,21 @@
|
|||||||
"name": "available_templates",
|
"name": "available_templates",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.cli.available_templates",
|
"path": "openapi_first.cli.available_templates",
|
||||||
"signature": "available_templates() -> list[str]",
|
"signature": "<bound method Function.signature of Function('available_templates', 18, 31)>",
|
||||||
"docstring": "Return a list of available application templates.\n\nReturns:\n list[str]:\n Sorted list of template names found in the internal templates directory."
|
"docstring": "Return a list of available application templates.\n\nReturns:\n list[str]:\n Sorted list of template names found in the internal templates directory."
|
||||||
},
|
},
|
||||||
"copy_template": {
|
"copy_template": {
|
||||||
"name": "copy_template",
|
"name": "copy_template",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.cli.copy_template",
|
"path": "openapi_first.cli.copy_template",
|
||||||
"signature": "copy_template(template: str, target_dir: Path) -> None",
|
"signature": "<bound method Function.signature of Function('copy_template', 34, 62)>",
|
||||||
"docstring": "Copy a bundled OpenAPI-first application template into a directory.\n\nArgs:\n template (str):\n Name of the template to copy.\n target_dir (Path):\n Filesystem path where the template should be copied.\n\nRaises:\n FileNotFoundError:\n If the requested template does not exist."
|
"docstring": "Copy a bundled OpenAPI-first application template into a directory.\n\nArgs:\n template (str):\n Name of the template to copy.\n target_dir (Path):\n Filesystem path where the template should be copied.\n\nRaises:\n FileNotFoundError:\n If the requested template does not exist."
|
||||||
},
|
},
|
||||||
"main": {
|
"main": {
|
||||||
"name": "main",
|
"name": "main",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.cli.main",
|
"path": "openapi_first.cli.main",
|
||||||
"signature": "main() -> None",
|
"signature": "<bound method Function.signature of Function('main', 65, 194)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,54 @@
|
|||||||
"path": "openapi_first.client",
|
"path": "openapi_first.client",
|
||||||
"docstring": "# Summary\n\nOpenAPI-first HTTP client for contract-driven services.\n\nThis module provides `OpenAPIClient`, a thin, strict HTTP client that\nderives all callable operations directly from an OpenAPI 3.x specification.\n\nIt is the client counterpart to `OpenAPIFirstApp`.\n\nNotes:\n **Core Principles:**\n\n - The OpenAPI specification is the single source of truth\n - Each operationId becomes a callable Python method\n - No implicit schema mutation or inference\n - No code generation step\n - Minimal abstraction over httpx\n\n **Responsibilities:**\n\n - Parses an OpenAPI 3.x specification\n - Dynamically creates one callable per operationId\n - Enforces presence of servers, paths, and operationId\n - Formats path parameters safely\n - Handles JSON request bodies explicitly\n - Returns raw `httpx.Response` objects\n\n **Constraints:**\n\n - This module intentionally does NOT: Generate client code, validate request/response schemas, deserialize responses, retry requests, implement authentication helpers, or assume non-2xx responses are failures.",
|
"docstring": "# Summary\n\nOpenAPI-first HTTP client for contract-driven services.\n\nThis module provides `OpenAPIClient`, a thin, strict HTTP client that\nderives all callable operations directly from an OpenAPI 3.x specification.\n\nIt is the client counterpart to `OpenAPIFirstApp`.\n\nNotes:\n **Core Principles:**\n\n - The OpenAPI specification is the single source of truth\n - Each operationId becomes a callable Python method\n - No implicit schema mutation or inference\n - No code generation step\n - Minimal abstraction over httpx\n\n **Responsibilities:**\n\n - Parses an OpenAPI 3.x specification\n - Dynamically creates one callable per operationId\n - Enforces presence of servers, paths, and operationId\n - Formats path parameters safely\n - Handles JSON request bodies explicitly\n - Returns raw `httpx.Response` objects\n\n **Constraints:**\n\n - This module intentionally does NOT: Generate client code, validate request/response schemas, deserialize responses, retry requests, implement authentication helpers, or assume non-2xx responses are failures.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"Callable": {
|
||||||
|
"name": "Callable",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.client.Callable",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Callable', 'collections.abc.Callable')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Any": {
|
||||||
|
"name": "Any",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.client.Any",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Any', 'typing.Any')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"urljoin": {
|
||||||
|
"name": "urljoin",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.client.urljoin",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('urljoin', 'urllib.parse.urljoin')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"httpx": {
|
||||||
|
"name": "httpx",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.client.httpx",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('httpx', 'httpx')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"OpenAPIFirstError": {
|
"OpenAPIFirstError": {
|
||||||
"name": "OpenAPIFirstError",
|
"name": "OpenAPIFirstError",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.client.OpenAPIFirstError",
|
"path": "openapi_first.client.OpenAPIFirstError",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstError', 'openapi_first.errors.OpenAPIFirstError')>",
|
||||||
"docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type."
|
"docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type."
|
||||||
},
|
},
|
||||||
"OpenAPIClientError": {
|
"OpenAPIClientError": {
|
||||||
"name": "OpenAPIClientError",
|
"name": "OpenAPIClientError",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.client.OpenAPIClientError",
|
"path": "openapi_first.client.OpenAPIClientError",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('OpenAPIClientError', 43, 46)>",
|
||||||
"docstring": "Raised when an OpenAPI client operation fails."
|
"docstring": "Raised when an OpenAPI client operation fails."
|
||||||
},
|
},
|
||||||
"OpenAPIClient": {
|
"OpenAPIClient": {
|
||||||
"name": "OpenAPIClient",
|
"name": "OpenAPIClient",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.client.OpenAPIClient",
|
"path": "openapi_first.client.OpenAPIClient",
|
||||||
"signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)",
|
"signature": "<bound method Class.signature of Class('OpenAPIClient', 49, 262)>",
|
||||||
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"spec": {
|
"spec": {
|
||||||
"name": "spec",
|
"name": "spec",
|
||||||
@@ -50,7 +78,7 @@
|
|||||||
"name": "operations",
|
"name": "operations",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.client.OpenAPIClient.operations",
|
"path": "openapi_first.client.OpenAPIClient.operations",
|
||||||
"signature": "operations() -> dict[str, Callable[..., httpx.Response]]",
|
"signature": "<bound method Function.signature of Function('operations', 127, 128)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,46 @@
|
|||||||
"path": "openapi_first.codegen",
|
"path": "openapi_first.codegen",
|
||||||
"docstring": "# Summary\n\nCore logic for generating Python source code from OpenAPI specifications.\n\nThis module provides reusable utilities for code generation, specifically\ngenerating Pydantic models and route handler stubs from OpenAPI 3.x schema\ndefinitions.",
|
"docstring": "# Summary\n\nCore logic for generating Python source code from OpenAPI specifications.\n\nThis module provides reusable utilities for code generation, specifically\ngenerating Pydantic models and route handler stubs from OpenAPI 3.x schema\ndefinitions.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"Path": {
|
||||||
|
"name": "Path",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.codegen.Path",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Path', 'pathlib.Path')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"InputFileType": {
|
||||||
|
"name": "InputFileType",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.codegen.InputFileType",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('InputFileType', 'datamodel_code_generator.InputFileType')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"PythonVersion": {
|
||||||
|
"name": "PythonVersion",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.codegen.PythonVersion",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('PythonVersion', 'datamodel_code_generator.PythonVersion')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"generate": {
|
||||||
|
"name": "generate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.codegen.generate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('generate', 'datamodel_code_generator.generate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"generate_routes": {
|
"generate_routes": {
|
||||||
"name": "generate_routes",
|
"name": "generate_routes",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.codegen.generate_routes",
|
"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, template_style: bool = False)",
|
"signature": "<bound method Alias.signature of Alias('generate_routes', 'openapi_first.codegen_routes.generate_routes')>",
|
||||||
"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``."
|
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one ``<resource>.py`` file per resource in *output_dir*.\nResources are derived from the first path segment (e.g. ``/pets``\nand ``/pets/{id}`` both group under ``pets``).\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\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": {
|
"generate_models": {
|
||||||
"name": "generate_models",
|
"name": "generate_models",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.codegen.generate_models",
|
"path": "openapi_first.codegen.generate_models",
|
||||||
"signature": "generate_models(spec_path: Path, output_path: Path, pydantic_version: int = 2) -> None",
|
"signature": "<bound method Function.signature of Function('generate_models', 22, 51)>",
|
||||||
"docstring": "Generate Pydantic models from an OpenAPI specification.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_path (Path):\n Path where the generated Python code should be written.\n pydantic_version (int, optional):\n The Pydantic version to target (1 or 2). Defaults to 2.\n\nNotes:\n **Reusability:**\n This function is designed to be used by the CLI and can be\n exposed as an MCP tool without modification."
|
"docstring": "Generate Pydantic models from an OpenAPI specification.\n\nArgs:\n spec_path (Path):\n Path to the OpenAPI specification file (YAML or JSON).\n output_path (Path):\n Path where the generated Python code should be written.\n pydantic_version (int, optional):\n The Pydantic version to target (1 or 2). Defaults to 2.\n\nNotes:\n **Reusability:**\n This function is designed to be used by the CLI and can be\n exposed as an MCP tool without modification."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,21 +2,35 @@
|
|||||||
"module": "openapi_first.codegen_routes",
|
"module": "openapi_first.codegen_routes",
|
||||||
"content": {
|
"content": {
|
||||||
"path": "openapi_first.codegen_routes",
|
"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``.\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.",
|
"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.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"Path": {
|
||||||
|
"name": "Path",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.codegen_routes.Path",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Path', 'pathlib.Path')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Any": {
|
||||||
|
"name": "Any",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.codegen_routes.Any",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Any', 'typing.Any')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"load_openapi": {
|
"load_openapi": {
|
||||||
"name": "load_openapi",
|
"name": "load_openapi",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.codegen_routes.load_openapi",
|
"path": "openapi_first.codegen_routes.load_openapi",
|
||||||
"signature": "load_openapi(path: str | Path)",
|
"signature": "<bound method Alias.signature of Alias('load_openapi', 'openapi_first.loader.load_openapi')>",
|
||||||
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
||||||
},
|
},
|
||||||
"generate_routes": {
|
"generate_routes": {
|
||||||
"name": "generate_routes",
|
"name": "generate_routes",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.codegen_routes.generate_routes",
|
"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, template_style: bool = False) -> list[Path]",
|
"signature": "<bound method Function.signature of Function('generate_routes', 29, 104)>",
|
||||||
"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``."
|
"docstring": "Generate route handler stubs from an OpenAPI specification.\n\nCreates one ``<resource>.py`` file per resource in *output_dir*.\nResources are derived from the first path segment (e.g. ``/pets``\nand ``/pets/{id}`` both group under ``pets``).\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\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``."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,15 @@
|
|||||||
"name": "OpenAPIFirstError",
|
"name": "OpenAPIFirstError",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.errors.OpenAPIFirstError",
|
"path": "openapi_first.errors.OpenAPIFirstError",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('OpenAPIFirstError', 22, 36)>",
|
||||||
"docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type."
|
"docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type."
|
||||||
},
|
},
|
||||||
"MissingOperationHandler": {
|
"MissingOperationHandler": {
|
||||||
"name": "MissingOperationHandler",
|
"name": "MissingOperationHandler",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.errors.MissingOperationHandler",
|
"path": "openapi_first.errors.MissingOperationHandler",
|
||||||
"signature": "MissingOperationHandler(*, path: str, method: str, operation_id: str | None = None)",
|
"signature": "<bound method Class.signature of Class('MissingOperationHandler', 39, 77)>",
|
||||||
"docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nArgs:\n path (str):\n The HTTP path declared in the OpenAPI specification.\n method (str):\n The HTTP method (as declared in the OpenAPI spec).\n operation_id (str | None):\n The operationId declared in the OpenAPI spec, if present.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync."
|
"docstring": "Raised when an OpenAPI operation cannot be resolved to a handler.\n\nNotes:\n **Scenarios:**\n\n - An OpenAPI operation does not define an `operationId`.\n - An `operationId` is defined but no matching function exists in\n the provided routes module.\n\n **Guarantees:**\n\n - This represents a violation of the OpenAPI-first contract and\n indicates that the specification and implementation are out of\n sync."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,25 +4,60 @@
|
|||||||
"path": "openapi_first.loader",
|
"path": "openapi_first.loader",
|
||||||
"docstring": "# Summary\n\nOpenAPI specification loading and validation utilities.\n\nThis module is responsible for loading an OpenAPI 3.x specification\nfrom disk and validating it before it is used by the application.\n\nIt enforces the principle that an invalid or malformed OpenAPI document\nmust never reach the routing or runtime layers.\n\nNotes:\n **Design Principles:**\n\n - OpenAPI is treated as an authoritative contract.\n - Invalid specifications fail fast at application startup.\n - Supported formats are JSON and YAML.\n - Validation errors are surfaced clearly and early.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Modify the OpenAPI document.\n - Infer missing fields.\n - Generate models or code.\n - Perform request/response validation at runtime.",
|
"docstring": "# Summary\n\nOpenAPI specification loading and validation utilities.\n\nThis module is responsible for loading an OpenAPI 3.x specification\nfrom disk and validating it before it is used by the application.\n\nIt enforces the principle that an invalid or malformed OpenAPI document\nmust never reach the routing or runtime layers.\n\nNotes:\n **Design Principles:**\n\n - OpenAPI is treated as an authoritative contract.\n - Invalid specifications fail fast at application startup.\n - Supported formats are JSON and YAML.\n - Validation errors are surfaced clearly and early.\n\n **Constraints:**\n\n - This module intentionally does NOT:\n - Modify the OpenAPI document.\n - Infer missing fields.\n - Generate models or code.\n - Perform request/response validation at runtime.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"json": {
|
||||||
|
"name": "json",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.loader.json",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('json', 'json')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Path": {
|
||||||
|
"name": "Path",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.loader.Path",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Path', 'pathlib.Path')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Any": {
|
||||||
|
"name": "Any",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.loader.Any",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Any', 'typing.Any')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"yaml": {
|
||||||
|
"name": "yaml",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.loader.yaml",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('yaml', 'yaml')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"validate_spec": {
|
||||||
|
"name": "validate_spec",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.loader.validate_spec",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('validate_spec', 'openapi_spec_validator.validate_spec')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"OpenAPIFirstError": {
|
"OpenAPIFirstError": {
|
||||||
"name": "OpenAPIFirstError",
|
"name": "OpenAPIFirstError",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.loader.OpenAPIFirstError",
|
"path": "openapi_first.loader.OpenAPIFirstError",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstError', 'openapi_first.errors.OpenAPIFirstError')>",
|
||||||
"docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type."
|
"docstring": "Base exception for all OpenAPI-first enforcement errors.\n\nNotes:\n **Responsibilities:**\n\n - This exception exists to allow callers, test suites, and CI\n pipelines to catch and distinguish OpenAPI contract violations\n from unrelated runtime errors.\n - All exceptions raised by the OpenAPI-first core should inherit\n from this type."
|
||||||
},
|
},
|
||||||
"OpenAPISpecLoadError": {
|
"OpenAPISpecLoadError": {
|
||||||
"name": "OpenAPISpecLoadError",
|
"name": "OpenAPISpecLoadError",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.loader.OpenAPISpecLoadError",
|
"path": "openapi_first.loader.OpenAPISpecLoadError",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('OpenAPISpecLoadError', 39, 50)>",
|
||||||
"docstring": "Raised when an OpenAPI specification cannot be loaded or validated.\n\nNotes:\n **Guarantees:**\n\n - This error indicates that the OpenAPI document is unreadable,\n malformed, or violates the OpenAPI 3.x specification."
|
"docstring": "Raised when an OpenAPI specification cannot be loaded or validated.\n\nNotes:\n **Guarantees:**\n\n - This error indicates that the OpenAPI document is unreadable,\n malformed, or violates the OpenAPI 3.x specification."
|
||||||
},
|
},
|
||||||
"load_openapi": {
|
"load_openapi": {
|
||||||
"name": "load_openapi",
|
"name": "load_openapi",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.loader.load_openapi",
|
"path": "openapi_first.loader.load_openapi",
|
||||||
"signature": "load_openapi(path: str | Path) -> dict[str, Any]",
|
"signature": "<bound method Function.signature of Function('load_openapi', 53, 110)>",
|
||||||
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,88 @@
|
|||||||
"path": "openapi_first.security",
|
"path": "openapi_first.security",
|
||||||
"docstring": "# Summary\n\nOpenAPI security scheme parsing and auto-generated auth dependencies.\n\nThis module reads `securitySchemes` and per-operation `security` from an\nOpenAPI spec, resolves `{ENV_VAR}` placeholders in `x-` extension fields,\nand generates FastAPI dependencies for token validation (e.g., Bearer JWT\nintrospection).",
|
"docstring": "# Summary\n\nOpenAPI security scheme parsing and auto-generated auth dependencies.\n\nThis module reads `securitySchemes` and per-operation `security` from an\nOpenAPI spec, resolves `{ENV_VAR}` placeholders in `x-` extension fields,\nand generates FastAPI dependencies for token validation (e.g., Bearer JWT\nintrospection).",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"os": {
|
||||||
|
"name": "os",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.os",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('os', 'os')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"re": {
|
||||||
|
"name": "re",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.re",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('re', 're')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Callable": {
|
||||||
|
"name": "Callable",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.Callable",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Callable', 'collections.abc.Callable')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Any": {
|
||||||
|
"name": "Any",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.Any",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Any', 'typing.Any')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"httpx": {
|
||||||
|
"name": "httpx",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.httpx",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('httpx', 'httpx')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Depends": {
|
||||||
|
"name": "Depends",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.Depends",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Depends', 'fastapi.Depends')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HTTPException": {
|
||||||
|
"name": "HTTPException",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.HTTPException",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HTTPException', 'fastapi.HTTPException')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Request": {
|
||||||
|
"name": "Request",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.Request",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Request', 'fastapi.Request')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HTTPAuthorizationCredentials": {
|
||||||
|
"name": "HTTPAuthorizationCredentials",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.HTTPAuthorizationCredentials",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HTTPAuthorizationCredentials', 'fastapi.security.HTTPAuthorizationCredentials')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HTTPBearer": {
|
||||||
|
"name": "HTTPBearer",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.security.HTTPBearer",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HTTPBearer', 'fastapi.security.HTTPBearer')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"parse_security_schemes": {
|
"parse_security_schemes": {
|
||||||
"name": "parse_security_schemes",
|
"name": "parse_security_schemes",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.security.parse_security_schemes",
|
"path": "openapi_first.security.parse_security_schemes",
|
||||||
"signature": "parse_security_schemes(spec: dict[str, Any]) -> dict[str, dict[str, Any]]",
|
"signature": "<bound method Function.signature of Function('parse_security_schemes', 46, 49)>",
|
||||||
"docstring": "Extract and resolve environment variables in security schemes."
|
"docstring": "Extract and resolve environment variables in security schemes."
|
||||||
},
|
},
|
||||||
"make_security_dependencies": {
|
"make_security_dependencies": {
|
||||||
"name": "make_security_dependencies",
|
"name": "make_security_dependencies",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.security.make_security_dependencies",
|
"path": "openapi_first.security.make_security_dependencies",
|
||||||
"signature": "make_security_dependencies(spec: dict[str, Any], security_schemes: dict[str, dict[str, Any]]) -> dict[str, list[Any]]",
|
"signature": "<bound method Function.signature of Function('make_security_dependencies', 116, 153)>",
|
||||||
"docstring": "Build a mapping of ``METHOD:/path`` → list of ``Depends(...)``.\n\nThe effective security for each operation is resolved by:\n\n1. Using the operation-level ``security`` field if present.\n2. Falling back to the top-level ``security`` field.\n3. An empty list means *no auth required* for that operation."
|
"docstring": "Build a mapping of ``METHOD:/path`` → list of ``Depends(...)``.\n\nThe effective security for each operation is resolved by:\n\n1. Using the operation-level ``security`` field if present.\n2. Falling back to the top-level ``security`` field.\n3. An empty list means *no auth required* for that operation."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,39 +4,46 @@
|
|||||||
"path": "openapi_first.templates.crud_app.data",
|
"path": "openapi_first.templates.crud_app.data",
|
||||||
"docstring": "In-memory mock data store for CRUD example.\n\nThis module intentionally avoids persistence and concurrency guarantees.\nIt is suitable for demos, tests, and scaffolding only.\n\nIt intentionally avoids\n- persistence\n- concurrency guarantees\n- validation\n- error handling\n\nThe implementation is suitable for:\n- demonstrations\n- tests\n- scaffolding and example services\n\nIt is explicitly NOT suitable for production use.\n\nThis module is not part of the ``openapi_first`` library API surface.",
|
"docstring": "In-memory mock data store for CRUD example.\n\nThis module intentionally avoids persistence and concurrency guarantees.\nIt is suitable for demos, tests, and scaffolding only.\n\nIt intentionally avoids\n- persistence\n- concurrency guarantees\n- validation\n- error handling\n\nThe implementation is suitable for:\n- demonstrations\n- tests\n- scaffolding and example services\n\nIt is explicitly NOT suitable for production use.\n\nThis module is not part of the ``openapi_first`` library API surface.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"Dict": {
|
||||||
|
"name": "Dict",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.data.Dict",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Dict', 'typing.Dict')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_items": {
|
"list_items": {
|
||||||
"name": "list_items",
|
"name": "list_items",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.list_items",
|
"path": "openapi_first.templates.crud_app.data.list_items",
|
||||||
"signature": "list_items() -> list[dict]",
|
"signature": "<bound method Function.signature of Function('list_items', 36, 48)>",
|
||||||
"docstring": "Return all items in the data store.\n\nThis function performs no filtering, pagination, or sorting.\nThe returned collection reflects the current in-memory state.\n\nReturns\n-------\nlist[dict]\n A list of item representations."
|
"docstring": "Return all items in the data store.\n\nThis function performs no filtering, pagination, or sorting.\nThe returned collection reflects the current in-memory state.\n\nReturns\n-------\nlist[dict]\n A list of item representations."
|
||||||
},
|
},
|
||||||
"get_item": {
|
"get_item": {
|
||||||
"name": "get_item",
|
"name": "get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.get_item",
|
"path": "openapi_first.templates.crud_app.data.get_item",
|
||||||
"signature": "get_item(item_id: int) -> dict",
|
"signature": "<bound method Function.signature of Function('get_item', 51, 68)>",
|
||||||
"docstring": "Retrieve a single item by ID.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present in the store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The stored item representation."
|
"docstring": "Retrieve a single item by ID.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present in the store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The stored item representation."
|
||||||
},
|
},
|
||||||
"create_item": {
|
"create_item": {
|
||||||
"name": "create_item",
|
"name": "create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.create_item",
|
"path": "openapi_first.templates.crud_app.data.create_item",
|
||||||
"signature": "create_item(payload: dict) -> dict",
|
"signature": "<bound method Function.signature of Function('create_item', 71, 92)>",
|
||||||
"docstring": "Create a new item in the data store.\n\nA new integer ID is assigned automatically. No validation is\nperformed on the provided payload.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The newly created item, including its assigned ID."
|
"docstring": "Create a new item in the data store.\n\nA new integer ID is assigned automatically. No validation is\nperformed on the provided payload.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The newly created item, including its assigned ID."
|
||||||
},
|
},
|
||||||
"update_item": {
|
"update_item": {
|
||||||
"name": "update_item",
|
"name": "update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.update_item",
|
"path": "openapi_first.templates.crud_app.data.update_item",
|
||||||
"signature": "update_item(item_id: int, payload: dict) -> dict",
|
"signature": "<bound method Function.signature of Function('update_item', 95, 117)>",
|
||||||
"docstring": "Replace an existing item in the data store.\n\nThis function overwrites the existing item entirely and does not\nperform partial updates or validation. If the item does not exist,\nit will be created implicitly.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item representation."
|
"docstring": "Replace an existing item in the data store.\n\nThis function overwrites the existing item entirely and does not\nperform partial updates or validation. If the item does not exist,\nit will be created implicitly.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item representation."
|
||||||
},
|
},
|
||||||
"delete_item": {
|
"delete_item": {
|
||||||
"name": "delete_item",
|
"name": "delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.delete_item",
|
"path": "openapi_first.templates.crud_app.data.delete_item",
|
||||||
"signature": "delete_item(item_id: int)",
|
"signature": "<bound method Function.signature of Function('delete_item', 120, 132)>",
|
||||||
"docstring": "Remove an item from the data store.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete."
|
"docstring": "Remove an item from the data store.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"module": "openapi_first.templates.crud_app",
|
"module": "openapi_first.templates.crud_app",
|
||||||
"content": {
|
"content": {
|
||||||
"path": "openapi_first.templates.crud_app",
|
"path": "openapi_first.templates.crud_app",
|
||||||
"docstring": "OpenAPI-first CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new CRUD example service using the bundled template:\n\n openapi-first scaffold crud_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold crud_app my-crud-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.",
|
"docstring": "OpenAPI-first CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new CRUD example service using the bundled template:\n\n openapi-first crud_app\n\nCreate the service in a custom directory:\n\n openapi-first crud_app my-crud-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.",
|
||||||
"objects": {
|
"objects": {
|
||||||
"data": {
|
"data": {
|
||||||
"name": "data",
|
"name": "data",
|
||||||
@@ -11,39 +11,46 @@
|
|||||||
"signature": null,
|
"signature": null,
|
||||||
"docstring": "In-memory mock data store for CRUD example.\n\nThis module intentionally avoids persistence and concurrency guarantees.\nIt is suitable for demos, tests, and scaffolding only.\n\nIt intentionally avoids\n- persistence\n- concurrency guarantees\n- validation\n- error handling\n\nThe implementation is suitable for:\n- demonstrations\n- tests\n- scaffolding and example services\n\nIt is explicitly NOT suitable for production use.\n\nThis module is not part of the ``openapi_first`` library API surface.",
|
"docstring": "In-memory mock data store for CRUD example.\n\nThis module intentionally avoids persistence and concurrency guarantees.\nIt is suitable for demos, tests, and scaffolding only.\n\nIt intentionally avoids\n- persistence\n- concurrency guarantees\n- validation\n- error handling\n\nThe implementation is suitable for:\n- demonstrations\n- tests\n- scaffolding and example services\n\nIt is explicitly NOT suitable for production use.\n\nThis module is not part of the ``openapi_first`` library API surface.",
|
||||||
"members": {
|
"members": {
|
||||||
|
"Dict": {
|
||||||
|
"name": "Dict",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.data.Dict",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Dict', 'typing.Dict')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_items": {
|
"list_items": {
|
||||||
"name": "list_items",
|
"name": "list_items",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.list_items",
|
"path": "openapi_first.templates.crud_app.data.list_items",
|
||||||
"signature": "list_items() -> list[dict]",
|
"signature": "<bound method Function.signature of Function('list_items', 36, 48)>",
|
||||||
"docstring": "Return all items in the data store.\n\nThis function performs no filtering, pagination, or sorting.\nThe returned collection reflects the current in-memory state.\n\nReturns\n-------\nlist[dict]\n A list of item representations."
|
"docstring": "Return all items in the data store.\n\nThis function performs no filtering, pagination, or sorting.\nThe returned collection reflects the current in-memory state.\n\nReturns\n-------\nlist[dict]\n A list of item representations."
|
||||||
},
|
},
|
||||||
"get_item": {
|
"get_item": {
|
||||||
"name": "get_item",
|
"name": "get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.get_item",
|
"path": "openapi_first.templates.crud_app.data.get_item",
|
||||||
"signature": "get_item(item_id: int) -> dict",
|
"signature": "<bound method Function.signature of Function('get_item', 51, 68)>",
|
||||||
"docstring": "Retrieve a single item by ID.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present in the store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The stored item representation."
|
"docstring": "Retrieve a single item by ID.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present in the store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The stored item representation."
|
||||||
},
|
},
|
||||||
"create_item": {
|
"create_item": {
|
||||||
"name": "create_item",
|
"name": "create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.create_item",
|
"path": "openapi_first.templates.crud_app.data.create_item",
|
||||||
"signature": "create_item(payload: dict) -> dict",
|
"signature": "<bound method Function.signature of Function('create_item', 71, 92)>",
|
||||||
"docstring": "Create a new item in the data store.\n\nA new integer ID is assigned automatically. No validation is\nperformed on the provided payload.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The newly created item, including its assigned ID."
|
"docstring": "Create a new item in the data store.\n\nA new integer ID is assigned automatically. No validation is\nperformed on the provided payload.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The newly created item, including its assigned ID."
|
||||||
},
|
},
|
||||||
"update_item": {
|
"update_item": {
|
||||||
"name": "update_item",
|
"name": "update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.update_item",
|
"path": "openapi_first.templates.crud_app.data.update_item",
|
||||||
"signature": "update_item(item_id: int, payload: dict) -> dict",
|
"signature": "<bound method Function.signature of Function('update_item', 95, 117)>",
|
||||||
"docstring": "Replace an existing item in the data store.\n\nThis function overwrites the existing item entirely and does not\nperform partial updates or validation. If the item does not exist,\nit will be created implicitly.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item representation."
|
"docstring": "Replace an existing item in the data store.\n\nThis function overwrites the existing item entirely and does not\nperform partial updates or validation. If the item does not exist,\nit will be created implicitly.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item representation."
|
||||||
},
|
},
|
||||||
"delete_item": {
|
"delete_item": {
|
||||||
"name": "delete_item",
|
"name": "delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.data.delete_item",
|
"path": "openapi_first.templates.crud_app.data.delete_item",
|
||||||
"signature": "delete_item(item_id: int)",
|
"signature": "<bound method Function.signature of Function('delete_item', 120, 132)>",
|
||||||
"docstring": "Remove an item from the data store.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete."
|
"docstring": "Remove an item from the data store.\n\nThis function assumes the item exists and will raise ``KeyError``\nif the ID is not present.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,18 +66,25 @@
|
|||||||
"name": "OpenAPIFirstApp",
|
"name": "OpenAPIFirstApp",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp",
|
"path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp",
|
||||||
"signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstApp', 'openapi_first.app.OpenAPIFirstApp')>",
|
||||||
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"openapi": {
|
"openapi": {
|
||||||
"name": "openapi",
|
"name": "openapi",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp.openapi",
|
"path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp.openapi",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('openapi', 'openapi_first.app.OpenAPIFirstApp.openapi')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"routes": {
|
||||||
|
"name": "routes",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.main.routes",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('routes', 'routes')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
@@ -87,40 +101,54 @@
|
|||||||
"signature": null,
|
"signature": null,
|
||||||
"docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThese handlers explicitly control HTTP status codes to ensure\nruntime behavior matches the OpenAPI contract.\n\nThis module defines OpenAPI-bound operation handlers for a simple CRUD\nservice. Functions in this module are bound to HTTP routes exclusively\nvia OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Error conditions are translated\ninto explicit HTTP responses rather than relying on implicit framework\nbehavior.\n\nNo routing decorators or path definitions appear in this module. All\nrouting, HTTP methods, and schemas are defined in the OpenAPI\nspecification.",
|
"docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThese handlers explicitly control HTTP status codes to ensure\nruntime behavior matches the OpenAPI contract.\n\nThis module defines OpenAPI-bound operation handlers for a simple CRUD\nservice. Functions in this module are bound to HTTP routes exclusively\nvia OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Error conditions are translated\ninto explicit HTTP responses rather than relying on implicit framework\nbehavior.\n\nNo routing decorators or path definitions appear in this module. All\nrouting, HTTP methods, and schemas are defined in the OpenAPI\nspecification.",
|
||||||
"members": {
|
"members": {
|
||||||
|
"Response": {
|
||||||
|
"name": "Response",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.routes.Response",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Response', 'fastapi.Response')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HTTPException": {
|
||||||
|
"name": "HTTPException",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.routes.HTTPException",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HTTPException', 'fastapi.HTTPException')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_items": {
|
"list_items": {
|
||||||
"name": "list_items",
|
"name": "list_items",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.list_items",
|
"path": "openapi_first.templates.crud_app.routes.list_items",
|
||||||
"signature": "list_items() -> list[dict]",
|
"signature": "<bound method Function.signature of Function('list_items', 32, 44)>",
|
||||||
"docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[dict]\n A list of item representations."
|
"docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[dict]\n A list of item representations."
|
||||||
},
|
},
|
||||||
"get_item": {
|
"get_item": {
|
||||||
"name": "get_item",
|
"name": "get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.get_item",
|
"path": "openapi_first.templates.crud_app.routes.get_item",
|
||||||
"signature": "get_item(item_id: int) -> dict",
|
"signature": "<bound method Function.signature of Function('get_item', 47, 72)>",
|
||||||
"docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
},
|
},
|
||||||
"create_item": {
|
"create_item": {
|
||||||
"name": "create_item",
|
"name": "create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.create_item",
|
"path": "openapi_first.templates.crud_app.routes.create_item",
|
||||||
"signature": "create_item(payload: dict, response: Response) -> dict",
|
"signature": "<bound method Function.signature of Function('create_item', 75, 96)>",
|
||||||
"docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item."
|
"docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item."
|
||||||
},
|
},
|
||||||
"update_item": {
|
"update_item": {
|
||||||
"name": "update_item",
|
"name": "update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.update_item",
|
"path": "openapi_first.templates.crud_app.routes.update_item",
|
||||||
"signature": "update_item(item_id: int, payload: dict) -> dict",
|
"signature": "<bound method Function.signature of Function('update_item', 99, 126)>",
|
||||||
"docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
},
|
},
|
||||||
"delete_item": {
|
"delete_item": {
|
||||||
"name": "delete_item",
|
"name": "delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.delete_item",
|
"path": "openapi_first.templates.crud_app.routes.delete_item",
|
||||||
"signature": "delete_item(item_id: int, response: Response) -> None",
|
"signature": "<bound method Function.signature of Function('delete_item', 129, 158)>",
|
||||||
"docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -131,46 +159,60 @@
|
|||||||
"signature": null,
|
"signature": null,
|
||||||
"docstring": "End-to-end tests for the OpenAPI-first CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Correct HTTP status codes and response payloads\n\nThe tests exercise all CRUD operations against an in-memory mock data\nstore and assume deterministic behavior within a single process.\n\nThe tests assume:\n- OpenAPI-first route binding\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process\n- One-to-one correspondence between OpenAPI operationId values and\n server/client callables",
|
"docstring": "End-to-end tests for the OpenAPI-first CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Correct HTTP status codes and response payloads\n\nThe tests exercise all CRUD operations against an in-memory mock data\nstore and assume deterministic behavior within a single process.\n\nThe tests assume:\n- OpenAPI-first route binding\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process\n- One-to-one correspondence between OpenAPI operationId values and\n server/client callables",
|
||||||
"members": {
|
"members": {
|
||||||
|
"TestClient": {
|
||||||
|
"name": "TestClient",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.test_crud_app.TestClient",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('TestClient', 'fastapi.testclient.TestClient')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"name": "app",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.test_crud_app.app",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('app', 'main.app')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"load_openapi": {
|
"load_openapi": {
|
||||||
"name": "load_openapi",
|
"name": "load_openapi",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.load_openapi",
|
"path": "openapi_first.templates.crud_app.test_crud_app.load_openapi",
|
||||||
"signature": "load_openapi(path: str | Path)",
|
"signature": "<bound method Alias.signature of Alias('load_openapi', 'openapi_first.loader.load_openapi')>",
|
||||||
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
||||||
},
|
},
|
||||||
"OpenAPIClient": {
|
"OpenAPIClient": {
|
||||||
"name": "OpenAPIClient",
|
"name": "OpenAPIClient",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient",
|
||||||
"signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIClient', 'openapi_first.client.OpenAPIClient')>",
|
||||||
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"spec": {
|
"spec": {
|
||||||
"name": "spec",
|
"name": "spec",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.spec",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.spec",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('spec', 'openapi_first.client.OpenAPIClient.spec')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"base_url": {
|
"base_url": {
|
||||||
"name": "base_url",
|
"name": "base_url",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.base_url",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.base_url",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('base_url', 'openapi_first.client.OpenAPIClient.base_url')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.client",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.client",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('client', 'openapi_first.client.OpenAPIClient.client')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"name": "operations",
|
"name": "operations",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.operations",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.operations",
|
||||||
"signature": "operations()",
|
"signature": "<bound method Alias.signature of Alias('operations', 'openapi_first.client.OpenAPIClient.operations')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,35 +235,35 @@
|
|||||||
"name": "test_list_items_initial",
|
"name": "test_list_items_initial",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_list_items_initial",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_list_items_initial",
|
||||||
"signature": "test_list_items_initial()",
|
"signature": "<bound method Function.signature of Function('test_list_items_initial', 38, 49)>",
|
||||||
"docstring": "Initial items should be present."
|
"docstring": "Initial items should be present."
|
||||||
},
|
},
|
||||||
"test_get_item": {
|
"test_get_item": {
|
||||||
"name": "test_get_item",
|
"name": "test_get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_get_item",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_get_item",
|
||||||
"signature": "test_get_item()",
|
"signature": "<bound method Function.signature of Function('test_get_item', 52, 62)>",
|
||||||
"docstring": "Existing item should be retrievable by ID."
|
"docstring": "Existing item should be retrievable by ID."
|
||||||
},
|
},
|
||||||
"test_create_item": {
|
"test_create_item": {
|
||||||
"name": "test_create_item",
|
"name": "test_create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_create_item",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_create_item",
|
||||||
"signature": "test_create_item()",
|
"signature": "<bound method Function.signature of Function('test_create_item', 65, 85)>",
|
||||||
"docstring": "Creating a new item should return the created entity."
|
"docstring": "Creating a new item should return the created entity."
|
||||||
},
|
},
|
||||||
"test_update_item": {
|
"test_update_item": {
|
||||||
"name": "test_update_item",
|
"name": "test_update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_update_item",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_update_item",
|
||||||
"signature": "test_update_item()",
|
"signature": "<bound method Function.signature of Function('test_update_item', 88, 112)>",
|
||||||
"docstring": "Updating an item should replace its values."
|
"docstring": "Updating an item should replace its values."
|
||||||
},
|
},
|
||||||
"test_delete_item": {
|
"test_delete_item": {
|
||||||
"name": "test_delete_item",
|
"name": "test_delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_delete_item",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_delete_item",
|
||||||
"signature": "test_delete_item()",
|
"signature": "<bound method Function.signature of Function('test_delete_item', 115, 125)>",
|
||||||
"docstring": "Deleting an item should remove it from the store."
|
"docstring": "Deleting an item should remove it from the store."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,25 @@
|
|||||||
"name": "OpenAPIFirstApp",
|
"name": "OpenAPIFirstApp",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp",
|
"path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp",
|
||||||
"signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstApp', 'openapi_first.app.OpenAPIFirstApp')>",
|
||||||
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"openapi": {
|
"openapi": {
|
||||||
"name": "openapi",
|
"name": "openapi",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp.openapi",
|
"path": "openapi_first.templates.crud_app.main.OpenAPIFirstApp.openapi",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('openapi', 'openapi_first.app.OpenAPIFirstApp.openapi')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"routes": {
|
||||||
|
"name": "routes",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.main.routes",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('routes', 'routes')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
|
|||||||
@@ -4,40 +4,54 @@
|
|||||||
"path": "openapi_first.templates.crud_app.routes",
|
"path": "openapi_first.templates.crud_app.routes",
|
||||||
"docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThese handlers explicitly control HTTP status codes to ensure\nruntime behavior matches the OpenAPI contract.\n\nThis module defines OpenAPI-bound operation handlers for a simple CRUD\nservice. Functions in this module are bound to HTTP routes exclusively\nvia OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Error conditions are translated\ninto explicit HTTP responses rather than relying on implicit framework\nbehavior.\n\nNo routing decorators or path definitions appear in this module. All\nrouting, HTTP methods, and schemas are defined in the OpenAPI\nspecification.",
|
"docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThese handlers explicitly control HTTP status codes to ensure\nruntime behavior matches the OpenAPI contract.\n\nThis module defines OpenAPI-bound operation handlers for a simple CRUD\nservice. Functions in this module are bound to HTTP routes exclusively\nvia OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Error conditions are translated\ninto explicit HTTP responses rather than relying on implicit framework\nbehavior.\n\nNo routing decorators or path definitions appear in this module. All\nrouting, HTTP methods, and schemas are defined in the OpenAPI\nspecification.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"Response": {
|
||||||
|
"name": "Response",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.routes.Response",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Response', 'fastapi.Response')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HTTPException": {
|
||||||
|
"name": "HTTPException",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.routes.HTTPException",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HTTPException', 'fastapi.HTTPException')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_items": {
|
"list_items": {
|
||||||
"name": "list_items",
|
"name": "list_items",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.list_items",
|
"path": "openapi_first.templates.crud_app.routes.list_items",
|
||||||
"signature": "list_items() -> list[dict]",
|
"signature": "<bound method Function.signature of Function('list_items', 32, 44)>",
|
||||||
"docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[dict]\n A list of item representations."
|
"docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[dict]\n A list of item representations."
|
||||||
},
|
},
|
||||||
"get_item": {
|
"get_item": {
|
||||||
"name": "get_item",
|
"name": "get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.get_item",
|
"path": "openapi_first.templates.crud_app.routes.get_item",
|
||||||
"signature": "get_item(item_id: int) -> dict",
|
"signature": "<bound method Function.signature of Function('get_item', 47, 72)>",
|
||||||
"docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\ndict\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
},
|
},
|
||||||
"create_item": {
|
"create_item": {
|
||||||
"name": "create_item",
|
"name": "create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.create_item",
|
"path": "openapi_first.templates.crud_app.routes.create_item",
|
||||||
"signature": "create_item(payload: dict, response: Response) -> dict",
|
"signature": "<bound method Function.signature of Function('create_item', 75, 96)>",
|
||||||
"docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item."
|
"docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : dict\n Item attributes excluding the ``id`` field.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\ndict\n The newly created item."
|
||||||
},
|
},
|
||||||
"update_item": {
|
"update_item": {
|
||||||
"name": "update_item",
|
"name": "update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.update_item",
|
"path": "openapi_first.templates.crud_app.routes.update_item",
|
||||||
"signature": "update_item(item_id: int, payload: dict) -> dict",
|
"signature": "<bound method Function.signature of Function('update_item', 99, 126)>",
|
||||||
"docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : dict\n Item attributes excluding the ``id`` field.\n\nReturns\n-------\ndict\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
},
|
},
|
||||||
"delete_item": {
|
"delete_item": {
|
||||||
"name": "delete_item",
|
"name": "delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.routes.delete_item",
|
"path": "openapi_first.templates.crud_app.routes.delete_item",
|
||||||
"signature": "delete_item(item_id: int, response: Response) -> None",
|
"signature": "<bound method Function.signature of Function('delete_item', 129, 158)>",
|
||||||
"docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,46 +4,60 @@
|
|||||||
"path": "openapi_first.templates.crud_app.test_crud_app",
|
"path": "openapi_first.templates.crud_app.test_crud_app",
|
||||||
"docstring": "End-to-end tests for the OpenAPI-first CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Correct HTTP status codes and response payloads\n\nThe tests exercise all CRUD operations against an in-memory mock data\nstore and assume deterministic behavior within a single process.\n\nThe tests assume:\n- OpenAPI-first route binding\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process\n- One-to-one correspondence between OpenAPI operationId values and\n server/client callables",
|
"docstring": "End-to-end tests for the OpenAPI-first CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Correct HTTP status codes and response payloads\n\nThe tests exercise all CRUD operations against an in-memory mock data\nstore and assume deterministic behavior within a single process.\n\nThe tests assume:\n- OpenAPI-first route binding\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process\n- One-to-one correspondence between OpenAPI operationId values and\n server/client callables",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"TestClient": {
|
||||||
|
"name": "TestClient",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.test_crud_app.TestClient",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('TestClient', 'fastapi.testclient.TestClient')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"name": "app",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.crud_app.test_crud_app.app",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('app', 'main.app')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"load_openapi": {
|
"load_openapi": {
|
||||||
"name": "load_openapi",
|
"name": "load_openapi",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.load_openapi",
|
"path": "openapi_first.templates.crud_app.test_crud_app.load_openapi",
|
||||||
"signature": "load_openapi(path: str | Path)",
|
"signature": "<bound method Alias.signature of Alias('load_openapi', 'openapi_first.loader.load_openapi')>",
|
||||||
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
||||||
},
|
},
|
||||||
"OpenAPIClient": {
|
"OpenAPIClient": {
|
||||||
"name": "OpenAPIClient",
|
"name": "OpenAPIClient",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient",
|
||||||
"signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIClient', 'openapi_first.client.OpenAPIClient')>",
|
||||||
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"spec": {
|
"spec": {
|
||||||
"name": "spec",
|
"name": "spec",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.spec",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.spec",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('spec', 'openapi_first.client.OpenAPIClient.spec')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"base_url": {
|
"base_url": {
|
||||||
"name": "base_url",
|
"name": "base_url",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.base_url",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.base_url",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('base_url', 'openapi_first.client.OpenAPIClient.base_url')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.client",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.client",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('client', 'openapi_first.client.OpenAPIClient.client')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"name": "operations",
|
"name": "operations",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.operations",
|
"path": "openapi_first.templates.crud_app.test_crud_app.OpenAPIClient.operations",
|
||||||
"signature": "operations()",
|
"signature": "<bound method Alias.signature of Alias('operations', 'openapi_first.client.OpenAPIClient.operations')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,35 +80,35 @@
|
|||||||
"name": "test_list_items_initial",
|
"name": "test_list_items_initial",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_list_items_initial",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_list_items_initial",
|
||||||
"signature": "test_list_items_initial()",
|
"signature": "<bound method Function.signature of Function('test_list_items_initial', 38, 49)>",
|
||||||
"docstring": "Initial items should be present."
|
"docstring": "Initial items should be present."
|
||||||
},
|
},
|
||||||
"test_get_item": {
|
"test_get_item": {
|
||||||
"name": "test_get_item",
|
"name": "test_get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_get_item",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_get_item",
|
||||||
"signature": "test_get_item()",
|
"signature": "<bound method Function.signature of Function('test_get_item', 52, 62)>",
|
||||||
"docstring": "Existing item should be retrievable by ID."
|
"docstring": "Existing item should be retrievable by ID."
|
||||||
},
|
},
|
||||||
"test_create_item": {
|
"test_create_item": {
|
||||||
"name": "test_create_item",
|
"name": "test_create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_create_item",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_create_item",
|
||||||
"signature": "test_create_item()",
|
"signature": "<bound method Function.signature of Function('test_create_item', 65, 85)>",
|
||||||
"docstring": "Creating a new item should return the created entity."
|
"docstring": "Creating a new item should return the created entity."
|
||||||
},
|
},
|
||||||
"test_update_item": {
|
"test_update_item": {
|
||||||
"name": "test_update_item",
|
"name": "test_update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_update_item",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_update_item",
|
||||||
"signature": "test_update_item()",
|
"signature": "<bound method Function.signature of Function('test_update_item', 88, 112)>",
|
||||||
"docstring": "Updating an item should replace its values."
|
"docstring": "Updating an item should replace its values."
|
||||||
},
|
},
|
||||||
"test_delete_item": {
|
"test_delete_item": {
|
||||||
"name": "test_delete_item",
|
"name": "test_delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.crud_app.test_crud_app.test_delete_item",
|
"path": "openapi_first.templates.crud_app.test_crud_app.test_delete_item",
|
||||||
"signature": "test_delete_item()",
|
"signature": "<bound method Function.signature of Function('test_delete_item', 115, 125)>",
|
||||||
"docstring": "Deleting an item should remove it from the store."
|
"docstring": "Deleting an item should remove it from the store."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"module": "openapi_first.templates.health_app",
|
"module": "openapi_first.templates.health_app",
|
||||||
"content": {
|
"content": {
|
||||||
"path": "openapi_first.templates.health_app",
|
"path": "openapi_first.templates.health_app",
|
||||||
"docstring": "OpenAPI-first FastAPI application template.\n\nThis package contains a minimal, fully working example of an\nOpenAPI-first FastAPI service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace (``routes``)\n\nNo routing decorators, implicit behavior, or framework-specific\nconvenience abstractions are used. All HTTP routes, methods, and\noperation bindings are defined in OpenAPI and enforced at application\nstartup.\n\nThis package is intended to be copied as a starting point for new\nservices via the ``openapi-first`` CLI. It is not part of the\n``openapi_first`` library API surface.\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new OpenAPI-first health check service using the bundled\ntemplate:\n\n openapi-first scaffold health_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold health_app my-health-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client call for the ``get_health`` operation:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n response = client.get_health()\n\n assert response.status_code == 200\n assert response.json() == {\"status\": \"ok\"}\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time",
|
"docstring": "OpenAPI-first FastAPI application template.\n\nThis package contains a minimal, fully working example of an\nOpenAPI-first FastAPI service built using the ``openapi_first`` library.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace (``routes``)\n\nNo routing decorators, implicit behavior, or framework-specific\nconvenience abstractions are used. All HTTP routes, methods, and\noperation bindings are defined in OpenAPI and enforced at application\nstartup.\n\nThis package is intended to be copied as a starting point for new\nservices via the ``openapi-first`` CLI. It is not part of the\n``openapi_first`` library API surface.\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new OpenAPI-first health check service using the bundled\ntemplate:\n\n openapi-first health_app\n\nCreate the service in a custom directory:\n\n openapi-first health_app my-health-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client call for the ``get_health`` operation:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n response = client.get_health()\n\n assert response.status_code == 200\n assert response.json() == {\"status\": \"ok\"}\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Path and request parameters must match the OpenAPI specification\n- Invalid or incomplete OpenAPI specs fail at client construction time",
|
||||||
"objects": {
|
"objects": {
|
||||||
"main": {
|
"main": {
|
||||||
"name": "main",
|
"name": "main",
|
||||||
@@ -15,18 +15,25 @@
|
|||||||
"name": "OpenAPIFirstApp",
|
"name": "OpenAPIFirstApp",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.health_app.main.OpenAPIFirstApp",
|
"path": "openapi_first.templates.health_app.main.OpenAPIFirstApp",
|
||||||
"signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstApp', 'openapi_first.app.OpenAPIFirstApp')>",
|
||||||
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"openapi": {
|
"openapi": {
|
||||||
"name": "openapi",
|
"name": "openapi",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.health_app.main.OpenAPIFirstApp.openapi",
|
"path": "openapi_first.templates.health_app.main.OpenAPIFirstApp.openapi",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('openapi', 'openapi_first.app.OpenAPIFirstApp.openapi')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"routes": {
|
||||||
|
"name": "routes",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.health_app.main.routes",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('routes', 'routes')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
@@ -47,7 +54,7 @@
|
|||||||
"name": "get_health",
|
"name": "get_health",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.health_app.routes.get_health",
|
"path": "openapi_first.templates.health_app.routes.get_health",
|
||||||
"signature": "get_health() -> dict",
|
"signature": "<bound method Function.signature of Function('get_health', 16, 32)>",
|
||||||
"docstring": "Health check operation handler.\n\nThis function implements the OpenAPI operation identified by\n``operationId: get_health``.\n\nIt contains no routing metadata or framework-specific logic.\nRequest binding, HTTP method, and response semantics are defined\nexclusively by the OpenAPI specification.\n\nReturns\n-------\ndict\n A minimal liveness payload indicating service health."
|
"docstring": "Health check operation handler.\n\nThis function implements the OpenAPI operation identified by\n``operationId: get_health``.\n\nIt contains no routing metadata or framework-specific logic.\nRequest binding, HTTP method, and response semantics are defined\nexclusively by the OpenAPI specification.\n\nReturns\n-------\ndict\n A minimal liveness payload indicating service health."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,25 @@
|
|||||||
"name": "OpenAPIFirstApp",
|
"name": "OpenAPIFirstApp",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.health_app.main.OpenAPIFirstApp",
|
"path": "openapi_first.templates.health_app.main.OpenAPIFirstApp",
|
||||||
"signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstApp', 'openapi_first.app.OpenAPIFirstApp')>",
|
||||||
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"openapi": {
|
"openapi": {
|
||||||
"name": "openapi",
|
"name": "openapi",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.health_app.main.OpenAPIFirstApp.openapi",
|
"path": "openapi_first.templates.health_app.main.OpenAPIFirstApp.openapi",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('openapi', 'openapi_first.app.OpenAPIFirstApp.openapi')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"routes": {
|
||||||
|
"name": "routes",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.health_app.main.routes",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('routes', 'routes')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"name": "get_health",
|
"name": "get_health",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.health_app.routes.get_health",
|
"path": "openapi_first.templates.health_app.routes.get_health",
|
||||||
"signature": "get_health() -> dict",
|
"signature": "<bound method Function.signature of Function('get_health', 16, 32)>",
|
||||||
"docstring": "Health check operation handler.\n\nThis function implements the OpenAPI operation identified by\n``operationId: get_health``.\n\nIt contains no routing metadata or framework-specific logic.\nRequest binding, HTTP method, and response semantics are defined\nexclusively by the OpenAPI specification.\n\nReturns\n-------\ndict\n A minimal liveness payload indicating service health."
|
"docstring": "Health check operation handler.\n\nThis function implements the OpenAPI operation identified by\n``operationId: get_health``.\n\nIt contains no routing metadata or framework-specific logic.\nRequest binding, HTTP method, and response semantics are defined\nexclusively by the OpenAPI specification.\n\nReturns\n-------\ndict\n A minimal liveness payload indicating service health."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,39 +4,60 @@
|
|||||||
"path": "openapi_first.templates.model_app.data",
|
"path": "openapi_first.templates.model_app.data",
|
||||||
"docstring": "In-memory data store using Pydantic models.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\nThis module provides a minimal, process-local data store for the\nmodel-based CRUD example application. It stores and returns domain\nobjects defined using Pydantic models and is intended solely for\ndemonstration and scaffolding purposes.\n\nThe implementation intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nIt is not part of the ``openapi_first`` library API surface.",
|
"docstring": "In-memory data store using Pydantic models.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\nThis module provides a minimal, process-local data store for the\nmodel-based CRUD example application. It stores and returns domain\nobjects defined using Pydantic models and is intended solely for\ndemonstration and scaffolding purposes.\n\nThe implementation intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nIt is not part of the ``openapi_first`` library API surface.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"Dict": {
|
||||||
|
"name": "Dict",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.data.Dict",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Dict', 'typing.Dict')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"name": "Item",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.data.Item",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Item', 'models.Item')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"ItemCreate": {
|
||||||
|
"name": "ItemCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.data.ItemCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('ItemCreate', 'models.ItemCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_items": {
|
"list_items": {
|
||||||
"name": "list_items",
|
"name": "list_items",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.list_items",
|
"path": "openapi_first.templates.model_app.data.list_items",
|
||||||
"signature": "list_items() -> list[Item]",
|
"signature": "<bound method Function.signature of Function('list_items', 34, 43)>",
|
||||||
"docstring": "Return all items in the data store.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects."
|
"docstring": "Return all items in the data store.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects."
|
||||||
},
|
},
|
||||||
"get_item": {
|
"get_item": {
|
||||||
"name": "get_item",
|
"name": "get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.get_item",
|
"path": "openapi_first.templates.model_app.data.get_item",
|
||||||
"signature": "get_item(item_id: int) -> Item",
|
"signature": "<bound method Function.signature of Function('get_item', 46, 65)>",
|
||||||
"docstring": "Retrieve a single item by ID.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
"docstring": "Retrieve a single item by ID.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
||||||
},
|
},
|
||||||
"create_item": {
|
"create_item": {
|
||||||
"name": "create_item",
|
"name": "create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.create_item",
|
"path": "openapi_first.templates.model_app.data.create_item",
|
||||||
"signature": "create_item(payload: ItemCreate) -> Item",
|
"signature": "<bound method Function.signature of Function('create_item', 68, 89)>",
|
||||||
"docstring": "Create a new item in the data store.\n\nA new identifier is assigned automatically. No additional validation\nis performed beyond Pydantic model validation.\n\nParameters\n----------\npayload : ItemCreate\n Data required to create a new item.\n\nReturns\n-------\nItem\n The newly created item."
|
"docstring": "Create a new item in the data store.\n\nA new identifier is assigned automatically. No additional validation\nis performed beyond Pydantic model validation.\n\nParameters\n----------\npayload : ItemCreate\n Data required to create a new item.\n\nReturns\n-------\nItem\n The newly created item."
|
||||||
},
|
},
|
||||||
"update_item": {
|
"update_item": {
|
||||||
"name": "update_item",
|
"name": "update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.update_item",
|
"path": "openapi_first.templates.model_app.data.update_item",
|
||||||
"signature": "update_item(item_id: int, payload: ItemCreate) -> Item",
|
"signature": "<bound method Function.signature of Function('update_item', 92, 120)>",
|
||||||
"docstring": "Replace an existing item in the data store.\n\nThis function performs a full replacement of the stored item.\nPartial updates are not supported.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
"docstring": "Replace an existing item in the data store.\n\nThis function performs a full replacement of the stored item.\nPartial updates are not supported.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
||||||
},
|
},
|
||||||
"delete_item": {
|
"delete_item": {
|
||||||
"name": "delete_item",
|
"name": "delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.delete_item",
|
"path": "openapi_first.templates.model_app.data.delete_item",
|
||||||
"signature": "delete_item(item_id: int) -> None",
|
"signature": "<bound method Function.signature of Function('delete_item', 123, 137)>",
|
||||||
"docstring": "Remove an item from the data store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
"docstring": "Remove an item from the data store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"module": "openapi_first.templates.model_app",
|
"module": "openapi_first.templates.model_app",
|
||||||
"content": {
|
"content": {
|
||||||
"path": "openapi_first.templates.model_app",
|
"path": "openapi_first.templates.model_app",
|
||||||
"docstring": "OpenAPI-first model-based CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service that uses explicit Pydantic domain models for request and\nresponse schemas.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- Pydantic domain models (``models``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit request and response modeling with Pydantic\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new model-based CRUD example service using the bundled template:\n\n openapi-first scaffold model_app\n\nCreate the service in a custom directory:\n\n openapi-first scaffold model_app my-model-service\n\nList all available application templates:\n\n openapi-first scaffold --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for model-based CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Request and response payloads conform to Pydantic models\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.",
|
"docstring": "OpenAPI-first model-based CRUD application template.\n\nThis package contains a complete, minimal example of an OpenAPI-first\nCRUD service that uses explicit Pydantic domain models for request and\nresponse schemas.\n\nThe application is assembled exclusively from:\n- an OpenAPI specification (``openapi.yaml``)\n- a handler namespace implementing CRUD operations (``routes``)\n- Pydantic domain models (``models``)\n- an in-memory mock data store (``data``)\n\nAll HTTP routes, methods, schemas, and operation bindings are defined\nin the OpenAPI specification and enforced at application startup.\nNo decorator-driven routing or implicit framework behavior is used.\n\nThis template demonstrates:\n- operationId-driven server-side route binding\n- explicit request and response modeling with Pydantic\n- explicit HTTP status code control in handlers\n- operationId-driven client usage against the same OpenAPI contract\n- end-to-end validation using in-memory data and tests\n\n----------------------------------------------------------------------\nScaffolding via CLI\n----------------------------------------------------------------------\n\nCreate a new model-based CRUD example service using the bundled template:\n\n openapi-first model_app\n\nCreate the service in a custom directory:\n\n openapi-first model_app my-model-service\n\nList all available application templates:\n\n openapi-first --list\n\nThe CLI copies template files verbatim into the target directory.\nNo code is generated or modified beyond the copied scaffold.\n\n----------------------------------------------------------------------\nClient Usage Example\n----------------------------------------------------------------------\n\nThe same OpenAPI specification used by the server can be used to\nconstruct a strict, operationId-driven HTTP client.\n\nExample client calls for model-based CRUD operations:\n\n from openapi_first.loader import load_openapi\n from openapi_first.client import OpenAPIClient\n\n spec = load_openapi(\"openapi.yaml\")\n client = OpenAPIClient(spec)\n\n # List items\n response = client.list_items()\n\n # Get item by ID\n response = client.get_item(\n path_params={\"item_id\": 1}\n )\n\n # Create item\n response = client.create_item(\n body={\"name\": \"Orange\", \"price\": 0.8}\n )\n\n # Update item\n response = client.update_item(\n path_params={\"item_id\": 1},\n body={\"name\": \"Green Apple\", \"price\": 0.6},\n )\n\n # Delete item\n response = client.delete_item(\n path_params={\"item_id\": 1}\n )\n\nClient guarantees:\n- One callable per OpenAPI ``operationId``\n- No hardcoded URLs or HTTP methods in user code\n- Request and response payloads conform to Pydantic models\n- Invalid or incomplete OpenAPI specs fail at client construction time\n\n----------------------------------------------------------------------\nNon-Goals\n----------------------------------------------------------------------\n\nThis template is intentionally minimal and is NOT:\n- production-ready\n- persistent or concurrency-safe\n- a reference architecture for data storage\n\nIt exists solely as a copyable example for learning, testing, and\nbootstrapping OpenAPI-first services.\n\nThis package is not part of the ``openapi_first`` library API surface.",
|
||||||
"objects": {
|
"objects": {
|
||||||
"data": {
|
"data": {
|
||||||
"name": "data",
|
"name": "data",
|
||||||
@@ -11,39 +11,60 @@
|
|||||||
"signature": null,
|
"signature": null,
|
||||||
"docstring": "In-memory data store using Pydantic models.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\nThis module provides a minimal, process-local data store for the\nmodel-based CRUD example application. It stores and returns domain\nobjects defined using Pydantic models and is intended solely for\ndemonstration and scaffolding purposes.\n\nThe implementation intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nIt is not part of the ``openapi_first`` library API surface.",
|
"docstring": "In-memory data store using Pydantic models.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\nThis module provides a minimal, process-local data store for the\nmodel-based CRUD example application. It stores and returns domain\nobjects defined using Pydantic models and is intended solely for\ndemonstration and scaffolding purposes.\n\nThe implementation intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nIt is not part of the ``openapi_first`` library API surface.",
|
||||||
"members": {
|
"members": {
|
||||||
|
"Dict": {
|
||||||
|
"name": "Dict",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.data.Dict",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Dict', 'typing.Dict')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"name": "Item",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.data.Item",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Item', 'models.Item')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"ItemCreate": {
|
||||||
|
"name": "ItemCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.data.ItemCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('ItemCreate', 'models.ItemCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_items": {
|
"list_items": {
|
||||||
"name": "list_items",
|
"name": "list_items",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.list_items",
|
"path": "openapi_first.templates.model_app.data.list_items",
|
||||||
"signature": "list_items() -> list[Item]",
|
"signature": "<bound method Function.signature of Function('list_items', 34, 43)>",
|
||||||
"docstring": "Return all items in the data store.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects."
|
"docstring": "Return all items in the data store.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects."
|
||||||
},
|
},
|
||||||
"get_item": {
|
"get_item": {
|
||||||
"name": "get_item",
|
"name": "get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.get_item",
|
"path": "openapi_first.templates.model_app.data.get_item",
|
||||||
"signature": "get_item(item_id: int) -> Item",
|
"signature": "<bound method Function.signature of Function('get_item', 46, 65)>",
|
||||||
"docstring": "Retrieve a single item by ID.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
"docstring": "Retrieve a single item by ID.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
||||||
},
|
},
|
||||||
"create_item": {
|
"create_item": {
|
||||||
"name": "create_item",
|
"name": "create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.create_item",
|
"path": "openapi_first.templates.model_app.data.create_item",
|
||||||
"signature": "create_item(payload: ItemCreate) -> Item",
|
"signature": "<bound method Function.signature of Function('create_item', 68, 89)>",
|
||||||
"docstring": "Create a new item in the data store.\n\nA new identifier is assigned automatically. No additional validation\nis performed beyond Pydantic model validation.\n\nParameters\n----------\npayload : ItemCreate\n Data required to create a new item.\n\nReturns\n-------\nItem\n The newly created item."
|
"docstring": "Create a new item in the data store.\n\nA new identifier is assigned automatically. No additional validation\nis performed beyond Pydantic model validation.\n\nParameters\n----------\npayload : ItemCreate\n Data required to create a new item.\n\nReturns\n-------\nItem\n The newly created item."
|
||||||
},
|
},
|
||||||
"update_item": {
|
"update_item": {
|
||||||
"name": "update_item",
|
"name": "update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.update_item",
|
"path": "openapi_first.templates.model_app.data.update_item",
|
||||||
"signature": "update_item(item_id: int, payload: ItemCreate) -> Item",
|
"signature": "<bound method Function.signature of Function('update_item', 92, 120)>",
|
||||||
"docstring": "Replace an existing item in the data store.\n\nThis function performs a full replacement of the stored item.\nPartial updates are not supported.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
"docstring": "Replace an existing item in the data store.\n\nThis function performs a full replacement of the stored item.\nPartial updates are not supported.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
||||||
},
|
},
|
||||||
"delete_item": {
|
"delete_item": {
|
||||||
"name": "delete_item",
|
"name": "delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.data.delete_item",
|
"path": "openapi_first.templates.model_app.data.delete_item",
|
||||||
"signature": "delete_item(item_id: int) -> None",
|
"signature": "<bound method Function.signature of Function('delete_item', 123, 137)>",
|
||||||
"docstring": "Remove an item from the data store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
"docstring": "Remove an item from the data store.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\n\nRaises\n------\nKeyError\n If the item does not exist."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,18 +80,25 @@
|
|||||||
"name": "OpenAPIFirstApp",
|
"name": "OpenAPIFirstApp",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.main.OpenAPIFirstApp",
|
"path": "openapi_first.templates.model_app.main.OpenAPIFirstApp",
|
||||||
"signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstApp', 'openapi_first.app.OpenAPIFirstApp')>",
|
||||||
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"openapi": {
|
"openapi": {
|
||||||
"name": "openapi",
|
"name": "openapi",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.model_app.main.OpenAPIFirstApp.openapi",
|
"path": "openapi_first.templates.model_app.main.OpenAPIFirstApp.openapi",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('openapi', 'openapi_first.app.OpenAPIFirstApp.openapi')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"routes": {
|
||||||
|
"name": "routes",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.main.routes",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('routes', 'routes')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
@@ -87,11 +115,18 @@
|
|||||||
"signature": null,
|
"signature": null,
|
||||||
"docstring": "Pydantic domain models for the CRUD example.\n\nThis module defines Pydantic models that represent the domain entities\nused by the service. These models are referenced by the OpenAPI\nspecification for request and response schemas.\n\nThe models are declarative and framework-agnostic. They contain no\npersistence logic, validation beyond type constraints, or business\nbehavior.\n\nThis module is not part of the ``openapi_first`` library API surface.\nIt exists solely to support the example application template.",
|
"docstring": "Pydantic domain models for the CRUD example.\n\nThis module defines Pydantic models that represent the domain entities\nused by the service. These models are referenced by the OpenAPI\nspecification for request and response schemas.\n\nThe models are declarative and framework-agnostic. They contain no\npersistence logic, validation beyond type constraints, or business\nbehavior.\n\nThis module is not part of the ``openapi_first`` library API surface.\nIt exists solely to support the example application template.",
|
||||||
"members": {
|
"members": {
|
||||||
|
"BaseModel": {
|
||||||
|
"name": "BaseModel",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.models.BaseModel",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('BaseModel', 'pydantic.BaseModel')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"ItemBase": {
|
"ItemBase": {
|
||||||
"name": "ItemBase",
|
"name": "ItemBase",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.models.ItemBase",
|
"path": "openapi_first.templates.model_app.models.ItemBase",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('ItemBase', 19, 27)>",
|
||||||
"docstring": "Base domain model for an item.\n\nDefines fields common to all item representations.",
|
"docstring": "Base domain model for an item.\n\nDefines fields common to all item representations.",
|
||||||
"members": {
|
"members": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -114,14 +149,14 @@
|
|||||||
"name": "ItemCreate",
|
"name": "ItemCreate",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.models.ItemCreate",
|
"path": "openapi_first.templates.model_app.models.ItemCreate",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('ItemCreate', 30, 39)>",
|
||||||
"docstring": "Domain model for item creation requests.\n\nThis model is used for request bodies when creating new items.\nIt intentionally excludes the ``id`` field, which is assigned\nby the service."
|
"docstring": "Domain model for item creation requests.\n\nThis model is used for request bodies when creating new items.\nIt intentionally excludes the ``id`` field, which is assigned\nby the service."
|
||||||
},
|
},
|
||||||
"Item": {
|
"Item": {
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.models.Item",
|
"path": "openapi_first.templates.model_app.models.Item",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Item', 42, 50)>",
|
||||||
"docstring": "Domain model for a persisted item.\n\nThis model represents the full item state returned in responses,\nincluding the server-assigned identifier.",
|
"docstring": "Domain model for a persisted item.\n\nThis model represents the full item state returned in responses,\nincluding the server-assigned identifier.",
|
||||||
"members": {
|
"members": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -142,40 +177,61 @@
|
|||||||
"signature": null,
|
"signature": null,
|
||||||
"docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThis module defines OpenAPI-bound operation handlers for a model-based\nCRUD service. Functions in this module are bound to HTTP routes\nexclusively via OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.",
|
"docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThis module defines OpenAPI-bound operation handlers for a model-based\nCRUD service. Functions in this module are bound to HTTP routes\nexclusively via OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.",
|
||||||
"members": {
|
"members": {
|
||||||
|
"Response": {
|
||||||
|
"name": "Response",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.routes.Response",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Response', 'fastapi.Response')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HTTPException": {
|
||||||
|
"name": "HTTPException",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.routes.HTTPException",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HTTPException', 'fastapi.HTTPException')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"ItemCreate": {
|
||||||
|
"name": "ItemCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.routes.ItemCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('ItemCreate', 'models.ItemCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_items": {
|
"list_items": {
|
||||||
"name": "list_items",
|
"name": "list_items",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.list_items",
|
"path": "openapi_first.templates.model_app.routes.list_items",
|
||||||
"signature": "list_items() -> list[Item]",
|
"signature": "<bound method Function.signature of Function('list_items', 29, 41)>",
|
||||||
"docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects."
|
"docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects."
|
||||||
},
|
},
|
||||||
"get_item": {
|
"get_item": {
|
||||||
"name": "get_item",
|
"name": "get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.get_item",
|
"path": "openapi_first.templates.model_app.routes.get_item",
|
||||||
"signature": "get_item(item_id: int) -> Item",
|
"signature": "<bound method Function.signature of Function('get_item', 44, 69)>",
|
||||||
"docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
},
|
},
|
||||||
"create_item": {
|
"create_item": {
|
||||||
"name": "create_item",
|
"name": "create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.create_item",
|
"path": "openapi_first.templates.model_app.routes.create_item",
|
||||||
"signature": "create_item(payload: ItemCreate, response: Response) -> Item",
|
"signature": "<bound method Function.signature of Function('create_item', 72, 93)>",
|
||||||
"docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item."
|
"docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item."
|
||||||
},
|
},
|
||||||
"update_item": {
|
"update_item": {
|
||||||
"name": "update_item",
|
"name": "update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.update_item",
|
"path": "openapi_first.templates.model_app.routes.update_item",
|
||||||
"signature": "update_item(item_id: int, payload: ItemCreate) -> Item",
|
"signature": "<bound method Function.signature of Function('update_item', 96, 123)>",
|
||||||
"docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
},
|
},
|
||||||
"delete_item": {
|
"delete_item": {
|
||||||
"name": "delete_item",
|
"name": "delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.delete_item",
|
"path": "openapi_first.templates.model_app.routes.delete_item",
|
||||||
"signature": "delete_item(item_id: int, response: Response) -> None",
|
"signature": "<bound method Function.signature of Function('delete_item', 126, 156)>",
|
||||||
"docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -186,46 +242,60 @@
|
|||||||
"signature": null,
|
"signature": null,
|
||||||
"docstring": "End-to-end tests for the OpenAPI-first model CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Pydantic model-based request and response handling\n\nAll CRUD operations are exercised against an in-memory mock data store\nbacked by Pydantic domain models.\n\nThe tests assume:\n- OpenAPI-first route binding\n- Pydantic model validation\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process",
|
"docstring": "End-to-end tests for the OpenAPI-first model CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Pydantic model-based request and response handling\n\nAll CRUD operations are exercised against an in-memory mock data store\nbacked by Pydantic domain models.\n\nThe tests assume:\n- OpenAPI-first route binding\n- Pydantic model validation\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process",
|
||||||
"members": {
|
"members": {
|
||||||
|
"TestClient": {
|
||||||
|
"name": "TestClient",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.test_model_app.TestClient",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('TestClient', 'fastapi.testclient.TestClient')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"name": "app",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.test_model_app.app",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('app', 'main.app')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"load_openapi": {
|
"load_openapi": {
|
||||||
"name": "load_openapi",
|
"name": "load_openapi",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.load_openapi",
|
"path": "openapi_first.templates.model_app.test_model_app.load_openapi",
|
||||||
"signature": "load_openapi(path: str | Path)",
|
"signature": "<bound method Alias.signature of Alias('load_openapi', 'openapi_first.loader.load_openapi')>",
|
||||||
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
||||||
},
|
},
|
||||||
"OpenAPIClient": {
|
"OpenAPIClient": {
|
||||||
"name": "OpenAPIClient",
|
"name": "OpenAPIClient",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient",
|
||||||
"signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIClient', 'openapi_first.client.OpenAPIClient')>",
|
||||||
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"spec": {
|
"spec": {
|
||||||
"name": "spec",
|
"name": "spec",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.spec",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.spec",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('spec', 'openapi_first.client.OpenAPIClient.spec')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"base_url": {
|
"base_url": {
|
||||||
"name": "base_url",
|
"name": "base_url",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.base_url",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.base_url",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('base_url', 'openapi_first.client.OpenAPIClient.base_url')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.client",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.client",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('client', 'openapi_first.client.OpenAPIClient.client')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"name": "operations",
|
"name": "operations",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.operations",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.operations",
|
||||||
"signature": "operations()",
|
"signature": "<bound method Alias.signature of Alias('operations', 'openapi_first.client.OpenAPIClient.operations')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,35 +318,35 @@
|
|||||||
"name": "test_list_items_initial",
|
"name": "test_list_items_initial",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_list_items_initial",
|
"path": "openapi_first.templates.model_app.test_model_app.test_list_items_initial",
|
||||||
"signature": "test_list_items_initial()",
|
"signature": "<bound method Function.signature of Function('test_list_items_initial', 37, 48)>",
|
||||||
"docstring": "Initial items should be present."
|
"docstring": "Initial items should be present."
|
||||||
},
|
},
|
||||||
"test_get_item": {
|
"test_get_item": {
|
||||||
"name": "test_get_item",
|
"name": "test_get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_get_item",
|
"path": "openapi_first.templates.model_app.test_model_app.test_get_item",
|
||||||
"signature": "test_get_item()",
|
"signature": "<bound method Function.signature of Function('test_get_item', 51, 61)>",
|
||||||
"docstring": "Existing item should be retrievable by ID."
|
"docstring": "Existing item should be retrievable by ID."
|
||||||
},
|
},
|
||||||
"test_create_item": {
|
"test_create_item": {
|
||||||
"name": "test_create_item",
|
"name": "test_create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_create_item",
|
"path": "openapi_first.templates.model_app.test_model_app.test_create_item",
|
||||||
"signature": "test_create_item()",
|
"signature": "<bound method Function.signature of Function('test_create_item', 64, 84)>",
|
||||||
"docstring": "Creating a new item should return the created entity."
|
"docstring": "Creating a new item should return the created entity."
|
||||||
},
|
},
|
||||||
"test_update_item": {
|
"test_update_item": {
|
||||||
"name": "test_update_item",
|
"name": "test_update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_update_item",
|
"path": "openapi_first.templates.model_app.test_model_app.test_update_item",
|
||||||
"signature": "test_update_item()",
|
"signature": "<bound method Function.signature of Function('test_update_item', 87, 111)>",
|
||||||
"docstring": "Updating an item should replace its values."
|
"docstring": "Updating an item should replace its values."
|
||||||
},
|
},
|
||||||
"test_delete_item": {
|
"test_delete_item": {
|
||||||
"name": "test_delete_item",
|
"name": "test_delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_delete_item",
|
"path": "openapi_first.templates.model_app.test_model_app.test_delete_item",
|
||||||
"signature": "test_delete_item()",
|
"signature": "<bound method Function.signature of Function('test_delete_item', 114, 124)>",
|
||||||
"docstring": "Deleting an item should remove it from the store."
|
"docstring": "Deleting an item should remove it from the store."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,25 @@
|
|||||||
"name": "OpenAPIFirstApp",
|
"name": "OpenAPIFirstApp",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.main.OpenAPIFirstApp",
|
"path": "openapi_first.templates.model_app.main.OpenAPIFirstApp",
|
||||||
"signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstApp', 'openapi_first.app.OpenAPIFirstApp')>",
|
||||||
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"openapi": {
|
"openapi": {
|
||||||
"name": "openapi",
|
"name": "openapi",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.model_app.main.OpenAPIFirstApp.openapi",
|
"path": "openapi_first.templates.model_app.main.OpenAPIFirstApp.openapi",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('openapi', 'openapi_first.app.OpenAPIFirstApp.openapi')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"routes": {
|
||||||
|
"name": "routes",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.main.routes",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('routes', 'routes')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"name": "app",
|
"name": "app",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
|
|||||||
@@ -4,11 +4,18 @@
|
|||||||
"path": "openapi_first.templates.model_app.models",
|
"path": "openapi_first.templates.model_app.models",
|
||||||
"docstring": "Pydantic domain models for the CRUD example.\n\nThis module defines Pydantic models that represent the domain entities\nused by the service. These models are referenced by the OpenAPI\nspecification for request and response schemas.\n\nThe models are declarative and framework-agnostic. They contain no\npersistence logic, validation beyond type constraints, or business\nbehavior.\n\nThis module is not part of the ``openapi_first`` library API surface.\nIt exists solely to support the example application template.",
|
"docstring": "Pydantic domain models for the CRUD example.\n\nThis module defines Pydantic models that represent the domain entities\nused by the service. These models are referenced by the OpenAPI\nspecification for request and response schemas.\n\nThe models are declarative and framework-agnostic. They contain no\npersistence logic, validation beyond type constraints, or business\nbehavior.\n\nThis module is not part of the ``openapi_first`` library API surface.\nIt exists solely to support the example application template.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"BaseModel": {
|
||||||
|
"name": "BaseModel",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.models.BaseModel",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('BaseModel', 'pydantic.BaseModel')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"ItemBase": {
|
"ItemBase": {
|
||||||
"name": "ItemBase",
|
"name": "ItemBase",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.models.ItemBase",
|
"path": "openapi_first.templates.model_app.models.ItemBase",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('ItemBase', 19, 27)>",
|
||||||
"docstring": "Base domain model for an item.\n\nDefines fields common to all item representations.",
|
"docstring": "Base domain model for an item.\n\nDefines fields common to all item representations.",
|
||||||
"members": {
|
"members": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -31,14 +38,14 @@
|
|||||||
"name": "ItemCreate",
|
"name": "ItemCreate",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.models.ItemCreate",
|
"path": "openapi_first.templates.model_app.models.ItemCreate",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('ItemCreate', 30, 39)>",
|
||||||
"docstring": "Domain model for item creation requests.\n\nThis model is used for request bodies when creating new items.\nIt intentionally excludes the ``id`` field, which is assigned\nby the service."
|
"docstring": "Domain model for item creation requests.\n\nThis model is used for request bodies when creating new items.\nIt intentionally excludes the ``id`` field, which is assigned\nby the service."
|
||||||
},
|
},
|
||||||
"Item": {
|
"Item": {
|
||||||
"name": "Item",
|
"name": "Item",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.models.Item",
|
"path": "openapi_first.templates.model_app.models.Item",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Item', 42, 50)>",
|
||||||
"docstring": "Domain model for a persisted item.\n\nThis model represents the full item state returned in responses,\nincluding the server-assigned identifier.",
|
"docstring": "Domain model for a persisted item.\n\nThis model represents the full item state returned in responses,\nincluding the server-assigned identifier.",
|
||||||
"members": {
|
"members": {
|
||||||
"id": {
|
"id": {
|
||||||
|
|||||||
@@ -4,40 +4,61 @@
|
|||||||
"path": "openapi_first.templates.model_app.routes",
|
"path": "openapi_first.templates.model_app.routes",
|
||||||
"docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThis module defines OpenAPI-bound operation handlers for a model-based\nCRUD service. Functions in this module are bound to HTTP routes\nexclusively via OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.",
|
"docstring": "CRUD route handlers bound via OpenAPI operationId.\n\nThis module defines OpenAPI-bound operation handlers for a model-based\nCRUD service. Functions in this module are bound to HTTP routes\nexclusively via OpenAPI ``operationId`` values.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"Response": {
|
||||||
|
"name": "Response",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.routes.Response",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Response', 'fastapi.Response')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HTTPException": {
|
||||||
|
"name": "HTTPException",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.routes.HTTPException",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HTTPException', 'fastapi.HTTPException')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"ItemCreate": {
|
||||||
|
"name": "ItemCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.routes.ItemCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('ItemCreate', 'models.ItemCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_items": {
|
"list_items": {
|
||||||
"name": "list_items",
|
"name": "list_items",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.list_items",
|
"path": "openapi_first.templates.model_app.routes.list_items",
|
||||||
"signature": "list_items() -> list[Item]",
|
"signature": "<bound method Function.signature of Function('list_items', 29, 41)>",
|
||||||
"docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects."
|
"docstring": "List all items.\n\nImplements the OpenAPI operation identified by\n``operationId: list_items``.\n\nReturns\n-------\nlist[Item]\n A list of item domain objects."
|
||||||
},
|
},
|
||||||
"get_item": {
|
"get_item": {
|
||||||
"name": "get_item",
|
"name": "get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.get_item",
|
"path": "openapi_first.templates.model_app.routes.get_item",
|
||||||
"signature": "get_item(item_id: int) -> Item",
|
"signature": "<bound method Function.signature of Function('get_item', 44, 69)>",
|
||||||
"docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Retrieve a single item by ID.\n\nImplements the OpenAPI operation identified by\n``operationId: get_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to retrieve.\n\nReturns\n-------\nItem\n The requested item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
},
|
},
|
||||||
"create_item": {
|
"create_item": {
|
||||||
"name": "create_item",
|
"name": "create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.create_item",
|
"path": "openapi_first.templates.model_app.routes.create_item",
|
||||||
"signature": "create_item(payload: ItemCreate, response: Response) -> Item",
|
"signature": "<bound method Function.signature of Function('create_item', 72, 93)>",
|
||||||
"docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item."
|
"docstring": "Create a new item.\n\nImplements the OpenAPI operation identified by\n``operationId: create_item``.\n\nParameters\n----------\npayload : ItemCreate\n Request body describing the item to create.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nItem\n The newly created item."
|
||||||
},
|
},
|
||||||
"update_item": {
|
"update_item": {
|
||||||
"name": "update_item",
|
"name": "update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.update_item",
|
"path": "openapi_first.templates.model_app.routes.update_item",
|
||||||
"signature": "update_item(item_id: int, payload: ItemCreate) -> Item",
|
"signature": "<bound method Function.signature of Function('update_item', 96, 123)>",
|
||||||
"docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Update an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: update_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to update.\npayload : ItemCreate\n New item data.\n\nReturns\n-------\nItem\n The updated item.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
},
|
},
|
||||||
"delete_item": {
|
"delete_item": {
|
||||||
"name": "delete_item",
|
"name": "delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.routes.delete_item",
|
"path": "openapi_first.templates.model_app.routes.delete_item",
|
||||||
"signature": "delete_item(item_id: int, response: Response) -> None",
|
"signature": "<bound method Function.signature of Function('delete_item', 126, 156)>",
|
||||||
"docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
"docstring": "Delete an existing item.\n\nImplements the OpenAPI operation identified by\n``operationId: delete_item``.\n\nParameters\n----------\nitem_id : int\n Identifier of the item to delete.\nresponse : fastapi.Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nNone\n No content.\n\nRaises\n------\nHTTPException\n 404 if the item does not exist."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,46 +4,60 @@
|
|||||||
"path": "openapi_first.templates.model_app.test_model_app",
|
"path": "openapi_first.templates.model_app.test_model_app",
|
||||||
"docstring": "End-to-end tests for the OpenAPI-first model CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Pydantic model-based request and response handling\n\nAll CRUD operations are exercised against an in-memory mock data store\nbacked by Pydantic domain models.\n\nThe tests assume:\n- OpenAPI-first route binding\n- Pydantic model validation\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process",
|
"docstring": "End-to-end tests for the OpenAPI-first model CRUD example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.\n- OpenAPI specification loading\n- OperationId-driven route binding on the server\n- OperationId-driven client invocation\n- Pydantic model-based request and response handling\n\nAll CRUD operations are exercised against an in-memory mock data store\nbacked by Pydantic domain models.\n\nThe tests assume:\n- OpenAPI-first route binding\n- Pydantic model validation\n- In-memory storage (no persistence guarantees)\n- Deterministic behavior in a single process",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"TestClient": {
|
||||||
|
"name": "TestClient",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.test_model_app.TestClient",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('TestClient', 'fastapi.testclient.TestClient')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"name": "app",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.model_app.test_model_app.app",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('app', 'main.app')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"load_openapi": {
|
"load_openapi": {
|
||||||
"name": "load_openapi",
|
"name": "load_openapi",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.load_openapi",
|
"path": "openapi_first.templates.model_app.test_model_app.load_openapi",
|
||||||
"signature": "load_openapi(path: str | Path)",
|
"signature": "<bound method Alias.signature of Alias('load_openapi', 'openapi_first.loader.load_openapi')>",
|
||||||
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
||||||
},
|
},
|
||||||
"OpenAPIClient": {
|
"OpenAPIClient": {
|
||||||
"name": "OpenAPIClient",
|
"name": "OpenAPIClient",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient",
|
||||||
"signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIClient', 'openapi_first.client.OpenAPIClient')>",
|
||||||
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"spec": {
|
"spec": {
|
||||||
"name": "spec",
|
"name": "spec",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.spec",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.spec",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('spec', 'openapi_first.client.OpenAPIClient.spec')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"base_url": {
|
"base_url": {
|
||||||
"name": "base_url",
|
"name": "base_url",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.base_url",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.base_url",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('base_url', 'openapi_first.client.OpenAPIClient.base_url')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.client",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.client",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('client', 'openapi_first.client.OpenAPIClient.client')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"name": "operations",
|
"name": "operations",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.operations",
|
"path": "openapi_first.templates.model_app.test_model_app.OpenAPIClient.operations",
|
||||||
"signature": "operations()",
|
"signature": "<bound method Alias.signature of Alias('operations', 'openapi_first.client.OpenAPIClient.operations')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,35 +80,35 @@
|
|||||||
"name": "test_list_items_initial",
|
"name": "test_list_items_initial",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_list_items_initial",
|
"path": "openapi_first.templates.model_app.test_model_app.test_list_items_initial",
|
||||||
"signature": "test_list_items_initial()",
|
"signature": "<bound method Function.signature of Function('test_list_items_initial', 37, 48)>",
|
||||||
"docstring": "Initial items should be present."
|
"docstring": "Initial items should be present."
|
||||||
},
|
},
|
||||||
"test_get_item": {
|
"test_get_item": {
|
||||||
"name": "test_get_item",
|
"name": "test_get_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_get_item",
|
"path": "openapi_first.templates.model_app.test_model_app.test_get_item",
|
||||||
"signature": "test_get_item()",
|
"signature": "<bound method Function.signature of Function('test_get_item', 51, 61)>",
|
||||||
"docstring": "Existing item should be retrievable by ID."
|
"docstring": "Existing item should be retrievable by ID."
|
||||||
},
|
},
|
||||||
"test_create_item": {
|
"test_create_item": {
|
||||||
"name": "test_create_item",
|
"name": "test_create_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_create_item",
|
"path": "openapi_first.templates.model_app.test_model_app.test_create_item",
|
||||||
"signature": "test_create_item()",
|
"signature": "<bound method Function.signature of Function('test_create_item', 64, 84)>",
|
||||||
"docstring": "Creating a new item should return the created entity."
|
"docstring": "Creating a new item should return the created entity."
|
||||||
},
|
},
|
||||||
"test_update_item": {
|
"test_update_item": {
|
||||||
"name": "test_update_item",
|
"name": "test_update_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_update_item",
|
"path": "openapi_first.templates.model_app.test_model_app.test_update_item",
|
||||||
"signature": "test_update_item()",
|
"signature": "<bound method Function.signature of Function('test_update_item', 87, 111)>",
|
||||||
"docstring": "Updating an item should replace its values."
|
"docstring": "Updating an item should replace its values."
|
||||||
},
|
},
|
||||||
"test_delete_item": {
|
"test_delete_item": {
|
||||||
"name": "test_delete_item",
|
"name": "test_delete_item",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.model_app.test_model_app.test_delete_item",
|
"path": "openapi_first.templates.model_app.test_model_app.test_delete_item",
|
||||||
"signature": "test_delete_item()",
|
"signature": "<bound method Function.signature of Function('test_delete_item', 114, 124)>",
|
||||||
"docstring": "Deleting an item should remove it from the store."
|
"docstring": "Deleting an item should remove it from the store."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,179 +4,319 @@
|
|||||||
"path": "openapi_first.templates.vet_app.data",
|
"path": "openapi_first.templates.vet_app.data",
|
||||||
"docstring": "In-memory data store for the Veterinary Clinic example.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\n\nIt provides minimal, process-local data stores for the five veterinary\nclinic entities. Each store exposes standard CRUD operations backed by\na simple dictionary.\n\nThis module intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nThis module is not part of the ``openapi_first`` library API surface.",
|
"docstring": "In-memory data store for the Veterinary Clinic example.\n\nThis module is NOT thread-safe and is intended for demos and scaffolds only.\n\nIt provides minimal, process-local data stores for the five veterinary\nclinic entities. Each store exposes standard CRUD operations backed by\na simple dictionary.\n\nThis module intentionally avoids:\n- persistence\n- concurrency guarantees\n- transactional semantics\n- validation beyond what Pydantic provides\n\nThis module is not part of the ``openapi_first`` library API surface.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"date": {
|
||||||
|
"name": "date",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.date",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('date', 'datetime.date')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"name": "datetime",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.datetime",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('datetime', 'datetime.datetime')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"timezone": {
|
||||||
|
"name": "timezone",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.timezone",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('timezone', 'datetime.timezone')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Parent": {
|
||||||
|
"name": "Parent",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.Parent",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Parent', 'models.Parent')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"ParentCreate": {
|
||||||
|
"name": "ParentCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.ParentCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('ParentCreate', 'models.ParentCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Vet": {
|
||||||
|
"name": "Vet",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.Vet",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Vet', 'models.Vet')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"VetCreate": {
|
||||||
|
"name": "VetCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.VetCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('VetCreate', 'models.VetCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Treatment": {
|
||||||
|
"name": "Treatment",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.Treatment",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Treatment', 'models.Treatment')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"TreatmentCreate": {
|
||||||
|
"name": "TreatmentCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.TreatmentCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('TreatmentCreate', 'models.TreatmentCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Procedure": {
|
||||||
|
"name": "Procedure",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.Procedure",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Procedure', 'models.Procedure')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"BasicNote": {
|
||||||
|
"name": "BasicNote",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.BasicNote",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('BasicNote', 'models.BasicNote')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HeartRateNote": {
|
||||||
|
"name": "HeartRateNote",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.HeartRateNote",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HeartRateNote', 'models.HeartRateNote')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"DentalNote": {
|
||||||
|
"name": "DentalNote",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.DentalNote",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('DentalNote', 'models.DentalNote')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"VaccineNote": {
|
||||||
|
"name": "VaccineNote",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.VaccineNote",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('VaccineNote', 'models.VaccineNote')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"PreOpNote": {
|
||||||
|
"name": "PreOpNote",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.PreOpNote",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('PreOpNote', 'models.PreOpNote')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"SurgeryNote": {
|
||||||
|
"name": "SurgeryNote",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.SurgeryNote",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('SurgeryNote', 'models.SurgeryNote')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Pet": {
|
||||||
|
"name": "Pet",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.Pet",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Pet', 'models.Pet')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"PetCreate": {
|
||||||
|
"name": "PetCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.PetCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('PetCreate', 'models.PetCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Appointment": {
|
||||||
|
"name": "Appointment",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.Appointment",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Appointment', 'models.Appointment')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"AppointmentCreate": {
|
||||||
|
"name": "AppointmentCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.data.AppointmentCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('AppointmentCreate', 'models.AppointmentCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_parents": {
|
"list_parents": {
|
||||||
"name": "list_parents",
|
"name": "list_parents",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.list_parents",
|
"path": "openapi_first.templates.vet_app.data.list_parents",
|
||||||
"signature": "list_parents() -> list[Parent]",
|
"signature": "<bound method Function.signature of Function('list_parents', 45, 46)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"get_parent": {
|
"get_parent": {
|
||||||
"name": "get_parent",
|
"name": "get_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.get_parent",
|
"path": "openapi_first.templates.vet_app.data.get_parent",
|
||||||
"signature": "get_parent(parent_id: int) -> Parent",
|
"signature": "<bound method Function.signature of Function('get_parent', 49, 50)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"create_parent": {
|
"create_parent": {
|
||||||
"name": "create_parent",
|
"name": "create_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.create_parent",
|
"path": "openapi_first.templates.vet_app.data.create_parent",
|
||||||
"signature": "create_parent(payload: ParentCreate) -> Parent",
|
"signature": "<bound method Function.signature of Function('create_parent', 53, 65)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"update_parent": {
|
"update_parent": {
|
||||||
"name": "update_parent",
|
"name": "update_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.update_parent",
|
"path": "openapi_first.templates.vet_app.data.update_parent",
|
||||||
"signature": "update_parent(parent_id: int, payload: ParentCreate) -> Parent",
|
"signature": "<bound method Function.signature of Function('update_parent', 68, 81)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"delete_parent": {
|
"delete_parent": {
|
||||||
"name": "delete_parent",
|
"name": "delete_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.delete_parent",
|
"path": "openapi_first.templates.vet_app.data.delete_parent",
|
||||||
"signature": "delete_parent(parent_id: int) -> None",
|
"signature": "<bound method Function.signature of Function('delete_parent', 84, 85)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"list_vets": {
|
"list_vets": {
|
||||||
"name": "list_vets",
|
"name": "list_vets",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.list_vets",
|
"path": "openapi_first.templates.vet_app.data.list_vets",
|
||||||
"signature": "list_vets() -> list[Vet]",
|
"signature": "<bound method Function.signature of Function('list_vets', 96, 97)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"get_vet": {
|
"get_vet": {
|
||||||
"name": "get_vet",
|
"name": "get_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.get_vet",
|
"path": "openapi_first.templates.vet_app.data.get_vet",
|
||||||
"signature": "get_vet(vet_id: int) -> Vet",
|
"signature": "<bound method Function.signature of Function('get_vet', 100, 101)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"create_vet": {
|
"create_vet": {
|
||||||
"name": "create_vet",
|
"name": "create_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.create_vet",
|
"path": "openapi_first.templates.vet_app.data.create_vet",
|
||||||
"signature": "create_vet(payload: VetCreate) -> Vet",
|
"signature": "<bound method Function.signature of Function('create_vet', 104, 117)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"update_vet": {
|
"update_vet": {
|
||||||
"name": "update_vet",
|
"name": "update_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.update_vet",
|
"path": "openapi_first.templates.vet_app.data.update_vet",
|
||||||
"signature": "update_vet(vet_id: int, payload: VetCreate) -> Vet",
|
"signature": "<bound method Function.signature of Function('update_vet', 120, 134)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"delete_vet": {
|
"delete_vet": {
|
||||||
"name": "delete_vet",
|
"name": "delete_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.delete_vet",
|
"path": "openapi_first.templates.vet_app.data.delete_vet",
|
||||||
"signature": "delete_vet(vet_id: int) -> None",
|
"signature": "<bound method Function.signature of Function('delete_vet', 137, 138)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"list_treatments": {
|
"list_treatments": {
|
||||||
"name": "list_treatments",
|
"name": "list_treatments",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.list_treatments",
|
"path": "openapi_first.templates.vet_app.data.list_treatments",
|
||||||
"signature": "list_treatments() -> list[Treatment]",
|
"signature": "<bound method Function.signature of Function('list_treatments', 149, 150)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"get_treatment": {
|
"get_treatment": {
|
||||||
"name": "get_treatment",
|
"name": "get_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.get_treatment",
|
"path": "openapi_first.templates.vet_app.data.get_treatment",
|
||||||
"signature": "get_treatment(treatment_id: int) -> Treatment",
|
"signature": "<bound method Function.signature of Function('get_treatment', 153, 154)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"create_treatment": {
|
"create_treatment": {
|
||||||
"name": "create_treatment",
|
"name": "create_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.create_treatment",
|
"path": "openapi_first.templates.vet_app.data.create_treatment",
|
||||||
"signature": "create_treatment(payload: TreatmentCreate) -> Treatment",
|
"signature": "<bound method Function.signature of Function('create_treatment', 157, 168)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"update_treatment": {
|
"update_treatment": {
|
||||||
"name": "update_treatment",
|
"name": "update_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.update_treatment",
|
"path": "openapi_first.templates.vet_app.data.update_treatment",
|
||||||
"signature": "update_treatment(treatment_id: int, payload: TreatmentCreate) -> Treatment",
|
"signature": "<bound method Function.signature of Function('update_treatment', 171, 183)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"delete_treatment": {
|
"delete_treatment": {
|
||||||
"name": "delete_treatment",
|
"name": "delete_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.delete_treatment",
|
"path": "openapi_first.templates.vet_app.data.delete_treatment",
|
||||||
"signature": "delete_treatment(treatment_id: int) -> None",
|
"signature": "<bound method Function.signature of Function('delete_treatment', 186, 187)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"list_pets": {
|
"list_pets": {
|
||||||
"name": "list_pets",
|
"name": "list_pets",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.list_pets",
|
"path": "openapi_first.templates.vet_app.data.list_pets",
|
||||||
"signature": "list_pets() -> list[Pet]",
|
"signature": "<bound method Function.signature of Function('list_pets', 198, 199)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"get_pet": {
|
"get_pet": {
|
||||||
"name": "get_pet",
|
"name": "get_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.get_pet",
|
"path": "openapi_first.templates.vet_app.data.get_pet",
|
||||||
"signature": "get_pet(pet_id: int) -> Pet",
|
"signature": "<bound method Function.signature of Function('get_pet', 202, 203)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"create_pet": {
|
"create_pet": {
|
||||||
"name": "create_pet",
|
"name": "create_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.create_pet",
|
"path": "openapi_first.templates.vet_app.data.create_pet",
|
||||||
"signature": "create_pet(payload: PetCreate) -> Pet",
|
"signature": "<bound method Function.signature of Function('create_pet', 206, 223)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"update_pet": {
|
"update_pet": {
|
||||||
"name": "update_pet",
|
"name": "update_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.update_pet",
|
"path": "openapi_first.templates.vet_app.data.update_pet",
|
||||||
"signature": "update_pet(pet_id: int, payload: PetCreate) -> Pet",
|
"signature": "<bound method Function.signature of Function('update_pet', 226, 244)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"delete_pet": {
|
"delete_pet": {
|
||||||
"name": "delete_pet",
|
"name": "delete_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.delete_pet",
|
"path": "openapi_first.templates.vet_app.data.delete_pet",
|
||||||
"signature": "delete_pet(pet_id: int) -> None",
|
"signature": "<bound method Function.signature of Function('delete_pet', 247, 248)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"list_appointments": {
|
"list_appointments": {
|
||||||
"name": "list_appointments",
|
"name": "list_appointments",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.list_appointments",
|
"path": "openapi_first.templates.vet_app.data.list_appointments",
|
||||||
"signature": "list_appointments() -> list[Appointment]",
|
"signature": "<bound method Function.signature of Function('list_appointments', 259, 260)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"get_appointment": {
|
"get_appointment": {
|
||||||
"name": "get_appointment",
|
"name": "get_appointment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.get_appointment",
|
"path": "openapi_first.templates.vet_app.data.get_appointment",
|
||||||
"signature": "get_appointment(appointment_id: int) -> Appointment",
|
"signature": "<bound method Function.signature of Function('get_appointment', 263, 264)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"create_appointment": {
|
"create_appointment": {
|
||||||
"name": "create_appointment",
|
"name": "create_appointment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.create_appointment",
|
"path": "openapi_first.templates.vet_app.data.create_appointment",
|
||||||
"signature": "create_appointment(payload: AppointmentCreate) -> Appointment",
|
"signature": "<bound method Function.signature of Function('create_appointment', 267, 282)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"update_appointment": {
|
"update_appointment": {
|
||||||
"name": "update_appointment",
|
"name": "update_appointment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.update_appointment",
|
"path": "openapi_first.templates.vet_app.data.update_appointment",
|
||||||
"signature": "update_appointment(appointment_id: int, payload: AppointmentCreate) -> Appointment",
|
"signature": "<bound method Function.signature of Function('update_appointment', 285, 301)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"delete_appointment": {
|
"delete_appointment": {
|
||||||
"name": "delete_appointment",
|
"name": "delete_appointment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.data.delete_appointment",
|
"path": "openapi_first.templates.vet_app.data.delete_appointment",
|
||||||
"signature": "delete_appointment(appointment_id: int) -> None",
|
"signature": "<bound method Function.signature of Function('delete_appointment', 304, 305)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,27 +4,48 @@
|
|||||||
"path": "openapi_first.templates.vet_app.main",
|
"path": "openapi_first.templates.vet_app.main",
|
||||||
"docstring": "Application entry point for an OpenAPI-first Veterinary Clinic service.\n\nThis module constructs a FastAPI application exclusively from an\nOpenAPI specification and a handler namespace, without using\ndecorator-driven routing.\n\nAll HTTP routes, methods, request/response schemas, and operation\nbindings are defined in the OpenAPI document referenced by\n``openapi_path``. Python callables defined in the ``routes`` module are\nbound to OpenAPI operations strictly via ``operationId``.\n\nThis module contains no routing logic, persistence concerns, or\nframework configuration beyond application assembly.\n\nDesign guarantees:\n- OpenAPI is the single source of truth\n- No undocumented routes can exist\n- Every OpenAPI operationId must resolve to exactly one handler\n- All contract violations fail at application startup\n\nThis file is intended to be used as the ASGI entry point.\n\nExample:\n uvicorn main:app",
|
"docstring": "Application entry point for an OpenAPI-first Veterinary Clinic service.\n\nThis module constructs a FastAPI application exclusively from an\nOpenAPI specification and a handler namespace, without using\ndecorator-driven routing.\n\nAll HTTP routes, methods, request/response schemas, and operation\nbindings are defined in the OpenAPI document referenced by\n``openapi_path``. Python callables defined in the ``routes`` module are\nbound to OpenAPI operations strictly via ``operationId``.\n\nThis module contains no routing logic, persistence concerns, or\nframework configuration beyond application assembly.\n\nDesign guarantees:\n- OpenAPI is the single source of truth\n- No undocumented routes can exist\n- Every OpenAPI operationId must resolve to exactly one handler\n- All contract violations fail at application startup\n\nThis file is intended to be used as the ASGI entry point.\n\nExample:\n uvicorn main:app",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"asynccontextmanager": {
|
||||||
|
"name": "asynccontextmanager",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.main.asynccontextmanager",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('asynccontextmanager', 'contextlib.asynccontextmanager')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"CORSMiddleware": {
|
||||||
|
"name": "CORSMiddleware",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.main.CORSMiddleware",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('CORSMiddleware', 'starlette.middleware.cors.CORSMiddleware')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"OpenAPIFirstApp": {
|
"OpenAPIFirstApp": {
|
||||||
"name": "OpenAPIFirstApp",
|
"name": "OpenAPIFirstApp",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp",
|
"path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp",
|
||||||
"signature": "OpenAPIFirstApp(*, openapi_path: str, routes_module: Any, **fastapi_kwargs: Any)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIFirstApp', 'openapi_first.app.OpenAPIFirstApp')>",
|
||||||
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nArgs:\n openapi_path (str):\n Filesystem path to the OpenAPI 3.x specification file. This\n specification is treated as the authoritative API contract.\n routes_module (ModuleType):\n Python module containing handler functions whose names correspond\n exactly to OpenAPI ``operationId`` values.\n **fastapi_kwargs (Any):\n Additional keyword arguments passed directly to\n ``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan\n handlers).\n\nRaises:\n OpenAPIFirstError:\n If the OpenAPI specification is invalid, or if any declared\n ``operationId`` does not have a corresponding handler function.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first.app import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
"docstring": "FastAPI application enforcing OpenAPI-first design.\n\nNotes:\n **Responsibilities:**\n\n - `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route\n registration with OpenAPI-driven binding.\n - All routes are derived from the provided OpenAPI specification,\n and each ``operationId`` is mapped to a Python function in the\n supplied routes module.\n - Auth dependencies are auto-injected from the spec's\n ``securitySchemes`` and per-operation ``security`` fields.\n\n **Guarantees:**\n\n - No route can exist without an OpenAPI declaration.\n - No OpenAPI operation can exist without a handler.\n - Swagger UI and ``/openapi.json`` always reflect the provided spec.\n - Handler functions remain framework-agnostic and testable.\n - Auth enforcement is driven entirely by the spec — no manual\n middleware or decorators required.\n\nExample:\n ```python\n from openapi_first import OpenAPIFirstApp\n import app.routes as routes\n\n app = OpenAPIFirstApp(\n openapi_path=\"app/openapi.json\",\n routes_module=routes,\n title=\"Example Service\",\n )\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"openapi": {
|
"openapi": {
|
||||||
"name": "openapi",
|
"name": "openapi",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp.openapi",
|
"path": "openapi_first.templates.vet_app.main.OpenAPIFirstApp.openapi",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('openapi', 'openapi_first.app.OpenAPIFirstApp.openapi')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"routes": {
|
||||||
|
"name": "routes",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.main.routes",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('routes', 'routes')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"lifespan": {
|
"lifespan": {
|
||||||
"name": "lifespan",
|
"name": "lifespan",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.main.lifespan",
|
"path": "openapi_first.templates.vet_app.main.lifespan",
|
||||||
"signature": "lifespan(app)",
|
"signature": "<bound method Function.signature of Function('lifespan', 34, 39)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
|
|||||||
@@ -4,11 +4,53 @@
|
|||||||
"path": "openapi_first.templates.vet_app.models",
|
"path": "openapi_first.templates.vet_app.models",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"date": {
|
||||||
|
"name": "date",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.models.date",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('date', 'datetime.date')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"datetime": {
|
||||||
|
"name": "datetime",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.models.datetime",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('datetime', 'datetime.datetime')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Annotated": {
|
||||||
|
"name": "Annotated",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.models.Annotated",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Annotated', 'typing.Annotated')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Literal": {
|
||||||
|
"name": "Literal",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.models.Literal",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Literal', 'typing.Literal')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"BaseModel": {
|
||||||
|
"name": "BaseModel",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.models.BaseModel",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('BaseModel', 'pydantic.BaseModel')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"Field": {
|
||||||
|
"name": "Field",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.models.Field",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Field', 'pydantic.Field')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"Metadata": {
|
"Metadata": {
|
||||||
"name": "Metadata",
|
"name": "Metadata",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.Metadata",
|
"path": "openapi_first.templates.vet_app.models.Metadata",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Metadata', 6, 8)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"createdOn": {
|
"createdOn": {
|
||||||
@@ -31,7 +73,7 @@
|
|||||||
"name": "ParentBase",
|
"name": "ParentBase",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.ParentBase",
|
"path": "openapi_first.templates.vet_app.models.ParentBase",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('ParentBase', 11, 15)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -68,14 +110,14 @@
|
|||||||
"name": "ParentCreate",
|
"name": "ParentCreate",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.ParentCreate",
|
"path": "openapi_first.templates.vet_app.models.ParentCreate",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('ParentCreate', 18, 19)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"Parent": {
|
"Parent": {
|
||||||
"name": "Parent",
|
"name": "Parent",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.Parent",
|
"path": "openapi_first.templates.vet_app.models.Parent",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Parent', 22, 23)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -91,7 +133,7 @@
|
|||||||
"name": "VetBase",
|
"name": "VetBase",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.VetBase",
|
"path": "openapi_first.templates.vet_app.models.VetBase",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('VetBase', 26, 31)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -135,14 +177,14 @@
|
|||||||
"name": "VetCreate",
|
"name": "VetCreate",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.VetCreate",
|
"path": "openapi_first.templates.vet_app.models.VetCreate",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('VetCreate', 34, 35)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"Vet": {
|
"Vet": {
|
||||||
"name": "Vet",
|
"name": "Vet",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.Vet",
|
"path": "openapi_first.templates.vet_app.models.Vet",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Vet', 38, 39)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -158,7 +200,7 @@
|
|||||||
"name": "ProcedureNoteBase",
|
"name": "ProcedureNoteBase",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.ProcedureNoteBase",
|
"path": "openapi_first.templates.vet_app.models.ProcedureNoteBase",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('ProcedureNoteBase', 42, 44)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"summary": {
|
"summary": {
|
||||||
@@ -181,7 +223,7 @@
|
|||||||
"name": "BasicNote",
|
"name": "BasicNote",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.BasicNote",
|
"path": "openapi_first.templates.vet_app.models.BasicNote",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('BasicNote', 47, 48)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"noteType": {
|
"noteType": {
|
||||||
@@ -197,7 +239,7 @@
|
|||||||
"name": "HeartRateNote",
|
"name": "HeartRateNote",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.HeartRateNote",
|
"path": "openapi_first.templates.vet_app.models.HeartRateNote",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('HeartRateNote', 51, 53)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"noteType": {
|
"noteType": {
|
||||||
@@ -220,7 +262,7 @@
|
|||||||
"name": "DentalNote",
|
"name": "DentalNote",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.DentalNote",
|
"path": "openapi_first.templates.vet_app.models.DentalNote",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('DentalNote', 56, 59)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"noteType": {
|
"noteType": {
|
||||||
@@ -250,7 +292,7 @@
|
|||||||
"name": "VaccineNote",
|
"name": "VaccineNote",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.VaccineNote",
|
"path": "openapi_first.templates.vet_app.models.VaccineNote",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('VaccineNote', 62, 65)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"noteType": {
|
"noteType": {
|
||||||
@@ -280,7 +322,7 @@
|
|||||||
"name": "PreOpNote",
|
"name": "PreOpNote",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.PreOpNote",
|
"path": "openapi_first.templates.vet_app.models.PreOpNote",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('PreOpNote', 68, 71)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"noteType": {
|
"noteType": {
|
||||||
@@ -310,7 +352,7 @@
|
|||||||
"name": "SurgeryNote",
|
"name": "SurgeryNote",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.SurgeryNote",
|
"path": "openapi_first.templates.vet_app.models.SurgeryNote",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('SurgeryNote', 74, 77)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"noteType": {
|
"noteType": {
|
||||||
@@ -347,7 +389,7 @@
|
|||||||
"name": "Procedure",
|
"name": "Procedure",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.Procedure",
|
"path": "openapi_first.templates.vet_app.models.Procedure",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Procedure', 86, 90)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -384,7 +426,7 @@
|
|||||||
"name": "TreatmentBase",
|
"name": "TreatmentBase",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.TreatmentBase",
|
"path": "openapi_first.templates.vet_app.models.TreatmentBase",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('TreatmentBase', 93, 96)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"label": {
|
"label": {
|
||||||
@@ -414,14 +456,14 @@
|
|||||||
"name": "TreatmentCreate",
|
"name": "TreatmentCreate",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.TreatmentCreate",
|
"path": "openapi_first.templates.vet_app.models.TreatmentCreate",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('TreatmentCreate', 99, 100)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"Treatment": {
|
"Treatment": {
|
||||||
"name": "Treatment",
|
"name": "Treatment",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.Treatment",
|
"path": "openapi_first.templates.vet_app.models.Treatment",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Treatment', 103, 104)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -437,7 +479,7 @@
|
|||||||
"name": "PetBase",
|
"name": "PetBase",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.PetBase",
|
"path": "openapi_first.templates.vet_app.models.PetBase",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('PetBase', 107, 114)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"name": {
|
"name": {
|
||||||
@@ -495,7 +537,7 @@
|
|||||||
"name": "PetCreate",
|
"name": "PetCreate",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.PetCreate",
|
"path": "openapi_first.templates.vet_app.models.PetCreate",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('PetCreate', 117, 118)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"parent_ids": {
|
"parent_ids": {
|
||||||
@@ -511,7 +553,7 @@
|
|||||||
"name": "Pet",
|
"name": "Pet",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.Pet",
|
"path": "openapi_first.templates.vet_app.models.Pet",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Pet', 121, 123)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -534,7 +576,7 @@
|
|||||||
"name": "AppointmentBase",
|
"name": "AppointmentBase",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.AppointmentBase",
|
"path": "openapi_first.templates.vet_app.models.AppointmentBase",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('AppointmentBase', 126, 130)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"date": {
|
"date": {
|
||||||
@@ -571,7 +613,7 @@
|
|||||||
"name": "AppointmentCreate",
|
"name": "AppointmentCreate",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.AppointmentCreate",
|
"path": "openapi_first.templates.vet_app.models.AppointmentCreate",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('AppointmentCreate', 133, 136)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"pet_id": {
|
"pet_id": {
|
||||||
@@ -601,7 +643,7 @@
|
|||||||
"name": "Appointment",
|
"name": "Appointment",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.models.Appointment",
|
"path": "openapi_first.templates.vet_app.models.Appointment",
|
||||||
"signature": null,
|
"signature": "<bound method Class.signature of Class('Appointment', 139, 143)>",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"members": {
|
"members": {
|
||||||
"id": {
|
"id": {
|
||||||
|
|||||||
@@ -4,193 +4,270 @@
|
|||||||
"path": "openapi_first.templates.vet_app.routes",
|
"path": "openapi_first.templates.vet_app.routes",
|
||||||
"docstring": "Veterinary Clinic route handlers bound via OpenAPI operationId.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.",
|
"docstring": "Veterinary Clinic route handlers bound via OpenAPI operationId.\n\nHandlers explicitly control HTTP response status codes to ensure runtime\nbehavior matches the OpenAPI contract. Domain models defined using\nPydantic are used for request and response payloads.\n\nNo routing decorators, path definitions, or implicit framework behavior\nappear in this module. All routing, HTTP methods, and schemas are defined\nin the OpenAPI specification.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"Response": {
|
||||||
|
"name": "Response",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.Response",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('Response', 'fastapi.Response')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"HTTPException": {
|
||||||
|
"name": "HTTPException",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.HTTPException",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('HTTPException', 'fastapi.HTTPException')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"UploadFile": {
|
||||||
|
"name": "UploadFile",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.UploadFile",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('UploadFile', 'fastapi.UploadFile')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"StreamingResponse": {
|
||||||
|
"name": "StreamingResponse",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.StreamingResponse",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('StreamingResponse', 'fastapi.responses.StreamingResponse')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"subscribe": {
|
||||||
|
"name": "subscribe",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.subscribe",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('subscribe', 'sse.subscribe')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"unsubscribe": {
|
||||||
|
"name": "unsubscribe",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.unsubscribe",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('unsubscribe', 'sse.unsubscribe')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"ParentCreate": {
|
||||||
|
"name": "ParentCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.ParentCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('ParentCreate', 'models.ParentCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"VetCreate": {
|
||||||
|
"name": "VetCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.VetCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('VetCreate', 'models.VetCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"TreatmentCreate": {
|
||||||
|
"name": "TreatmentCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.TreatmentCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('TreatmentCreate', 'models.TreatmentCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"PetCreate": {
|
||||||
|
"name": "PetCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.PetCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('PetCreate', 'models.PetCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"AppointmentCreate": {
|
||||||
|
"name": "AppointmentCreate",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.routes.AppointmentCreate",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('AppointmentCreate', 'models.AppointmentCreate')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"list_parents": {
|
"list_parents": {
|
||||||
"name": "list_parents",
|
"name": "list_parents",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.list_parents",
|
"path": "openapi_first.templates.vet_app.routes.list_parents",
|
||||||
"signature": "list_parents(limit: int = 20, offset: int = 0) -> dict",
|
"signature": "<bound method Function.signature of Function('list_parents', 60, 76)>",
|
||||||
"docstring": "List parents (paginated).\n\nParameters\n----------\nlimit : int\n Maximum number of records to return.\noffset : int\n Number of records to skip.\n\nReturns\n-------\ndict\n Paginated response with ``total`` and ``items``."
|
"docstring": "List parents (paginated).\n\nParameters\n----------\nlimit : int\n Maximum number of records to return.\noffset : int\n Number of records to skip.\n\nReturns\n-------\ndict\n Paginated response with ``total`` and ``items``."
|
||||||
},
|
},
|
||||||
"create_parent": {
|
"create_parent": {
|
||||||
"name": "create_parent",
|
"name": "create_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.create_parent",
|
"path": "openapi_first.templates.vet_app.routes.create_parent",
|
||||||
"signature": "create_parent(payload: ParentCreate, response: Response) -> Parent",
|
"signature": "<bound method Function.signature of Function('create_parent', 79, 94)>",
|
||||||
"docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\nresponse : Response\n Response object used to set the HTTP status code.\n\nReturns\n-------\nParent\n The newly created parent."
|
"docstring": "Create a parent.\n\nParameters\n----------\npayload : ParentCreate\n Parent data excluding the ``id`` field.\n\nReturns\n-------\nParent\n The newly created parent."
|
||||||
},
|
},
|
||||||
"get_parent": {
|
"get_parent": {
|
||||||
"name": "get_parent",
|
"name": "get_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.get_parent",
|
"path": "openapi_first.templates.vet_app.routes.get_parent",
|
||||||
"signature": "get_parent(id: int) -> Parent",
|
"signature": "<bound method Function.signature of Function('get_parent', 97, 118)>",
|
||||||
"docstring": "Retrieve a single parent by ID.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nReturns\n-------\nParent\n The requested parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist."
|
"docstring": "Retrieve a single parent by ID.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nReturns\n-------\nParent\n The requested parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist."
|
||||||
},
|
},
|
||||||
"update_parent": {
|
"update_parent": {
|
||||||
"name": "update_parent",
|
"name": "update_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.update_parent",
|
"path": "openapi_first.templates.vet_app.routes.update_parent",
|
||||||
"signature": "update_parent(id: int, payload: ParentCreate) -> Parent",
|
"signature": "<bound method Function.signature of Function('update_parent', 121, 144)>",
|
||||||
"docstring": "Update an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\npayload : ParentCreate\n Updated parent data.\n\nReturns\n-------\nParent\n The updated parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist."
|
"docstring": "Update an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\npayload : ParentCreate\n Updated parent data.\n\nReturns\n-------\nParent\n The updated parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist."
|
||||||
},
|
},
|
||||||
"delete_parent": {
|
"delete_parent": {
|
||||||
"name": "delete_parent",
|
"name": "delete_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.delete_parent",
|
"path": "openapi_first.templates.vet_app.routes.delete_parent",
|
||||||
"signature": "delete_parent(id: int, response: Response) -> None",
|
"signature": "<bound method Function.signature of Function('delete_parent', 147, 164)>",
|
||||||
"docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\nresponse : Response\n Response object used to set the HTTP status code.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist."
|
"docstring": "Delete an existing parent.\n\nParameters\n----------\nid : int\n Identifier of the parent.\n\nRaises\n------\nHTTPException\n 404 if the parent does not exist."
|
||||||
},
|
},
|
||||||
"list_vets": {
|
"list_vets": {
|
||||||
"name": "list_vets",
|
"name": "list_vets",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.list_vets",
|
"path": "openapi_first.templates.vet_app.routes.list_vets",
|
||||||
"signature": "list_vets(limit: int = 20, offset: int = 0)",
|
"signature": "<bound method Function.signature of Function('list_vets', 172, 175)>",
|
||||||
"docstring": "List vets (paginated)."
|
"docstring": "List vets (paginated)."
|
||||||
},
|
},
|
||||||
"create_vet": {
|
"create_vet": {
|
||||||
"name": "create_vet",
|
"name": "create_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.create_vet",
|
"path": "openapi_first.templates.vet_app.routes.create_vet",
|
||||||
"signature": "create_vet(payload: VetCreate, response: Response)",
|
"signature": "<bound method Function.signature of Function('create_vet', 178, 182)>",
|
||||||
"docstring": "Create a vet."
|
"docstring": "Create a vet."
|
||||||
},
|
},
|
||||||
"get_vet": {
|
"get_vet": {
|
||||||
"name": "get_vet",
|
"name": "get_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.get_vet",
|
"path": "openapi_first.templates.vet_app.routes.get_vet",
|
||||||
"signature": "get_vet(id: int)",
|
"signature": "<bound method Function.signature of Function('get_vet', 185, 190)>",
|
||||||
"docstring": "Retrieve a single vet by ID."
|
"docstring": "Retrieve a single vet by ID."
|
||||||
},
|
},
|
||||||
"update_vet": {
|
"update_vet": {
|
||||||
"name": "update_vet",
|
"name": "update_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.update_vet",
|
"path": "openapi_first.templates.vet_app.routes.update_vet",
|
||||||
"signature": "update_vet(id: int, payload: VetCreate)",
|
"signature": "<bound method Function.signature of Function('update_vet', 193, 198)>",
|
||||||
"docstring": "Update an existing vet."
|
"docstring": "Update an existing vet."
|
||||||
},
|
},
|
||||||
"delete_vet": {
|
"delete_vet": {
|
||||||
"name": "delete_vet",
|
"name": "delete_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.delete_vet",
|
"path": "openapi_first.templates.vet_app.routes.delete_vet",
|
||||||
"signature": "delete_vet(id: int, response: Response)",
|
"signature": "<bound method Function.signature of Function('delete_vet', 201, 207)>",
|
||||||
"docstring": "Delete an existing vet."
|
"docstring": "Delete an existing vet."
|
||||||
},
|
},
|
||||||
"list_treatments": {
|
"list_treatments": {
|
||||||
"name": "list_treatments",
|
"name": "list_treatments",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.list_treatments",
|
"path": "openapi_first.templates.vet_app.routes.list_treatments",
|
||||||
"signature": "list_treatments() -> list[Treatment]",
|
"signature": "<bound method Function.signature of Function('list_treatments', 215, 223)>",
|
||||||
"docstring": "List treatments (catalogue).\n\nReturns\n-------\nlist[Treatment]\n A list of treatment domain objects."
|
"docstring": "List treatments (catalogue).\n\nReturns\n-------\nlist[Treatment]\n A list of treatment domain objects."
|
||||||
},
|
},
|
||||||
"create_treatment": {
|
"create_treatment": {
|
||||||
"name": "create_treatment",
|
"name": "create_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.create_treatment",
|
"path": "openapi_first.templates.vet_app.routes.create_treatment",
|
||||||
"signature": "create_treatment(payload: TreatmentCreate, response: Response)",
|
"signature": "<bound method Function.signature of Function('create_treatment', 226, 230)>",
|
||||||
"docstring": "Add a treatment (admin only)."
|
"docstring": "Add a treatment (admin only)."
|
||||||
},
|
},
|
||||||
"get_treatment": {
|
"get_treatment": {
|
||||||
"name": "get_treatment",
|
"name": "get_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.get_treatment",
|
"path": "openapi_first.templates.vet_app.routes.get_treatment",
|
||||||
"signature": "get_treatment(id: int)",
|
"signature": "<bound method Function.signature of Function('get_treatment', 233, 238)>",
|
||||||
"docstring": "Retrieve a single treatment by ID."
|
"docstring": "Retrieve a single treatment by ID."
|
||||||
},
|
},
|
||||||
"update_treatment": {
|
"update_treatment": {
|
||||||
"name": "update_treatment",
|
"name": "update_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.update_treatment",
|
"path": "openapi_first.templates.vet_app.routes.update_treatment",
|
||||||
"signature": "update_treatment(id: int, payload: TreatmentCreate)",
|
"signature": "<bound method Function.signature of Function('update_treatment', 241, 246)>",
|
||||||
"docstring": "Update an existing treatment."
|
"docstring": "Update an existing treatment."
|
||||||
},
|
},
|
||||||
"delete_treatment": {
|
"delete_treatment": {
|
||||||
"name": "delete_treatment",
|
"name": "delete_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.delete_treatment",
|
"path": "openapi_first.templates.vet_app.routes.delete_treatment",
|
||||||
"signature": "delete_treatment(id: int, response: Response)",
|
"signature": "<bound method Function.signature of Function('delete_treatment', 249, 255)>",
|
||||||
"docstring": "Delete an existing treatment."
|
"docstring": "Delete an existing treatment."
|
||||||
},
|
},
|
||||||
"list_pets": {
|
"list_pets": {
|
||||||
"name": "list_pets",
|
"name": "list_pets",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.list_pets",
|
"path": "openapi_first.templates.vet_app.routes.list_pets",
|
||||||
"signature": "list_pets(limit: int = 20, offset: int = 0)",
|
"signature": "<bound method Function.signature of Function('list_pets', 263, 266)>",
|
||||||
"docstring": "List pets (paginated)."
|
"docstring": "List pets (paginated)."
|
||||||
},
|
},
|
||||||
"create_pet": {
|
"create_pet": {
|
||||||
"name": "create_pet",
|
"name": "create_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.create_pet",
|
"path": "openapi_first.templates.vet_app.routes.create_pet",
|
||||||
"signature": "create_pet(payload: PetCreate, response: Response)",
|
"signature": "<bound method Function.signature of Function('create_pet', 269, 273)>",
|
||||||
"docstring": "Create a pet."
|
"docstring": "Create a pet."
|
||||||
},
|
},
|
||||||
"get_pet": {
|
"get_pet": {
|
||||||
"name": "get_pet",
|
"name": "get_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.get_pet",
|
"path": "openapi_first.templates.vet_app.routes.get_pet",
|
||||||
"signature": "get_pet(id: int)",
|
"signature": "<bound method Function.signature of Function('get_pet', 276, 281)>",
|
||||||
"docstring": "Retrieve a single pet by ID."
|
"docstring": "Retrieve a single pet by ID."
|
||||||
},
|
},
|
||||||
"update_pet": {
|
"update_pet": {
|
||||||
"name": "update_pet",
|
"name": "update_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.update_pet",
|
"path": "openapi_first.templates.vet_app.routes.update_pet",
|
||||||
"signature": "update_pet(id: int, payload: PetCreate)",
|
"signature": "<bound method Function.signature of Function('update_pet', 284, 289)>",
|
||||||
"docstring": "Update an existing pet."
|
"docstring": "Update an existing pet."
|
||||||
},
|
},
|
||||||
"delete_pet": {
|
"delete_pet": {
|
||||||
"name": "delete_pet",
|
"name": "delete_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.delete_pet",
|
"path": "openapi_first.templates.vet_app.routes.delete_pet",
|
||||||
"signature": "delete_pet(id: int, response: Response)",
|
"signature": "<bound method Function.signature of Function('delete_pet', 292, 298)>",
|
||||||
"docstring": "Delete an existing pet."
|
"docstring": "Delete an existing pet."
|
||||||
},
|
},
|
||||||
"upload_pet_photo": {
|
"upload_pet_photo": {
|
||||||
"name": "upload_pet_photo",
|
"name": "upload_pet_photo",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.upload_pet_photo",
|
"path": "openapi_first.templates.vet_app.routes.upload_pet_photo",
|
||||||
"signature": "upload_pet_photo(id: int, file: UploadFile) -> dict",
|
"signature": "<bound method Function.signature of Function('upload_pet_photo', 301, 317)>",
|
||||||
"docstring": "Upload a pet photo.\n\nParameters\n----------\nid : int\n Identifier of the pet.\nfile : UploadFile\n Image file to upload.\n\nReturns\n-------\ndict\n A confirmation with the pet ID."
|
"docstring": "Upload a pet photo.\n\nParameters\n----------\nid : int\n Identifier of the pet.\nfile : UploadFile\n Image file to upload.\n\nReturns\n-------\ndict\n A confirmation with the pet ID."
|
||||||
},
|
},
|
||||||
"list_appointments": {
|
"list_appointments": {
|
||||||
"name": "list_appointments",
|
"name": "list_appointments",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.list_appointments",
|
"path": "openapi_first.templates.vet_app.routes.list_appointments",
|
||||||
"signature": "list_appointments(limit: int = 20, offset: int = 0, date: str = None, vet: int = None, pet: int = None)",
|
"signature": "<bound method Function.signature of Function('list_appointments', 325, 337)>",
|
||||||
"docstring": "List appointments (paginated, filterable)."
|
"docstring": "List appointments (paginated, filterable)."
|
||||||
},
|
},
|
||||||
"create_appointment": {
|
"create_appointment": {
|
||||||
"name": "create_appointment",
|
"name": "create_appointment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.create_appointment",
|
"path": "openapi_first.templates.vet_app.routes.create_appointment",
|
||||||
"signature": "create_appointment(payload: AppointmentCreate, response: Response)",
|
"signature": "<bound method Function.signature of Function('create_appointment', 340, 344)>",
|
||||||
"docstring": "Create an appointment."
|
"docstring": "Create an appointment."
|
||||||
},
|
},
|
||||||
"get_appointment": {
|
"get_appointment": {
|
||||||
"name": "get_appointment",
|
"name": "get_appointment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.get_appointment",
|
"path": "openapi_first.templates.vet_app.routes.get_appointment",
|
||||||
"signature": "get_appointment(id: int)",
|
"signature": "<bound method Function.signature of Function('get_appointment', 347, 352)>",
|
||||||
"docstring": "Retrieve a single appointment by ID."
|
"docstring": "Retrieve a single appointment by ID."
|
||||||
},
|
},
|
||||||
"update_appointment": {
|
"update_appointment": {
|
||||||
"name": "update_appointment",
|
"name": "update_appointment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.update_appointment",
|
"path": "openapi_first.templates.vet_app.routes.update_appointment",
|
||||||
"signature": "update_appointment(id: int, payload: AppointmentCreate)",
|
"signature": "<bound method Function.signature of Function('update_appointment', 355, 360)>",
|
||||||
"docstring": "Update an existing appointment."
|
"docstring": "Update an existing appointment."
|
||||||
},
|
},
|
||||||
"delete_appointment": {
|
"delete_appointment": {
|
||||||
"name": "delete_appointment",
|
"name": "delete_appointment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.delete_appointment",
|
"path": "openapi_first.templates.vet_app.routes.delete_appointment",
|
||||||
"signature": "delete_appointment(id: int, response: Response)",
|
"signature": "<bound method Function.signature of Function('delete_appointment', 363, 369)>",
|
||||||
"docstring": "Delete an existing appointment."
|
"docstring": "Delete an existing appointment."
|
||||||
},
|
},
|
||||||
"stream_actions": {
|
"stream_actions": {
|
||||||
"name": "stream_actions",
|
"name": "stream_actions",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.routes.stream_actions",
|
"path": "openapi_first.templates.vet_app.routes.stream_actions",
|
||||||
"signature": "stream_actions(id: int)",
|
"signature": "<bound method Function.signature of Function('stream_actions', 372, 389)>",
|
||||||
"docstring": "Stream animal actions via SSE, scoped to a pet's species."
|
"docstring": "Stream animal actions via SSE, scoped to a pet's species."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,18 +4,39 @@
|
|||||||
"path": "openapi_first.templates.vet_app.sse",
|
"path": "openapi_first.templates.vet_app.sse",
|
||||||
"docstring": null,
|
"docstring": null,
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"asyncio": {
|
||||||
|
"name": "asyncio",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.sse.asyncio",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('asyncio', 'asyncio')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"random": {
|
||||||
|
"name": "random",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.sse.random",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('random', 'random')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"json": {
|
||||||
|
"name": "json",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.sse.json",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('json', 'json')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"subscribe": {
|
"subscribe": {
|
||||||
"name": "subscribe",
|
"name": "subscribe",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.sse.subscribe",
|
"path": "openapi_first.templates.vet_app.sse.subscribe",
|
||||||
"signature": "subscribe(pet_id: int, species: str) -> asyncio.Queue",
|
"signature": "<bound method Function.signature of Function('subscribe', 33, 37)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"unsubscribe": {
|
"unsubscribe": {
|
||||||
"name": "unsubscribe",
|
"name": "unsubscribe",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.sse.unsubscribe",
|
"path": "openapi_first.templates.vet_app.sse.unsubscribe",
|
||||||
"signature": "unsubscribe(pet_id: int, q: asyncio.Queue)",
|
"signature": "<bound method Function.signature of Function('unsubscribe', 40, 48)>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,46 +4,60 @@
|
|||||||
"path": "openapi_first.templates.vet_app.test_vet_app",
|
"path": "openapi_first.templates.vet_app.test_vet_app",
|
||||||
"docstring": "End-to-end tests for the OpenAPI-first Veterinary Clinic example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.",
|
"docstring": "End-to-end tests for the OpenAPI-first Veterinary Clinic example app.\n\nThese tests validate that all CRUD operations behave correctly\nagainst the in-memory mock data store using Pydantic models.",
|
||||||
"objects": {
|
"objects": {
|
||||||
|
"TestClient": {
|
||||||
|
"name": "TestClient",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.test_vet_app.TestClient",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('TestClient', 'fastapi.testclient.TestClient')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"name": "app",
|
||||||
|
"kind": "alias",
|
||||||
|
"path": "openapi_first.templates.vet_app.test_vet_app.app",
|
||||||
|
"signature": "<bound method Alias.signature of Alias('app', 'main.app')>",
|
||||||
|
"docstring": null
|
||||||
|
},
|
||||||
"load_openapi": {
|
"load_openapi": {
|
||||||
"name": "load_openapi",
|
"name": "load_openapi",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.load_openapi",
|
"path": "openapi_first.templates.vet_app.test_vet_app.load_openapi",
|
||||||
"signature": "load_openapi(path: str | Path)",
|
"signature": "<bound method Alias.signature of Alias('load_openapi', 'openapi_first.loader.load_openapi')>",
|
||||||
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
"docstring": "Load and validate an OpenAPI 3.x specification from disk.\n\nArgs:\n path (str | Path):\n Filesystem path to an OpenAPI specification file. Supported\n extensions: `.json`, `.yaml`, `.yml`.\n\nReturns:\n dict[str, Any]:\n Parsed and validated OpenAPI specification.\n\nRaises:\n OpenAPISpecLoadError:\n If the file does not exist, cannot be parsed, or fails OpenAPI\n schema validation.\n\nNotes:\n **Guarantees:**\n\n - The specification is parsed based on file extension and validated\n using a strict OpenAPI schema validator.\n - Any error results in an immediate exception, preventing\n application startup."
|
||||||
},
|
},
|
||||||
"OpenAPIClient": {
|
"OpenAPIClient": {
|
||||||
"name": "OpenAPIClient",
|
"name": "OpenAPIClient",
|
||||||
"kind": "class",
|
"kind": "class",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient",
|
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient",
|
||||||
"signature": "OpenAPIClient(spec: dict[str, Any], base_url: str | None = None, client: httpx.Client | None = None)",
|
"signature": "<bound method Alias.signature of Alias('OpenAPIClient', 'openapi_first.client.OpenAPIClient')>",
|
||||||
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nArgs:\n spec (dict[str, Any]):\n Parsed OpenAPI 3.x specification.\n base_url (str | None):\n Base URL of the target service. If omitted, the first entry in the\n OpenAPI `servers` list is used.\n client (httpx.Client | None):\n Optional preconfigured httpx client instance.\n\nRaises:\n OpenAPIClientError:\n If no servers are defined, spec has no paths, operationIds are\n missing/duplicate, or required parameters are missing.\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
"docstring": "OpenAPI-first HTTP client (`httpx`-based).\n\nNotes:\n **Responsibilities:**\n\n - This client derives all callable methods directly from an\n OpenAPI 3.x specification. Each `operationId` becomes a method\n on the client instance.\n\n **Guarantees:**\n\n - One callable per `operationId`.\n - Explicit parameters (path, query, headers, body).\n - No implicit schema inference or mutation.\n - Returns raw `httpx.Response` objects.\n - No response validation or deserialization.\n\nExample:\n ```python\n from openapi_first import loader, client\n\n spec = loader.load_openapi(\"openapi.yaml\")\n\n api = client.OpenAPIClient(\n spec=spec,\n base_url=\"http://localhost:8000\",\n )\n\n # Call operationId: getUser\n response = api.getUser(\n path_params={\"user_id\": 123}\n )\n\n print(response.status_code)\n print(response.json())\n ```",
|
||||||
"members": {
|
"members": {
|
||||||
"spec": {
|
"spec": {
|
||||||
"name": "spec",
|
"name": "spec",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.spec",
|
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.spec",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('spec', 'openapi_first.client.OpenAPIClient.spec')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"base_url": {
|
"base_url": {
|
||||||
"name": "base_url",
|
"name": "base_url",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.base_url",
|
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.base_url",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('base_url', 'openapi_first.client.OpenAPIClient.base_url')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"name": "client",
|
"name": "client",
|
||||||
"kind": "attribute",
|
"kind": "attribute",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.client",
|
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.client",
|
||||||
"signature": null,
|
"signature": "<bound method Alias.signature of Alias('client', 'openapi_first.client.OpenAPIClient.client')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"name": "operations",
|
"name": "operations",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.operations",
|
"path": "openapi_first.templates.vet_app.test_vet_app.OpenAPIClient.operations",
|
||||||
"signature": "operations()",
|
"signature": "<bound method Alias.signature of Alias('operations', 'openapi_first.client.OpenAPIClient.operations')>",
|
||||||
"docstring": null
|
"docstring": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,91 +87,91 @@
|
|||||||
"name": "test_list_parents",
|
"name": "test_list_parents",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_list_parents",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_list_parents",
|
||||||
"signature": "test_list_parents()",
|
"signature": "<bound method Function.signature of Function('test_list_parents', 24, 30)>",
|
||||||
"docstring": "List parents returns paginated response."
|
"docstring": "List parents returns paginated response."
|
||||||
},
|
},
|
||||||
"test_create_parent": {
|
"test_create_parent": {
|
||||||
"name": "test_create_parent",
|
"name": "test_create_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_create_parent",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_create_parent",
|
||||||
"signature": "test_create_parent()",
|
"signature": "<bound method Function.signature of Function('test_create_parent', 33, 40)>",
|
||||||
"docstring": "Creating a parent returns 201 with the created entity."
|
"docstring": "Creating a parent returns 201 with the created entity."
|
||||||
},
|
},
|
||||||
"test_get_parent": {
|
"test_get_parent": {
|
||||||
"name": "test_get_parent",
|
"name": "test_get_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_get_parent",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_get_parent",
|
||||||
"signature": "test_get_parent()",
|
"signature": "<bound method Function.signature of Function('test_get_parent', 43, 48)>",
|
||||||
"docstring": "Get parent by ID returns the entity."
|
"docstring": "Get parent by ID returns the entity."
|
||||||
},
|
},
|
||||||
"test_update_parent": {
|
"test_update_parent": {
|
||||||
"name": "test_update_parent",
|
"name": "test_update_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_update_parent",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_update_parent",
|
||||||
"signature": "test_update_parent()",
|
"signature": "<bound method Function.signature of Function('test_update_parent', 51, 57)>",
|
||||||
"docstring": "Update parent replaces its values."
|
"docstring": "Update parent replaces its values."
|
||||||
},
|
},
|
||||||
"test_delete_parent": {
|
"test_delete_parent": {
|
||||||
"name": "test_delete_parent",
|
"name": "test_delete_parent",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_delete_parent",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_delete_parent",
|
||||||
"signature": "test_delete_parent()",
|
"signature": "<bound method Function.signature of Function('test_delete_parent', 60, 64)>",
|
||||||
"docstring": "Delete parent returns 204 and removes the entity."
|
"docstring": "Delete parent returns 204 and removes the entity."
|
||||||
},
|
},
|
||||||
"test_list_vets": {
|
"test_list_vets": {
|
||||||
"name": "test_list_vets",
|
"name": "test_list_vets",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_list_vets",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_list_vets",
|
||||||
"signature": "test_list_vets()",
|
"signature": "<bound method Function.signature of Function('test_list_vets', 67, 73)>",
|
||||||
"docstring": "List vets returns paginated response."
|
"docstring": "List vets returns paginated response."
|
||||||
},
|
},
|
||||||
"test_create_vet": {
|
"test_create_vet": {
|
||||||
"name": "test_create_vet",
|
"name": "test_create_vet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_create_vet",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_create_vet",
|
||||||
"signature": "test_create_vet()",
|
"signature": "<bound method Function.signature of Function('test_create_vet', 76, 81)>",
|
||||||
"docstring": "Creating a vet returns 201."
|
"docstring": "Creating a vet returns 201."
|
||||||
},
|
},
|
||||||
"test_list_treatments": {
|
"test_list_treatments": {
|
||||||
"name": "test_list_treatments",
|
"name": "test_list_treatments",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_list_treatments",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_list_treatments",
|
||||||
"signature": "test_list_treatments()",
|
"signature": "<bound method Function.signature of Function('test_list_treatments', 84, 88)>",
|
||||||
"docstring": "List treatments returns an array."
|
"docstring": "List treatments returns an array."
|
||||||
},
|
},
|
||||||
"test_create_treatment": {
|
"test_create_treatment": {
|
||||||
"name": "test_create_treatment",
|
"name": "test_create_treatment",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_create_treatment",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_create_treatment",
|
||||||
"signature": "test_create_treatment()",
|
"signature": "<bound method Function.signature of Function('test_create_treatment', 91, 96)>",
|
||||||
"docstring": "Creating a treatment returns 201."
|
"docstring": "Creating a treatment returns 201."
|
||||||
},
|
},
|
||||||
"test_create_pet": {
|
"test_create_pet": {
|
||||||
"name": "test_create_pet",
|
"name": "test_create_pet",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_create_pet",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_create_pet",
|
||||||
"signature": "test_create_pet()",
|
"signature": "<bound method Function.signature of Function('test_create_pet', 99, 105)>",
|
||||||
"docstring": "Creating a pet links FK references."
|
"docstring": "Creating a pet links FK references."
|
||||||
},
|
},
|
||||||
"test_upload_pet_photo": {
|
"test_upload_pet_photo": {
|
||||||
"name": "test_upload_pet_photo",
|
"name": "test_upload_pet_photo",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_upload_pet_photo",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_upload_pet_photo",
|
||||||
"signature": "test_upload_pet_photo()",
|
"signature": "<bound method Function.signature of Function('test_upload_pet_photo', 108, 115)>",
|
||||||
"docstring": "Upload pet photo returns 200."
|
"docstring": "Upload pet photo returns 200."
|
||||||
},
|
},
|
||||||
"test_list_appointments": {
|
"test_list_appointments": {
|
||||||
"name": "test_list_appointments",
|
"name": "test_list_appointments",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_list_appointments",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_list_appointments",
|
||||||
"signature": "test_list_appointments()",
|
"signature": "<bound method Function.signature of Function('test_list_appointments', 118, 124)>",
|
||||||
"docstring": "List appointments returns paginated response with filter params."
|
"docstring": "List appointments returns paginated response with filter params."
|
||||||
},
|
},
|
||||||
"test_full_appointment_lifecycle": {
|
"test_full_appointment_lifecycle": {
|
||||||
"name": "test_full_appointment_lifecycle",
|
"name": "test_full_appointment_lifecycle",
|
||||||
"kind": "function",
|
"kind": "function",
|
||||||
"path": "openapi_first.templates.vet_app.test_vet_app.test_full_appointment_lifecycle",
|
"path": "openapi_first.templates.vet_app.test_vet_app.test_full_appointment_lifecycle",
|
||||||
"signature": "test_full_appointment_lifecycle()",
|
"signature": "<bound method Function.signature of Function('test_full_appointment_lifecycle', 127, 151)>",
|
||||||
"docstring": "Create a parent, vet, treatment, pet, then an appointment."
|
"docstring": "Create a parent, vet, treatment, pet, then an appointment."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
theme:
|
|
||||||
name: material
|
|
||||||
palette:
|
|
||||||
scheme: slate
|
|
||||||
primary: blue grey
|
|
||||||
accent: teal
|
|
||||||
font:
|
|
||||||
text: Roboto
|
|
||||||
code: JetBrains Mono
|
|
||||||
features:
|
|
||||||
- navigation.sections
|
|
||||||
- navigation.expand
|
|
||||||
- navigation.top
|
|
||||||
- navigation.instant
|
|
||||||
- navigation.tracking
|
|
||||||
- navigation.indexes
|
|
||||||
- content.code.copy
|
|
||||||
- content.code.annotate
|
|
||||||
- content.tabs.link
|
|
||||||
- content.action.edit
|
|
||||||
- search.highlight
|
|
||||||
- search.share
|
|
||||||
- search.suggest
|
|
||||||
- navigation.tabs
|
|
||||||
- toc.integrate
|
|
||||||
- header.autohide
|
|
||||||
- announce.dismiss
|
|
||||||
- footer.social
|
|
||||||
- content.code.select
|
|
||||||
- content.code.line_numbers
|
|
||||||
- content.tooltips
|
|
||||||
icon:
|
|
||||||
logo: material/api
|
|
||||||
repo: fontawesome/brands/github
|
|
||||||
markdown_extensions:
|
|
||||||
- pymdownx.superfences
|
|
||||||
- pymdownx.inlinehilite
|
|
||||||
- pymdownx.snippets
|
|
||||||
- admonition
|
|
||||||
- pymdownx.details
|
|
||||||
- pymdownx.highlight:
|
|
||||||
linenums: true
|
|
||||||
anchor_linenums: true
|
|
||||||
line_spans: __span
|
|
||||||
pygments_lang_class: true
|
|
||||||
- pymdownx.tabbed:
|
|
||||||
alternate_style: true
|
|
||||||
- pymdownx.tasklist:
|
|
||||||
custom_checkbox: true
|
|
||||||
- tables
|
|
||||||
- footnotes
|
|
||||||
- pymdownx.caret
|
|
||||||
- pymdownx.tilde
|
|
||||||
- pymdownx.mark
|
|
||||||
extra_css:
|
|
||||||
- https://unpkg.com/dracula-prism/dist/css/dracula-prism.css
|
|
||||||
plugins:
|
|
||||||
- search
|
|
||||||
site_name: openapi_first
|
|
||||||
docs_dir: wiki
|
|
||||||
site_dir: ../site/wiki
|
|
||||||
nav:
|
|
||||||
- Home: index.md
|
|
||||||
- Overview: 01_overview.md
|
|
||||||
- Components: 02_components.md
|
|
||||||
- Use Cases:
|
|
||||||
- Quickstart: 03_use_cases/01_quickstart.md
|
|
||||||
- Templates: 03_use_cases/02_templates.md
|
|
||||||
- Client: 03_use_cases/03_client.md
|
|
||||||
- Codegen: 03_use_cases/04_codegen.md
|
|
||||||
- Design: 04_design.md
|
|
||||||
- Security: 05_security.md
|
|
||||||
- Error Handling: 06_error_handling.md
|
|
||||||
- Testing: 07_testing.md
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
# Overview — The OpenAPI-First Mental Model
|
|
||||||
|
|
||||||
`openapi-first` inverts the usual FastAPI workflow. Instead of decorating routes in code and letting FastAPI *invent* an OpenAPI document for you, you write **one OpenAPI document first** and let the library assemble both the application **and** the client from it. The spec is the contract; code is the implementation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧠 1. The Mental Model
|
|
||||||
|
|
||||||
### 1.1 One source of truth
|
|
||||||
|
|
||||||
Your OpenAPI document (`openapi.yaml` or `openapi.json`) is the **single authoritative contract**:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
paths:
|
|
||||||
/health:
|
|
||||||
get:
|
|
||||||
operationId: get_health
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
```
|
|
||||||
|
|
||||||
Every route, method, parameter, schema, and security requirement lives **here** — and only here. Code never declares routes.
|
|
||||||
|
|
||||||
### 1.2 `operationId` is the binding key
|
|
||||||
|
|
||||||
The only bridge between the spec and your Python code is the `operationId`. Each operation maps, **by name**, to exactly one plain callable:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# routes.py
|
|
||||||
def get_health():
|
|
||||||
"""Health check operation handler."""
|
|
||||||
return {"status": "ok"}
|
|
||||||
```
|
|
||||||
|
|
||||||
`openapi_first` resolves `operationId: get_health` → `routes.get_health` and registers the route. **No decorators, no `@app.get`, no routing metadata in code.**
|
|
||||||
|
|
||||||
Guarantees this binding provides:
|
|
||||||
|
|
||||||
- 🚫 No undocumented route can exist — every route *must* be in the spec
|
|
||||||
- 🚫 No spec operation can go unhandled — every `operationId` *must* resolve at startup
|
|
||||||
- 🔒 No auth can be bypassed — security is injected from `securitySchemes` + per-operation `security`, spec-driven
|
|
||||||
- 🧱 No drift possible — the server and client are built from the *same* document
|
|
||||||
|
|
||||||
### 1.3 Fail-fast by design
|
|
||||||
|
|
||||||
Contract violations are detected **at application startup** (or client construction), never silently at request time:
|
|
||||||
|
|
||||||
| What goes wrong | When it fails |
|
|
||||||
|------------------------------------------|----------------------|
|
|
||||||
| Invalid / unloadable spec | `load_openapi` at startup |
|
|
||||||
| Spec fails OpenAPI 3.x validation | `load_openapi` at startup |
|
|
||||||
| `operationId` missing a handler | `bind_routes` at startup |
|
|
||||||
| Operation declared but no `operationId` | `bind_routes` at startup |
|
|
||||||
| Missing / duplicate `operationId` in client | `OpenAPIClient(...)` construction |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ 2. What It Looks Like
|
|
||||||
|
|
||||||
### 2.1 Scaffold an application
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first scaffold health_app my-health-service
|
|
||||||
openapi-first scaffold --list
|
|
||||||
```
|
|
||||||
|
|
||||||
`scaffold` copies a bundled template (verbatim — no code generation, no mutation) into a directory of your choice.
|
|
||||||
|
|
||||||
### 2.2 Bootstrap the server
|
|
||||||
|
|
||||||
```python
|
|
||||||
# main.py
|
|
||||||
from openapi_first.app import OpenAPIFirstApp
|
|
||||||
import routes
|
|
||||||
|
|
||||||
app = OpenAPIFirstApp(
|
|
||||||
openapi_path="openapi.yaml",
|
|
||||||
routes_module=routes,
|
|
||||||
title="My Service",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Run with your FastAPI-compatible server (ASGI):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uvicorn main:app --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
FastAPI itself drives the server; every route, response model, and security dependency comes from the spec. `/openapi.json` and Swagger UI always reflect the spec, byte-for-byte.
|
|
||||||
|
|
||||||
### 2.3 Talk to it with the client
|
|
||||||
|
|
||||||
```python
|
|
||||||
# client-side
|
|
||||||
from openapi_first.loader import load_openapi
|
|
||||||
from openapi_first.client import OpenAPIClient
|
|
||||||
|
|
||||||
spec = load_openapi("openapi.yaml")
|
|
||||||
client = OpenAPIClient(spec)
|
|
||||||
|
|
||||||
response = client.get_health() # operationId-driven call
|
|
||||||
print(response.status_code) # 200
|
|
||||||
print(response.json()) # {"status": "ok"}
|
|
||||||
```
|
|
||||||
|
|
||||||
`OpenAPIClient` builds one callable per `operationId` from the *same* spec, so the client can never drift from the server.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧩 3. Where Things Live
|
|
||||||
|
|
||||||
| Concern | Module |
|
|
||||||
|------------------------|---------------------|
|
|
||||||
| Load + validate spec | `openapi_first.loader` |
|
|
||||||
| Boot FastAPI app | `openapi_first.app` |
|
|
||||||
| Bind routes by opId | `openapi_first.binder` |
|
|
||||||
| HTTP client | `openapi_first.client` |
|
|
||||||
| Error hierarchy | `openapi_first.errors` |
|
|
||||||
| Security dependencies | `openapi_first.security` |
|
|
||||||
| Pydantic model codegen | `openapi_first.codegen` (uses `datamodel_code_generator`) |
|
|
||||||
| Route stub codegen | `openapi_first.codegen_routes` |
|
|
||||||
| Models / routes CLI | `openapi_first.cli` |
|
|
||||||
| Bundled templates | `openapi_first.templates` |
|
|
||||||
|
|
||||||
Jurisdictions:
|
|
||||||
|
|
||||||
- `loader`, `app`, `binder`, `client`, `errors`, `security` are the **library API surface** — stable, tested, documented.
|
|
||||||
- `templates` are **copyable scaffolds** — not part of the library API; excluded from lint/format/type gates, never imported at runtime.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 4. The Non-Goals
|
|
||||||
|
|
||||||
`openapi-first` deliberately does **not**:
|
|
||||||
|
|
||||||
- Parse decorators to generate an OpenAPI schema (that's default FastAPI behavior — the inverse)
|
|
||||||
- Generate models from code at runtime (only at **build time** via CLI, from spec → Pydantic)
|
|
||||||
- Validate request/response bodies against the spec at runtime (contract is enforced at startup / client construction; FastAPI + Pydantic handle runtime coercion)
|
|
||||||
- Invent routing from path conventions — `operationId` binding only
|
|
||||||
- Ship a production feature set in the bundled templates (they're demos/scaffolds: in-memory stores, no concurrency, no auth configured)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧭 5. Path Forward
|
|
||||||
|
|
||||||
New here? Start with [01 – Quickstart](03_use_cases/01_quickstart.md). Want to copy a runnable app? Jump to [02 – Templates](03_use_cases/02_templates.md). Digging into internals? See [Design](04_design.md).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [01 – Quickstart](03_use_cases/01_quickstart.md) · [02 – Components](02_components.md) · [01 – Overview](01_overview.md)
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
# Components — What Ships in the Box
|
|
||||||
|
|
||||||
The library is deliberately small. Everything you need to run an OpenAPI-first service and talk to it with a strict client fits in a handful of modules.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗂️ 1. Module Map
|
|
||||||
|
|
||||||
| Module | Responsibility | Import |
|
|
||||||
|---------------------------------|---------------------------------------------------|--------|
|
|
||||||
| `openapi_first.loader` | Load/validate the spec, resolve `{ENV_VAR}` | `load_openapi` |
|
|
||||||
| `openapi_first.app` | OpenAPI-first FastAPI application bootstrap | `OpenAPIFirstApp` |
|
|
||||||
| `openapi_first.binder` | Spec → route binding via `operationId` | `bind_routes` |
|
|
||||||
| `openapi_first.client` | `operationId`-driven HTTP client | `OpenAPIClient` |
|
|
||||||
| `openapi_first.errors` | Explicit error hierarchy | `OpenAPIFirstError`, `OpenAPIClientError`, `MissingOperationHandler` |
|
|
||||||
| `openapi_first.security` | Security-dependency construction from the spec | `parse_security_schemes`, `make_security_dependencies` |
|
|
||||||
| `openapi_first.codegen` | Pydantic model generation (build-time) | `generate_models` |
|
|
||||||
| `openapi_first.codegen_routes` | Route-handler stub generation (build-time) | `generate_routes` |
|
|
||||||
| `openapi_first.cli` | `scaffold` / `models` / `routes` command surface | CLI entry point |
|
|
||||||
| `openapi_first.templates` | Copyable application templates (NOT library API) | `openapi-first scaffold` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚙️ 2. `loader` — Load & Validate
|
|
||||||
|
|
||||||
`load_openapi(path: str | Path) -> dict[str, Any]` (in `openapi_first/loader.py`).
|
|
||||||
|
|
||||||
`loader.py` ensures a spec is real, readable, parseable, and schema-valid *before* anything else runs — a golden rule of fail-fast.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from openapi_first.loader import load_openapi
|
|
||||||
|
|
||||||
spec = load_openapi("openapi.yaml")
|
|
||||||
```
|
|
||||||
|
|
||||||
Behavior:
|
|
||||||
|
|
||||||
- Accepts `.json`, `.yaml`, `.yml` (parsed by extension).
|
|
||||||
- Runs strict OpenAPI 3.x validation (`openapi-spec-validator`) at load time.
|
|
||||||
- Raises `OpenAPISpecLoadError` on: missing file, unparseable content, or spec-validation failure.
|
|
||||||
- Does **not** modify, coerce, or "fix" the spec — it's a read-only gate.
|
|
||||||
|
|
||||||
**Env-var resolution** lives at the *security* layer rather than the loader (see [Security](05_security.md)).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧬 3. `app` — The Application Bootstrap
|
|
||||||
|
|
||||||
`OpenAPIFirstApp` (in `openapi_first/app.py`) is a FastAPI subclass that replaces manual route registration with OpenAPI-driven binding.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from openapi_first.app import OpenAPIFirstApp
|
|
||||||
import routes
|
|
||||||
|
|
||||||
app = OpenAPIFirstApp(
|
|
||||||
openapi_path="openapi.yaml",
|
|
||||||
routes_module=routes,
|
|
||||||
title="My Service",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Startup pipeline (fail-fast, in this order):**
|
|
||||||
|
|
||||||
1. **Load** the spec (`.yaml`/`.json`).
|
|
||||||
2. **Validate** it against OpenAPI 3.x schema.
|
|
||||||
3. **Parse** `securitySchemes` and per-operation `security`.
|
|
||||||
4. **Build** per-route security dependencies.
|
|
||||||
5. **Bind** every path/method → handler by `operationId`; a missing handler, a missing `operationId` on a declared operation, or an unbound operation raises at startup.
|
|
||||||
|
|
||||||
**Guarantees:**
|
|
||||||
|
|
||||||
- Every route has a spec declaration (no undocumented routes).
|
|
||||||
- Every spec operation has a handler (no unhandled operations).
|
|
||||||
- Auth enforcement is spec-driven, not hand-wired.
|
|
||||||
- `/openapi.json` + Swagger UI always reflect the provided spec.
|
|
||||||
|
|
||||||
Keyword arguments beyond `openapi_path` / `routes_module` pass straight through to `fastapi.FastAPI` (it's a subclass — `title`, `version`, middleware, lifespan, … all work).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 4. `binder` — Spec → Route Binding
|
|
||||||
|
|
||||||
`bind_routes(app, spec, routes_module, security_deps=None) -> None` (in `openapi_first/binder.py`).
|
|
||||||
|
|
||||||
This is the heart of the OpenAPI-first guarantee. For each `path` + HTTP method in the spec:
|
|
||||||
|
|
||||||
1. Reads the operation's `operationId`.
|
|
||||||
2. Looks up `routes_module.<operationId>` — a **plain callable**.
|
|
||||||
3. Registers a FastAPI `APIRoute` bound to that handler, injecting `Depends(...)` for any matching security requirements.
|
|
||||||
|
|
||||||
Failures are explicit and early:
|
|
||||||
|
|
||||||
| Condition | Raised |
|
|
||||||
|---------------------------------------|---------------------------------------|
|
|
||||||
| No `operationId` on an operation | `MissingOperationHandler` |
|
|
||||||
| `operationId` has no handler function | `MissingOperationHandler` |
|
|
||||||
| A path/method declared but unbound | `MissingOperationHandler` |
|
|
||||||
|
|
||||||
Handlers stay framework-agnostic: they're plain functions `(payload, id, response)` named after `operationId`s — no decorators, no routing metadata.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📡 5. `client` — The Other Side of the Contract
|
|
||||||
|
|
||||||
`OpenAPIClient(spec, base_url=None, client=None)` (in `openapi_first/client.py`).
|
|
||||||
|
|
||||||
The same spec that builds the server builds its client — one callable per `operationId`, keyed by name:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from openapi_first.loader import load_openapi
|
|
||||||
from openapi_first.client import OpenAPIClient
|
|
||||||
|
|
||||||
spec = load_openapi("openapi.yaml")
|
|
||||||
client = OpenAPIClient(spec)
|
|
||||||
|
|
||||||
response = client.get_health()
|
|
||||||
response = client.get_user(path_params={"user_id": 1})
|
|
||||||
response = client.create_user(body={"name": "Ada"})
|
|
||||||
```
|
|
||||||
|
|
||||||
How operations become methods:
|
|
||||||
|
|
||||||
- Each `operationId` → a dynamically-built callable on the client.
|
|
||||||
- **Path parameters** → `path_params={"user_id": 1}`.
|
|
||||||
- **Request body** → `body={...}` (JSON) or raw content for non-JSON media types.
|
|
||||||
- **Query/headers** → `query=` / `headers=`.
|
|
||||||
- Returns the raw `httpx.Response` (no implicit deserialization, no hidden schema inference).
|
|
||||||
|
|
||||||
Client-construction guarantees (fail-fast, same philosophy as the app):
|
|
||||||
|
|
||||||
- Spec must declare at least one `servers` entry (`base_url` falls back to it).
|
|
||||||
- Spec must have `paths`.
|
|
||||||
- Every operation must have a **unique** `operationId`.
|
|
||||||
- Required params must match the spec at call time.
|
|
||||||
|
|
||||||
Construction errors raise `OpenAPIClientError`; request-time errors surface as `httpx` exceptions with `operationId` context.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💥 6. `errors` — Explicit Error Hierarchy
|
|
||||||
|
|
||||||
Everything raised by `openapi-first` derives from `OpenAPIFirstError` (in `openapi_first/errors.py`), so callers can handle first-party failures with a single `except`:
|
|
||||||
|
|
||||||
- `OpenAPIFirstError` — base (comparable / aims to be picklable share).
|
|
||||||
- `OpenAPIClientError` — client-side contract violations.
|
|
||||||
- `MissingOperationHandler` — spec declares an operation whose handler is missing or unresolvable; carries `path`, `method`, and optional `operationId`.
|
|
||||||
|
|
||||||
See [Error Handling](06_error_handling.md) for the full table.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛡️ 7. `security` — Auth from the Spec
|
|
||||||
|
|
||||||
`security.py` turns an OpenAPI `securitySchemes` section into FastAPI `Depends(...)` objects — **no manual middleware**.
|
|
||||||
|
|
||||||
- `parse_security_schemes(spec) -> dict[str, dict]` — collects schemes, resolving `{ENV_VAR}` placeholders.
|
|
||||||
- `make_security_dependencies(spec, security_schemes) -> dict[str, list[Depends]]` — builds `METHOD:/path` → dependency list from per-operation `security`, falling back to top-level `security`.
|
|
||||||
|
|
||||||
Supported scheme types (extensible):
|
|
||||||
|
|
||||||
- `type: http, scheme: bearer` — `OpenAPIFirstSecurityDependency(HTTPBearer)`. With `x-introspect-path`/`x-server-url`, it introspects the JWT against an auth service; optionally sets `request.state.user`. Without introspection, it validates token presence and stores it on `request.state.token`.
|
|
||||||
- API-key style schemes via the same resolution path.
|
|
||||||
|
|
||||||
Key properties: resolution is **per-operation**, env placeholders resolve once at startup, and dependencies are injected by `binder` — your handlers never mention security.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏭 8. `codegen` & `cli` — Build-Time Tooling
|
|
||||||
|
|
||||||
Generation is strictly **build-time** — spec → code, run once by a developer, committed:
|
|
||||||
|
|
||||||
### `openapi-first models <spec> -o <file>`
|
|
||||||
|
|
||||||
Generates Pydantic models from spec schemas (wraps `datamodel_code_generator`):
|
|
||||||
|
|
||||||
```python
|
|
||||||
# cli path
|
|
||||||
openapi-first models openapi.yaml -o app/models.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### `openapi-first routes <spec> -o <dir> [--use-models] [--models-module models]`
|
|
||||||
|
|
||||||
Generates one `routes_<resource>.py` stub file per resource, with `NotImplementedError` handlers bound to `operationId`s (optionally importing your generated models):
|
|
||||||
|
|
||||||
```python
|
|
||||||
openapi-first routes openapi.yaml -o app/routes --use-models
|
|
||||||
```
|
|
||||||
|
|
||||||
### `openapi-first scaffold <template> [path]`
|
|
||||||
|
|
||||||
Copies a bundled application template verbatim (see [Templates](03_use_cases/02_templates.md)).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧱 9. `templates` — Copyable Applications
|
|
||||||
|
|
||||||
Four bundled, runnable applications live under `openapi_first/templates/`: `health_app`, `crud_app`, `model_app`, and `vet_app`. They are **not** part of the library API — no lint/format/type gates, never imported at runtime. They exist to be copied via:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first scaffold <template> [target-dir]
|
|
||||||
openapi-first scaffold --list
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Related
|
|
||||||
|
|
||||||
- [01 – Overview](01_overview.md) · [02 – Templates](03_use_cases/02_templates.md) · [03 – Security](05_security.md) · [04 – Design](04_design.md)
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
# Use Case 1: Quickstart — Build Your First OpenAPI-First Service
|
|
||||||
|
|
||||||
This guide walks you from an empty directory to a running, contract-driven service in a few minutes, then talks to it with the generated client.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ 1. Prerequisites
|
|
||||||
|
|
||||||
- Python 3.10+
|
|
||||||
- `openapi-first` installed (see [Overview](../01_overview.md#installation))
|
|
||||||
- `pip install "fastapi[standard]"` (or `uvicorn`) to run the app
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 2. Write the OpenAPI document
|
|
||||||
|
|
||||||
OpenAPI comes first. Create `openapi.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
openapi: 3.0.3
|
|
||||||
info:
|
|
||||||
title: Greeting Service
|
|
||||||
version: 1.0.0
|
|
||||||
servers:
|
|
||||||
- url: http://localhost:8000
|
|
||||||
paths:
|
|
||||||
/greet/{name}:
|
|
||||||
get:
|
|
||||||
operationId: get_greeting
|
|
||||||
parameters:
|
|
||||||
- name: name
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: A greeting
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
greeting:
|
|
||||||
type: string
|
|
||||||
```
|
|
||||||
|
|
||||||
Key points: every operation needs `operationId`, and every route must exist *only* here.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧑💻 3. Write the handlers
|
|
||||||
|
|
||||||
Create `routes.py` — plain functions, no decorators, named exactly like the `operationId`s:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# routes.py
|
|
||||||
def get_greeting(name: str) -> dict:
|
|
||||||
"""Return a greeting for the given name."""
|
|
||||||
return {"greeting": f"Hello, {name}!"}
|
|
||||||
```
|
|
||||||
|
|
||||||
If a handler is missing at startup, the app **refuses to boot** (`MissingOperationHandler`) — the fail-fast guarantee catches contract drift immediately.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 4. Bootstrap the app
|
|
||||||
|
|
||||||
Create `main.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# main.py
|
|
||||||
from openapi_first.app import OpenAPIFirstApp
|
|
||||||
import routes
|
|
||||||
|
|
||||||
app = OpenAPIFirstApp(
|
|
||||||
openapi_path="openapi.yaml",
|
|
||||||
routes_module=routes,
|
|
||||||
title="Greeting Service",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
Run it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
uvicorn main:app --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
Visit `http://localhost:8000/docs` (Swagger UI) and `http://localhost:8000/openapi.json` — both are generated from *your* spec.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📡 5. Call it with the client
|
|
||||||
|
|
||||||
The same spec builds a strict client:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# client.py
|
|
||||||
from openapi_first.loader import load_openapi
|
|
||||||
from openapi_first.client import OpenAPIClient
|
|
||||||
|
|
||||||
spec = load_openapi("openapi.yaml")
|
|
||||||
client = OpenAPIClient(spec)
|
|
||||||
|
|
||||||
response = client.get_greeting(path_params={"name": "Ada"})
|
|
||||||
print(response.status_code) # 200
|
|
||||||
print(response.json()) # {"greeting": "Hello, Ada!"}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 6. Next Steps
|
|
||||||
|
|
||||||
- Copy a fuller example: [02 – Templates](02_templates.md)
|
|
||||||
- Generate models/routes from a bigger spec: [04 – Codegen](04_codegen.md)
|
|
||||||
- Drive everything from a client: [03 – Client](03_client.md)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [01 – Overview](../01_overview.md) · [02 – Components](../02_components.md) · [01 – Quickstart](01_quickstart.md)
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
# Use Case 2: Templates — Copyable Reference Applications
|
|
||||||
|
|
||||||
`openapi-first` ships four runnable, copyable applications under `openapi_first/templates/`. They are **not part of the library API** — they are bundled scaffold examples you copy into your own project and build on.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎬 1. What Templates Are
|
|
||||||
|
|
||||||
A template is a complete, self-contained OpenAPI-first service:
|
|
||||||
|
|
||||||
- A bundled directory inside `openapi_first/templates/<name>/`
|
|
||||||
- Copyable **verbatim** — no code generation, no mutation — via the CLI
|
|
||||||
- Each one demonstrates a specific set of OpenAPI-first behaviors and FastAPI features
|
|
||||||
|
|
||||||
All templates share the same skeleton:
|
|
||||||
|
|
||||||
```
|
|
||||||
<name>_app/
|
|
||||||
├── __init__.py # explains the template + how to scaffold it
|
|
||||||
├── openapi.yaml # the contract (source of truth)
|
|
||||||
├── main.py # assembles OpenAPIFirstApp from the spec
|
|
||||||
├── routes.py # operationId-bound handler functions
|
|
||||||
└── data.py # in-memory data store (demo only)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 2. The Four Templates
|
|
||||||
|
|
||||||
### 2.1 `health_app` — minimal liveness probe
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first scaffold health_app
|
|
||||||
# or into a custom directory:
|
|
||||||
openapi-first scaffold health_app my-health-service
|
|
||||||
```
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `openapi.yaml` | `GET /health` → `operationId: get_health` |
|
|
||||||
| `routes.py` | `get_health()` returns `{"status": "ok"}` |
|
|
||||||
| `main.py` | `OpenAPIFirstApp(openapi_path="openapi.yaml", routes_module=routes)` |
|
|
||||||
|
|
||||||
**Why it exists:** the absolute minimal OpenAPI-first round trip — one operation, one handler, zero moving parts. The best starting point to internalize the mental model.
|
|
||||||
|
|
||||||
**Smoke test:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -e .
|
|
||||||
uvicorn main:app
|
|
||||||
curl http://localhost:8000/health
|
|
||||||
# → {"status": "ok"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 `crud_app` — dict-based CRUD
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first scaffold crud_app my-crud-service
|
|
||||||
```
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `openapi.yaml` | Full CRUD over `/items` (list/get/create/update/delete) |
|
|
||||||
| `routes.py` | Handlers bound via `operationId`s `list_items`, `get_item`, `create_item`, `update_item`, `delete_item` |
|
|
||||||
| `data.py` | In-memory dict store with auto-incrementing `id` |
|
|
||||||
|
|
||||||
Behaviors you learn:
|
|
||||||
|
|
||||||
- **Explicit status codes** — `create_item`/`delete_item` take `response: Response` and set `201`/`204`; `get_item`/`update_item` raise `HTTPException(404)` on `KeyError`
|
|
||||||
- **Handlers as plain callables** — no FastAPI decorators, routing comes solely from the spec
|
|
||||||
- **Mock data store** — `data.py` is a copyable in-memory store, explicitly **not** production-ready
|
|
||||||
|
|
||||||
### 2.3 `model_app` — Pydantic model CRUD
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first scaffold model_app my-model-service
|
|
||||||
```
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `openapi.yaml` | Same CRUD surface, schemas reference models |
|
|
||||||
| `models.py` | Pydantic `Item`, `ItemCreate`, `ItemBase` (request/response models) |
|
|
||||||
| `routes.py` | Handlers type-annotated with the models; `create_item` sets `201` |
|
|
||||||
| `data.py` | In-memory store returning real model instances |
|
|
||||||
|
|
||||||
Behaviors you learn:
|
|
||||||
|
|
||||||
- **Pydantic request/response models** — payloads validated and serialized via FastAPI
|
|
||||||
- **Same handler contracts** — identical `operationId` set as `crud_app`, so the two are interchangeable
|
|
||||||
- **Models in the client too** — the same spec drives `OpenAPIClient` body handling
|
|
||||||
|
|
||||||
### 2.4 `vet_app` — the full-featured demo
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first scaffold vet_app my-vet-clinic
|
|
||||||
```
|
|
||||||
|
|
||||||
| File | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `openapi.yaml` | Five resources (parents, vets, treatments, pets, appointments) + SSE + upload + discriminated unions |
|
|
||||||
| `models.py` | Pydantic models incl. **discriminated unions** (`noteType` literal fields) |
|
|
||||||
| `routes.py` | ~20 handlers across all resources, incl. pagination, filtering, photo upload, SSE streaming |
|
|
||||||
| `sse.py` | Server-Sent Events helper (`StreamingResponse`, per-pet subscriber queues, background `asyncio` workers) |
|
|
||||||
| `data.py` | Larger in-memory store (parents → vets → treatments → pets → appointments) |
|
|
||||||
| `main.py` | App + CORS + lifespan example |
|
|
||||||
|
|
||||||
Behaviors you learn — the *advanced* tier:
|
|
||||||
|
|
||||||
- **Discriminated unions** — `ProcedureNotes` uses `oneOf` + `discriminator.noteType` mapping; Pydantic models use `Literal[...]` discriminator fields
|
|
||||||
- **SSE streaming** — a `GET /pets/{id}/actions` operation streaming `text/event-stream` via `StreamingResponse` with background task workers
|
|
||||||
- **File upload** — `UploadFile` handler setting a multi-part body
|
|
||||||
- **CORS + middleware** — `add_middleware(CORSMiddleware, ...)` alongside the spec-driven setup
|
|
||||||
- **Response injection** — handlers set `201`/`204` explicitly via injected `Response`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 3. CLI Reference
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List available templates
|
|
||||||
openapi-first scaffold --list
|
|
||||||
|
|
||||||
# Copy a template into its default directory (template name, dashes)
|
|
||||||
openapi-first scaffold health_app
|
|
||||||
|
|
||||||
# Copy into a custom target directory
|
|
||||||
openapi-first scaffold crud_app my-project/crud
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Protip:** `DEFAULT_TEMPLATE` is `health_app`, so `openapi-first scaffold` with no template name scaffolds the health app.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧩 4. Anatomy of a Scaffolded Service
|
|
||||||
|
|
||||||
After `openapi-first scaffold health_app my-health-service`, your directory contains a drop-in FastAPI service:
|
|
||||||
|
|
||||||
```
|
|
||||||
my-health-service/
|
|
||||||
├── openapi.yaml # THE contract
|
|
||||||
├── main.py # `app = OpenAPIFirstApp(openapi_path=..., routes_module=routes)`
|
|
||||||
└── routes.py # `def get_health(): ...`
|
|
||||||
```
|
|
||||||
|
|
||||||
Run it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd my-health-service
|
|
||||||
pip install -e .
|
|
||||||
uvicorn main:app --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
`/docs`, `/openapi.json`, and every declared route now exist — all derived from `openapi.yaml`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ 5. Production Disclaimer
|
|
||||||
|
|
||||||
Templates use **in-memory, non-persistent, non-concurrency-safe** data stores. They are learning scaffolds — **not** production references. Swap in a real data layer (SQL/REDIS/object store) the moment you go beyond a demo.
|
|
||||||
|
|
||||||
See the `__init__.py` of each template for detailed client examples, CLI examples, and design notes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [01 – Quickstart](01_quickstart.md) · [04 – Codegen](04_codegen.md) · [02 – Components](../02_components.md)
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
# Use Case 3: The OperationId-Driven Client
|
|
||||||
|
|
||||||
`OpenAPIClient` is the other side of the contract. It reads the *same* OpenAPI document the server runs on and exposes one callable per `operationId` — so "client" and "server" are two views of one truth.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔍 1. Before You Start
|
|
||||||
|
|
||||||
The client is `httpx`-based and returns raw `httpx.Response` objects — no magic deserialization, no hidden schema inference:
|
|
||||||
|
|
||||||
- **No** response Pydantic models
|
|
||||||
- **No** implicit URL construction (path params are explicit)
|
|
||||||
- **No** hand-written `requests.get(...)` scattered through your code
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧬 2. Constructing the Client
|
|
||||||
|
|
||||||
```python
|
|
||||||
from openapi_first.loader import load_openapi
|
|
||||||
from openapi_first.client import OpenAPIClient
|
|
||||||
|
|
||||||
spec = load_openapi("openapi.yaml")
|
|
||||||
client = OpenAPIClient(spec)
|
|
||||||
```
|
|
||||||
|
|
||||||
The base URL comes from the spec's `servers` list (first entry) unless you pass `base_url` explicitly:
|
|
||||||
|
|
||||||
```python
|
|
||||||
client = OpenAPIClient(spec, base_url="https://api.internal.myco/v1")
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also hand over a preconfigured `httpx.Client` (custom transport, TLS, retries):
|
|
||||||
|
|
||||||
```python
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
transport = httpx.HTTPTransport(retries=3)
|
|
||||||
client = OpenAPIClient(
|
|
||||||
spec,
|
|
||||||
client=httpx.Client(transport=transport),
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💥 3. Fail-Fast at Construction
|
|
||||||
|
|
||||||
`OpenAPIClient(...)` **raises immediately** if the contract is broken — you find out at startup, not on the first request:
|
|
||||||
|
|
||||||
| Violation | Error |
|
|
||||||
|----------------------------------------|-----------------------------------|
|
|
||||||
| Spec has no `servers` entry | `OpenAPIClientError` |
|
|
||||||
| Spec has no `paths` | `OpenAPIClientError` |
|
|
||||||
| Operation missing `operationId` | `OpenAPIClientError` |
|
|
||||||
| Duplicate `operationId` | `OpenAPIClientError` |
|
|
||||||
| Operation references unknown parameters| `OpenAPIClientError` |
|
|
||||||
|
|
||||||
Missing *required parameters* fail at **call time** — pydoclint-grade strictness on the wire.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📞 4. Calling Operations
|
|
||||||
|
|
||||||
Every `operationId` becomes a method. The call signature is uniform across the whole client:
|
|
||||||
|
|
||||||
```python
|
|
||||||
response = client.<operationId>(
|
|
||||||
*,
|
|
||||||
path_params: dict | None = None,
|
|
||||||
query: dict | None = None,
|
|
||||||
headers: dict | None = None,
|
|
||||||
body: Any | None = None,
|
|
||||||
timeout: float | None = None,
|
|
||||||
) -> httpx.Response
|
|
||||||
```
|
|
||||||
|
|
||||||
Concrete examples (from the `crud_app` / `health_app` templates):
|
|
||||||
|
|
||||||
```python
|
|
||||||
# No parameters — simplest
|
|
||||||
response = client.get_health()
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
# Path parameter
|
|
||||||
response = client.get_item(path_params={"item_id": 3})
|
|
||||||
|
|
||||||
# Query parameters
|
|
||||||
response = client.list_items(query={"limit": 10, "offset": 20})
|
|
||||||
|
|
||||||
# JSON request body
|
|
||||||
response = client.create_item(body={"name": "Orange", "price": 0.8})
|
|
||||||
|
|
||||||
# Custom headers / timeout
|
|
||||||
response = client.get_user(
|
|
||||||
path_params={"user_id": 1},
|
|
||||||
headers={"X-Internal-Key": "..."},
|
|
||||||
timeout=30,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Returns:** the raw `httpx.Response`, so `status_code`, `.json()`, `.headers` are all yours to inspect.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧠 5. How Parameters Are Bound
|
|
||||||
|
|
||||||
For each operation the client knows exactly *where* each parameter belongs:
|
|
||||||
|
|
||||||
| OpenAPI location | Client kwarg |
|
|
||||||
|------------------|-------------------|
|
|
||||||
| `in: path` | `path_params[name]` |
|
|
||||||
| `in: query` | `query[name]` |
|
|
||||||
| `in: header` | `headers[name]` |
|
|
||||||
| `requestBody` | `body` |
|
|
||||||
|
|
||||||
JSON media types are sent as `json=`; other media types as raw `content=`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 6. Server ↔ Client, One Spec
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# One directory, two processes
|
|
||||||
uvicorn main:app --port 8000 # server
|
|
||||||
python -c "import asyncio; from client_script import run; asyncio.run(run())" # client
|
|
||||||
```
|
|
||||||
|
|
||||||
Or the same client against a remote environment:
|
|
||||||
|
|
||||||
```python
|
|
||||||
client = OpenAPIClient(
|
|
||||||
spec,
|
|
||||||
base_url="https://staging.internal.myco",
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
The URL is the *only* thing that changes between environments — the contract never does.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✍️ 7. OperationId as the API
|
|
||||||
|
|
||||||
Because the client is operationId-driven:
|
|
||||||
|
|
||||||
- Adding an operation = adding a method (and vice versa)
|
|
||||||
- No operation can be "forgotten" by the client — it's constructed from the spec
|
|
||||||
- `operationId` is the one name you remember; the HTTP verb/path is an implementation detail
|
|
||||||
|
|
||||||
If an `operationId` you call doesn't exist, you get an `AttributeError` at construction-scan time — never a silent 404.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [01 – Overview](../01_overview.md) · [01 – Quickstart](01_quickstart.md) · [02 – Templates](02_templates.md)
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
# Codegen — Generate Models & Routes From Your Spec
|
|
||||||
|
|
||||||
> **TL;DR** — point `openapi-first models` / `openapi-first routes` at your spec and get Pydantic models plus operation-matching route stubs. The output is deterministic and is a *starting point* — not a maintained artifact.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧭 1. Why Celebrate Codegen?
|
|
||||||
|
|
||||||
Two pain points kill OpenAPI projects:
|
|
||||||
|
|
||||||
1. **The "write the spec, then write the same thing as Pydantic models" step** — exactly where server/client param types drift (your route says `item_id: int`, your client sends a string…).
|
|
||||||
2. **Typing handler names by hand** when `bind_routes` requires each `operationId` to resolve to a function of the exact same name.
|
|
||||||
|
|
||||||
Codegen collapses both. One command, one source file, the same shapes everywhere.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 2. Model Generation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first models openapi.yaml -o app/_gen/models.py
|
|
||||||
```
|
|
||||||
|
|
||||||
The generated module mirrors the spec's `components.schemas` **by name**:
|
|
||||||
|
|
||||||
| Spec | Generated |
|
|
||||||
|------|-----------|
|
|
||||||
| `components.schemas.User` | `class User(BaseModel)` |
|
|
||||||
| `components.schemas.Item` | `class Item(BaseModel)` |
|
|
||||||
| `required + type` from schema | Pydantic `Field(...)` / type hints ✓ |
|
|
||||||
|
|
||||||
**Never hand-edit those files.** If the spec changes, regenerate — just like re-running a compiler after editing source.
|
|
||||||
|
|
||||||
### Why constructor-time validation still applies
|
|
||||||
|
|
||||||
Codegen doesn't change the design: the generated models are plain Pydantic, and the **client/server still validate against the spec** at startup. Codegen is a *convenience accelerator* on top of the fail-fast guarantees in [04 – Design](../04_design.md) and [06 – Error Handling](../06_error_handling.md).
|
|
||||||
|
|
||||||
> **Wire model vs. domain model.** Generated models are the **wire contract**. Keep them separate from your persisted/domain model (see the standards *CRUD Template*): give the wire schema an `id: str`, but model the Mongo document so the id is optional.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔁 3. Route Generation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first routes openapi.yaml -o app/interfaces \
|
|
||||||
--use-models --models-module app._gen.models \
|
|
||||||
--inject-request --package
|
|
||||||
```
|
|
||||||
|
|
||||||
One file per resource (derived from the first path segment). Flags:
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `--use-models` | Import request-body `$ref` models from `--models-module` |
|
|
||||||
| `--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.
|
|
||||||
|
|
||||||
### Resource names and extra media types
|
|
||||||
|
|
||||||
- A path such as `/fetch-requests` yields a **sanitized** module name `fetch_requests.py` (a raw hyphen could never be imported).
|
|
||||||
- `multipart/form-data` and `application/x-www-form-urlencoded` bodies expand to one parameter per schema property: `UploadFile = File(...)` for `format: binary`, `Form(...)` otherwise — not an opaque `payload: dict`. Fields are required only when listed in the schema's `required`.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 4. Idempotent Output
|
|
||||||
|
|
||||||
Generated output is deterministic w.r.t. the spec:
|
|
||||||
- Same spec → byte-identical files (unless you hand-edit — you won't)
|
|
||||||
- Ordering follows spec declaration order
|
|
||||||
- No timestamps, no machine names, no hidden randomness
|
|
||||||
|
|
||||||
That determinism is what lets you `git diff` after a spec change and see **exactly** what moved.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧩 Related
|
|
||||||
|
|
||||||
- [01 – Overview](../01_overview.md) · [02 – Templates](02_templates.md) · [03 – Client](03_client.md) · [04 – Design](../04_design.md) · [07 – Testing](../07_testing.md)
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
# Design — Guarantees, Startup Pipeline, and the Contract Model
|
|
||||||
|
|
||||||
This page is the architecture reference: the fail-fast startup surface, the guarantees that hold by construction, and the trade-offs baked into every decision.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ 1. The Startup Pipeline
|
|
||||||
|
|
||||||
Everything happens **once, eagerly, at construction time** — for the app at `OpenAPIFirstApp(...)`, for the client at `OpenAPIClient(...)`. There is no lazy loading, no deferred binding, no "it'll work on first request".
|
|
||||||
|
|
||||||
```text
|
|
||||||
openapi.yaml ──► loader.load_openapi
|
|
||||||
├─ parse (json/yaml by extension)
|
|
||||||
├─ validate (strict OpenAPI 3.x validator)
|
|
||||||
└─► dict ──▶ OpenAPISpecLoadError
|
|
||||||
│
|
|
||||||
OpenAPIFirstApp(openapi_path=..., routes_module=routes)
|
|
||||||
├─ 1. load + validate spec (loader)
|
|
||||||
├─ 2. parse securitySchemes (security)
|
|
||||||
├─ 3. make_security_dependencies (security)
|
|
||||||
├─ 4. bind_routes: every operationId ──► routes.<operationId>
|
|
||||||
│ └─ missing op / missing handler ──► MissingOperationHandler
|
|
||||||
└─► FastAPI app: routes registry + /openapi.json + Swagger UI
|
|
||||||
```
|
|
||||||
|
|
||||||
Client — same spec, same guarantees:
|
|
||||||
|
|
||||||
```text
|
|
||||||
OpenAPIClient(spec)
|
|
||||||
├─ require servers[]
|
|
||||||
├─ require paths
|
|
||||||
├─ one callable per operationId (client.<operationId>)
|
|
||||||
│ └─ missing / duplicate opId ──► OpenAPIClientError
|
|
||||||
└─► ready
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📜 2. Guarantees That Hold by Construction
|
|
||||||
|
|
||||||
These are not conventions — they are enforced at startup or client construction:
|
|
||||||
|
|
||||||
1. **Every route is spec-declared.** Routes are registered *only* from `paths`; there is no decorator-driven or implicit routing.
|
|
||||||
2. **Every operation is handled.** Each `operationId` resolves to an exact handler name in `routes_module`; a missing one aborts bootstrap.
|
|
||||||
3. **Every operation has an `operationId`.** An operation without one cannot be bound and fails fast.
|
|
||||||
4. **Client and server use the same spec.** One document drives both sides, so they cannot drift.
|
|
||||||
5. **Auth is spec-driven.** `security` dependencies come from `securitySchemes` + per-operation `security`; no manual middleware.
|
|
||||||
6. **Spec is valid before use.** Invalid, malformed, or unloadable specs are rejected at load, not at first request.
|
|
||||||
|
|
||||||
### The startup "no half-states" property
|
|
||||||
|
|
||||||
Because binding, validation, and security resolution all run at construction, a booted app is a **provably-complete app**. If the contract is violated in any way, the process refuses to start — the failure is loud, immediate, and tells you exactly what to fix.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧩 3. Component Responsibilities
|
|
||||||
|
|
||||||
| Module | Owns | Refuses to |
|
|
||||||
|--------|------|------------|
|
|
||||||
| `loader` | parse + validate the spec | modify or "fix" the spec |
|
|
||||||
| `app` | assemble FastAPI from spec + handlers | routing decisions, decorators |
|
|
||||||
| `binder` | `operationId` → handler mapping | infer handlers from paths |
|
|
||||||
| `client` | build one callable per `operationId` | guess URLs, deserialize implicitly |
|
|
||||||
| `security` | schemes → FastAPI `Depends` | manual middleware |
|
|
||||||
| `errors` | the error hierarchy | swallow failures |
|
|
||||||
| `codegen` | build-time model/routes generation | runtime codegen |
|
|
||||||
| `templates` | copyable scaffolds | production data stores |
|
|
||||||
|
|
||||||
**Rule of thumb for contributors:** keep modules coercive (they raise if the contract is wrong) and narrow (one responsibility each). Pydoclint + the test suite keep that contract honest.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚖️ 4. Design Trade-offs (Accepted)
|
|
||||||
|
|
||||||
| Decision | Chosen because | What you give up |
|
|
||||||
|----------|----------------|------------------|
|
|
||||||
| Handlers are plain callables | Framework-agnostic, testable, grep-able | No decorator sugar |
|
|
||||||
| Fail-fast at startup | Drift caught in CI, not at 3am | Slightly heavier boot |
|
|
||||||
| Raw `httpx.Response` from client | No hidden deserialization/validation | You read `.json()` yourself |
|
|
||||||
| Build-time codegen | One-way generation, no runtime generator dep | Spec must already be spec-valid |
|
|
||||||
| Templates copy verbatim | Scaffold, not magic | Templates never updated in place |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Related
|
|
||||||
|
|
||||||
- [01 – Overview](01_overview.md) · [02 – Components](02_components.md) · [05 – Security](05_security.md) · [06 – Error Handling](06_error_handling.md) · [07 – Testing](07_testing.md)
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
# Security — Auth Driven by the Spec, Not by Hand
|
|
||||||
|
|
||||||
The most distinctive thing about `openapi-first` security is that you **never write middleware or `Depends(authenticate)` calls yourself**. All authentication is *declared* in the OpenAPI document and *enforced* automatically.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗺️ 1. Where Security Lives
|
|
||||||
|
|
||||||
Two places in the spec, both respected:
|
|
||||||
|
|
||||||
### 1.1 `components.securitySchemes` — the inventory
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
components:
|
|
||||||
securitySchemes:
|
|
||||||
internalBearer:
|
|
||||||
type: http
|
|
||||||
scheme: bearer
|
|
||||||
bearerFormat: JWT
|
|
||||||
x-server-url: "{AUTH_SERVER_URL}"
|
|
||||||
x-introspect-path: "/introspect"
|
|
||||||
```
|
|
||||||
|
|
||||||
`x-server-url` and `x-introspect-path` are library extensions that point at a JWT introspection endpoint (see [Introspection](#-5-introspection)).
|
|
||||||
|
|
||||||
### 1.2 `security` — per-operation (or global) requirements
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
security:
|
|
||||||
- internalBearer: [] # applied to every operation by default
|
|
||||||
|
|
||||||
paths:
|
|
||||||
/pets:
|
|
||||||
get:
|
|
||||||
operationId: list_pets
|
|
||||||
# inherits: security: [{internalBearer: []}]
|
|
||||||
post:
|
|
||||||
operationId: create_pet
|
|
||||||
security: [] # override — public endpoint
|
|
||||||
```
|
|
||||||
|
|
||||||
OpenAPI dynamic-scoping rules apply: an operation-level `security` **replaces** the global list (it does not merge).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📖 2. Reading the Spec
|
|
||||||
|
|
||||||
`security.py` exposes two functions:
|
|
||||||
|
|
||||||
| Function | Returns | Purpose |
|
|
||||||
|----------|---------|---------|
|
|
||||||
| `parse_security_schemes(spec)` | `dict[str, dict]` | Collect schemes, resolve `{ENV_VAR}` placeholders |
|
|
||||||
| `make_security_dependencies(spec, schemes)` | `dict[str, list[Depends]]` (keyed `METHOD:/path`) | Effective per-operation security deps |
|
|
||||||
|
|
||||||
Env placeholders of the form `{NAME}` are resolved **once at startup** from `os.environ`. This is how you avoid embedding credentials or auth-service URLs in the committed spec.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 3. The Bearer Dependency
|
|
||||||
|
|
||||||
`make_security_dependencies` builds a FastAPI dependency for `type: http, scheme: bearer`.
|
|
||||||
|
|
||||||
Two modes, decided by whether an introspection endpoint is configured:
|
|
||||||
|
|
||||||
### 3.1 With introspection (`x-introspect-path`)
|
|
||||||
|
|
||||||
- Reads `Authorization: Bearer <token>`
|
|
||||||
- POSTs `{"token": "<token>"}` to `{x-server-url}{x-introspect-path}` synchronously via the bundled httpx client
|
|
||||||
- Expects a response with `active: true`
|
|
||||||
- On valid: `{"user": ...}` from the introspection body → `request.state.user`
|
|
||||||
- On failure: `401` (missing/invalid token) or `503` (auth service unreachable)
|
|
||||||
|
|
||||||
### 3.2 Without introspection
|
|
||||||
|
|
||||||
- Validates only that a Bearer token is present
|
|
||||||
- Stores it on `request.state.token`; no remote call
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 4. Wiring It Together
|
|
||||||
|
|
||||||
```python
|
|
||||||
# server-side
|
|
||||||
from openapi_first.loader import load_openapi
|
|
||||||
from openapi_first.security import (
|
|
||||||
parse_security_schemes,
|
|
||||||
make_security_dependencies,
|
|
||||||
)
|
|
||||||
from openapi_first.app import OpenAPIFirstApp
|
|
||||||
import routes
|
|
||||||
|
|
||||||
spec = load_openapi("openapi.yaml")
|
|
||||||
schemes = parse_security_schemes(spec)
|
|
||||||
security_deps = make_security_dependencies(spec, schemes)
|
|
||||||
|
|
||||||
app = OpenAPIFirstApp(
|
|
||||||
openapi_path="openapi.yaml",
|
|
||||||
routes_module=routes,
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
`OpenAPIFirstApp` already does this internally — the snippet above shows what it encapsulates (and what you use directly if you assemble the pieces by hand).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 5. Testing Security
|
|
||||||
|
|
||||||
Because handlers are plain callables, security is the *one* place FastAPI's `TestClient` earns its keep:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
def test_unauthenticated_is_401(app, overrides):
|
|
||||||
with TestClient(app) as client:
|
|
||||||
r = client.get("/pets")
|
|
||||||
assert r.status_code == 401
|
|
||||||
|
|
||||||
def test_invalid_token_using_fake_introspector(app):
|
|
||||||
# Point x-introspect-path at a stub uvicorn/TestServer returning active:false
|
|
||||||
with TestClient(app) as client:
|
|
||||||
r = client.get("/pets", headers={"Authorization": "Bearer nope"})
|
|
||||||
assert r.status_code == 401
|
|
||||||
```
|
|
||||||
|
|
||||||
See [Testing](07_testing.md) for the full recipe, including how tests stub the introspection server.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛡️ 7. Common Patterns
|
|
||||||
|
|
||||||
| Pattern | How |
|
|
||||||
|---------|-----|
|
|
||||||
| Public endpoint | `security: []` on the operation |
|
|
||||||
| Whole-spec auth | top-level `security:` (applies to all) |
|
|
||||||
| Route-specific scheme | replace `security` on that operation |
|
|
||||||
| Env-driven auth URL | `x-server-url: "{AUTH_SERVER_URL}"` |
|
|
||||||
| Offline token carry | scheme without `x-introspect-path` → `request.state.token` |
|
|
||||||
| Auth on the client side | pass the token via `client.<operationId>(headers={"Authorization": ...})` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [02 – Components](02_components.md) · [04 – Design](04_design.md) · [06 – Error Handling](06_error_handling.md) · [07 – Testing](07_testing.md)
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
# Error Handling — Fail Loud, Fail Early
|
|
||||||
|
|
||||||
`openapi-first` treats errors as **first-class contract documents**: every failure mode is a named exception with a stable import pathhare, and every one surfaces as early as possible.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧬 1. The Hierarchy
|
|
||||||
|
|
||||||
All errors derive from `OpenAPIFirstError` (in `openapi_first/errors.py`), so a single `except OpenAPIFirstError` catches every first-party failure:
|
|
||||||
|
|
||||||
```text
|
|
||||||
OpenAPIFirstError
|
|
||||||
├── OpenAPISpecError # spec-level problems
|
|
||||||
│ └── OpenAPISpecLoadError # load / parse / validation (loader)
|
|
||||||
├── OpenAPIClientError # client-side contract issues (client)
|
|
||||||
└── MissingOperationHandler # spec op with no handler (binder)
|
|
||||||
|
|
||||||
# Security / loader layers raise through OpenAPISpecError subclasses too
|
|
||||||
```
|
|
||||||
|
|
||||||
| Exception | Module | Raised when |
|
|
||||||
|----------------------------------|-----------|-------------|
|
|
||||||
| `OpenAPISpecLoadError` | `loader` | Path missing, file unreadable, YAML/JSON invalid, or spec fails OpenAPI 3.x validation |
|
|
||||||
| `OpenAPIClientError` | `client` | No `servers`, no `paths`, missing/duplicate `operationId`, missing required params at construction |
|
|
||||||
| `MissingOperationHandler` | `errors` | An operation is declared whose `operationId` has no matching handler in `routes_module` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⏱️ 2. When Things Fail
|
|
||||||
|
|
||||||
The single most important rule: **violations are eager, not lazy.**
|
|
||||||
|
|
||||||
### 2.1 At application startup
|
|
||||||
|
|
||||||
```python
|
|
||||||
# openapi.yaml missing ────────────────────────────► OpenAPISpecLoadError
|
|
||||||
# operationId without a handler ───────────────────► MissingOperationHandler
|
|
||||||
# operation with no operationId declared ──────────► MissingOperationHandler
|
|
||||||
```
|
|
||||||
|
|
||||||
Because these raise during `OpenAPIFirstApp(...)` construction, CI catches them the moment a spec and its routes drift — before a single request is served.
|
|
||||||
|
|
||||||
### 2.2 At client construction
|
|
||||||
|
|
||||||
```python
|
|
||||||
OpenAPIClient(spec) # fails fast, same philosophy
|
|
||||||
```
|
|
||||||
|
|
||||||
- Spec with no `servers` → `OpenAPIClientError`
|
|
||||||
- Spec with no `paths` → `OpenAPIClientError`
|
|
||||||
- Duplicate `operationId`s → `OpenAPIClientError` (client methods must be unambiguous)
|
|
||||||
- Operation missing `operationId` → `OpenAPIClientError`
|
|
||||||
|
|
||||||
### 2.3 At call time (client)
|
|
||||||
|
|
||||||
Runtime transport errors surface as `httpx` exceptions (`httpx.RequestError` family), not swallowed or remapped. Missing required args fail before any HTTP request is made:
|
|
||||||
|
|
||||||
```python
|
|
||||||
client.get_user(path_params={"user_id": ...}) # OK
|
|
||||||
client.get_user() # ValueError — user_id required
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧰 3. Handling in Your App
|
|
||||||
|
|
||||||
### 3.1 Server-side
|
|
||||||
|
|
||||||
Handlers raise FastAPI `HTTPException` for expected operation-level failures (404/422), and the `OperationId`-binding errors only exist at startup:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from fastapi import HTTPException
|
|
||||||
|
|
||||||
def get_item(item_id: int):
|
|
||||||
"""Retrieve an item by ID.
|
|
||||||
|
|
||||||
Implements the OpenAPI operation ``get_item``.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
item_id (int): Identifier of the item.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
HTTPException: If the item does not exist (404).
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return _get_item(item_id)
|
|
||||||
except KeyError:
|
|
||||||
raise HTTPException(status_code=404, detail="Item not found")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Client-side
|
|
||||||
|
|
||||||
```python
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = client.get_item(path_params={"item_id": 1})
|
|
||||||
except httpx.HTTPStatusError as exc:
|
|
||||||
... # 4xx/5xx from the server
|
|
||||||
```
|
|
||||||
|
|
||||||
`httpx.HTTPStatusError` isn't raised by the library — it's the standard `httpx.raise_for_status()` you can opt into per call. The library never masks a response code.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛡️ 4. Fail-Fast Guarantees Recap
|
|
||||||
|
|
||||||
| Layer | You write | The library guarantees |
|
|
||||||
|-------|-----------|------------------------|
|
|
||||||
| Loader | a spec path | unreadable/invalid specs never reach your app |
|
|
||||||
| Binder | handler functions | every operation must resolve, or the app won't start |
|
|
||||||
| Client | a spec | every operationId becomes a callable; missing ones fail at construction |
|
|
||||||
| Runtime | handler code | FastAPI + Pydantic handle coercion; contract checks already happened at startup |
|
|
||||||
|
|
||||||
None of these can silently degrade: a violation is an **exception at construction**, not a 500 at request time.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [01 – Overview](01_overview.md) · [05 – Security](05_security.md) · [07 – Testing](07_testing.md)
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
# Testing — Smoke-First, Contract-First
|
|
||||||
|
|
||||||
Everything `openapi-first` ships is tested against **real specs, real handlers, and real HTTP** through FastAPI's `TestClient` and the bundled templates. There are no fakes of the library itself.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 1. Suite Overview
|
|
||||||
|
|
||||||
```
|
|
||||||
tests/
|
|
||||||
├── conftest.py # fixtures: spec_file, routes_module, app, client
|
|
||||||
├── test_app.py # OpenAPIFirstApp: routes served, overrides, fail-fast
|
|
||||||
├── test_binder.py # bind_routes: opId resolution + missing-handler failures
|
|
||||||
├── test_loader.py # load_openapi: json/yaml, env resolution, validation
|
|
||||||
└── test_client.py # OpenAPIClient: opId→callable, params, error cases
|
|
||||||
```
|
|
||||||
|
|
||||||
Run with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pytest # 23 tests, zero mocks of the library
|
|
||||||
pytest -q
|
|
||||||
pytest tests/test_loader.py
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ 2. The Fixture Pattern
|
|
||||||
|
|
||||||
```python
|
|
||||||
# conftest.py (abridged)
|
|
||||||
@pytest.fixture
|
|
||||||
def spec_file(tmp_path):
|
|
||||||
path = tmp_path / "openapi.json"
|
|
||||||
path.write_text(json.dumps(SPEC), encoding="utf-8")
|
|
||||||
return str(path)
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def app(spec_file):
|
|
||||||
return OpenAPIFirstApp(
|
|
||||||
openapi_path=spec_file,
|
|
||||||
routes_module=routes_module(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def client(spec_file):
|
|
||||||
return OpenAPIClient(json.loads(Path(spec_file).read_text()))
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 3. What's Actually Asserted
|
|
||||||
|
|
||||||
### 3.1 App-level smoke
|
|
||||||
|
|
||||||
```python
|
|
||||||
def test_app_routes_served(spec_file):
|
|
||||||
app = OpenAPIFirstApp(openapi_path=spec_file, routes_module=routes)
|
|
||||||
client = TestClient(app)
|
|
||||||
assert client.get("/health").json() == {"status": "ok"}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Fail-fast (the heart)
|
|
||||||
|
|
||||||
```python
|
|
||||||
def test_app_missing_handler_fails_at_startup(spec_file):
|
|
||||||
with pytest.raises(MissingOperationHandler):
|
|
||||||
OpenAPIFirstApp(openapi_path=spec_file, routes_module=empty_routes)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 Loader validation
|
|
||||||
|
|
||||||
```python
|
|
||||||
def test_loader_invalid_spec_fails():
|
|
||||||
with pytest.raises(OpenAPISpecLoadError):
|
|
||||||
load_openapi("broken.yaml")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.4 Client contract drift
|
|
||||||
|
|
||||||
```python
|
|
||||||
def test_client_duplicate_operation_id_raises():
|
|
||||||
with pytest.raises(OpenAPIClientError):
|
|
||||||
OpenAPIClient(dup_spec)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏛️ 4. Testing the Templates
|
|
||||||
|
|
||||||
Each bundled template ships its own test + in-memory store, so you get a runnable contract test the moment you scaffold:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
openapi-first scaffold crud_app my-service
|
|
||||||
cd my-service
|
|
||||||
pytest -q
|
|
||||||
```
|
|
||||||
|
|
||||||
`test_crud_app.py` / `test_model_app.py` / `test_vet_app.py` exercise the full CRUD surface through `TestClient` — including `201`/`204` status codes, 404s, and (in `vet_app`) SSE streaming via `StreamingResponse`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🕹️ 5. Quality Gates (CI)
|
|
||||||
|
|
||||||
The same gates the library itself must pass are what keep the docs honest:
|
|
||||||
|
|
||||||
| Gate | Purpose |
|
|
||||||
|------|---------|
|
|
||||||
| `black --check` | formatting parity |
|
|
||||||
| `ruff check` | lint hygiene |
|
|
||||||
| `mypy` | type safety (strict, `--disable-error-code` where intentional) |
|
|
||||||
| `pytest` | 23 tests, green |
|
|
||||||
| coverage | tracked via pytest-cov (HTML + XML + term) |
|
|
||||||
|
|
||||||
Run the whole gate locally:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
black --check openapi_first tests
|
|
||||||
ruff check openapi_first tests
|
|
||||||
mypy openapi_first
|
|
||||||
pytest
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 6. Testing Tips
|
|
||||||
|
|
||||||
- **Start from startup:** assert `OpenAPIFirstApp(...)` *raises* for the broken contracts — those are your most valuable tests
|
|
||||||
- **Client ↔ server:** smoke a client against the same spec the app was built from — one spec, two sides, zero drift
|
|
||||||
- **Templates are scaffolds:** their tests are copyable starting points, not canonical suites
|
|
||||||
- **No mocking of the library:** exercise loader → binder → app → client as a real pipeline
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related
|
|
||||||
|
|
||||||
- [01 – Overview](01_overview.md) · [02 – Components](02_components.md) · [04 – Error Handling](06_error_handling.md)
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# 🧩 openapi-first — OpenAPI as the Single Source of Truth
|
|
||||||
|
|
||||||
`openapi-first` is a small, strict library for **OpenAPI-first FastAPI bootstrapping**: the OpenAPI document *is* the application contract — not an afterthought generated from decorators)Skip. Routes, schemas, security, and even the HTTP client are all derived from one specification.
|
|
||||||
|
|
||||||
> **Doc model:** this wiki is written for humans — how-to guides, examples, and usage recipes. The authoritative API contracts live in the code (docstrings) and the machine-readable bundle under `docs/mcp/`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Key Features
|
|
||||||
|
|
||||||
* 📜 **One spec, two sides** — the same OpenAPI document boots both a FastAPI server (`OpenAPIFirstApp`) and a strict HTTP client (`OpenAPIClient`)
|
|
||||||
* 🔗 **`operationId` binding** — handler functions are bound to routes purely by `operationId`; no routing decorators
|
|
||||||
* 🧱 **Fail-fast contracts** — startup and client construction fail loudly when a handler, operation, or security scheme is missing
|
|
||||||
* 🛡️ **Spec-driven security** — `securitySchemes` + per-operation `security` auto-injected as FastAPI dependencies (Bearer JWT introspection included)
|
|
||||||
* 🧠 **Contract-first codegen** — `models` (Pydantic) and `routes` (resource stubs) generated from a spec
|
|
||||||
* 🧩 **Bundled templates** — `health_app`, `crud_app`, `model_app`, `vet_app` scaffolds for the whole lifecycle, from `/health` to SSE + multi-resource CRUD
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📦 Installation
|
|
||||||
|
|
||||||
From your internal PyPI:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install --extra-index-url https://$PYPI_USERNAME:$PYPI_PASSWORD@pip.aetoskia.com/simple openapi-first
|
|
||||||
```
|
|
||||||
|
|
||||||
From local source:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -e .
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📁 Documentation Structure
|
|
||||||
|
|
||||||
| Section | Description |
|
|
||||||
|---------|-------------|
|
|
||||||
| [Overview](01_overview.md) | The mental model: spec as contract, `operationId` binding, fail-fast guarantees |
|
|
||||||
| [Components](02_components.md) | `app`, `binder`, `loader`, `client`, `errors`, `security`, `codegen`, `cli` |
|
|
||||||
| **Use cases** | Step-by-step recipes |
|
|
||||||
| · [01 – Quickstart](03_use_cases/01_quickstart.md) | Scaffold your first OpenAPI-first service |
|
|
||||||
| · [02 – Templates](03_use_cases/02_templates.md) | The bundled application templates and how to use them |
|
|
||||||
| · [03 – Client setup](03_use_cases/03_client.md) | Build an `operationId`-driven HTTP client |
|
|
||||||
| · [04 – Codegen](03_use_cases/04_codegen.md) | Generate Pydantic models and route stubs from a spec |
|
|
||||||
| [Design](04_design.md) | Architecture, responsibilities, and startup pipeline |
|
|
||||||
| [Security](05_security.md) | `securitySchemes`, env resolution, and JWT introspection |
|
|
||||||
| [Error Handling](06_error_handling.md) | The error hierarchy and when each error is raised |
|
|
||||||
| [Testing](07_testing.md) | Test strategy, quality gates, and coverage |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Related Resources
|
|
||||||
|
|
||||||
* **Source Code:** [Gitea Repository](https://git.aetoskia.com/aetos/openapi-first)
|
|
||||||
* **Internal PyPI:** [pip.aetoskia.com/simple/openapi-first](https://pip.aetoskia.com/simple/openapi-first)
|
|
||||||
* **Drone CI:** Auto-builds and publishes tagged releases, gated on black / ruff / mypy / pytest.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
© Aetoskia Internal — `openapi-first` 0.0.6
|
|
||||||
@@ -70,23 +70,6 @@ class OpenAPIFirstApp(FastAPI):
|
|||||||
"""
|
"""
|
||||||
FastAPI application enforcing OpenAPI-first design.
|
FastAPI application enforcing OpenAPI-first design.
|
||||||
|
|
||||||
Args:
|
|
||||||
openapi_path (str):
|
|
||||||
Filesystem path to the OpenAPI 3.x specification file. This
|
|
||||||
specification is treated as the authoritative API contract.
|
|
||||||
routes_module (ModuleType):
|
|
||||||
Python module containing handler functions whose names correspond
|
|
||||||
exactly to OpenAPI ``operationId`` values.
|
|
||||||
**fastapi_kwargs (Any):
|
|
||||||
Additional keyword arguments passed directly to
|
|
||||||
``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan
|
|
||||||
handlers).
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
OpenAPIFirstError:
|
|
||||||
If the OpenAPI specification is invalid, or if any declared
|
|
||||||
``operationId`` does not have a corresponding handler function.
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
**Responsibilities:**
|
**Responsibilities:**
|
||||||
|
|
||||||
@@ -109,7 +92,7 @@ class OpenAPIFirstApp(FastAPI):
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
```python
|
```python
|
||||||
from openapi_first.app import OpenAPIFirstApp
|
from openapi_first import OpenAPIFirstApp
|
||||||
import app.routes as routes
|
import app.routes as routes
|
||||||
|
|
||||||
app = OpenAPIFirstApp(
|
app = OpenAPIFirstApp(
|
||||||
@@ -127,6 +110,26 @@ class OpenAPIFirstApp(FastAPI):
|
|||||||
routes_module: ModuleType,
|
routes_module: ModuleType,
|
||||||
**fastapi_kwargs: Any,
|
**fastapi_kwargs: Any,
|
||||||
):
|
):
|
||||||
|
"""
|
||||||
|
Initialize the application.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
openapi_path (str):
|
||||||
|
Filesystem path to the OpenAPI 3.x specification file. This
|
||||||
|
specification is treated as the authoritative API contract.
|
||||||
|
routes_module (ModuleType):
|
||||||
|
Python module containing handler functions whose names correspond
|
||||||
|
exactly to OpenAPI ``operationId`` values.
|
||||||
|
**fastapi_kwargs (Any):
|
||||||
|
Additional keyword arguments passed directly to
|
||||||
|
``fastapi.FastAPI`` (e.g., title, version, middleware, lifespan
|
||||||
|
handlers).
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OpenAPIFirstError:
|
||||||
|
If the OpenAPI specification is invalid, or if any declared
|
||||||
|
``operationId`` does not have a corresponding handler function.
|
||||||
|
"""
|
||||||
# Initialize FastAPI normally
|
# Initialize FastAPI normally
|
||||||
super().__init__(**fastapi_kwargs)
|
super().__init__(**fastapi_kwargs)
|
||||||
|
|
||||||
|
|||||||
@@ -131,24 +131,6 @@ def main() -> None:
|
|||||||
default="models",
|
default="models",
|
||||||
help="Python module path for models (default: models)",
|
help="Python module path for models (default: models)",
|
||||||
)
|
)
|
||||||
routes_parser.add_argument(
|
|
||||||
"--inject-request",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="Prepend `request: Request` to every generated handler",
|
|
||||||
)
|
|
||||||
routes_parser.add_argument(
|
|
||||||
"--package",
|
|
||||||
action="store_true",
|
|
||||||
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()
|
args = parser.parse_args()
|
||||||
|
|
||||||
@@ -171,9 +153,6 @@ def main() -> None:
|
|||||||
output_dir=Path(args.output_dir),
|
output_dir=Path(args.output_dir),
|
||||||
use_models=args.use_models,
|
use_models=args.use_models,
|
||||||
models_module=args.models_module,
|
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):")
|
print(f"Generated {len(files)} route file(s):")
|
||||||
for f in files:
|
for f in files:
|
||||||
|
|||||||
@@ -50,20 +50,6 @@ class OpenAPIClient:
|
|||||||
"""
|
"""
|
||||||
OpenAPI-first HTTP client (`httpx`-based).
|
OpenAPI-first HTTP client (`httpx`-based).
|
||||||
|
|
||||||
Args:
|
|
||||||
spec (dict[str, Any]):
|
|
||||||
Parsed OpenAPI 3.x specification.
|
|
||||||
base_url (str | None):
|
|
||||||
Base URL of the target service. If omitted, the first entry in the
|
|
||||||
OpenAPI `servers` list is used.
|
|
||||||
client (httpx.Client | None):
|
|
||||||
Optional preconfigured httpx client instance.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
OpenAPIClientError:
|
|
||||||
If no servers are defined, spec has no paths, operationIds are
|
|
||||||
missing/duplicate, or required parameters are missing.
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
**Responsibilities:**
|
**Responsibilities:**
|
||||||
|
|
||||||
@@ -106,6 +92,21 @@ class OpenAPIClient:
|
|||||||
base_url: str | None = None,
|
base_url: str | None = None,
|
||||||
client: httpx.Client | None = None,
|
client: httpx.Client | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Initialize the OpenAPI client.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
spec (dict[str, Any]):
|
||||||
|
Parsed OpenAPI 3.x specification.
|
||||||
|
base_url (str | None):
|
||||||
|
Base URL of the target service. If omitted, the first entry in the OpenAPI `servers` list is used.
|
||||||
|
client (httpx.Client | None):
|
||||||
|
Optional preconfigured httpx client instance.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OpenAPIClientError:
|
||||||
|
If no servers are defined, spec has no paths, operationIds are missing/duplicate, or required parameters are missing.
|
||||||
|
"""
|
||||||
self.spec = spec
|
self.spec = spec
|
||||||
self.base_url = base_url or self._resolve_base_url(spec)
|
self.base_url = base_url or self._resolve_base_url(spec)
|
||||||
self.client = client or httpx.Client(base_url=self.base_url)
|
self.client = client or httpx.Client(base_url=self.base_url)
|
||||||
|
|||||||
@@ -18,48 +18,13 @@ Notes:
|
|||||||
parent resource (``pets``).
|
parent resource (``pets``).
|
||||||
- Parameter types and defaults are inferred from the spec.
|
- Parameter types and defaults are inferred from the spec.
|
||||||
- ``response: Response`` is injected for non-200 success codes.
|
- ``response: Response`` is injected for non-200 success codes.
|
||||||
- Resource names are sanitized to valid Python module identifiers,
|
|
||||||
so a path such as ``/fetch-requests`` yields ``fetch_requests.py``.
|
|
||||||
- Passing ``inject_request=True`` prepends ``request: Request`` to
|
|
||||||
every handler (the composition-root / DI convention).
|
|
||||||
- ``multipart/form-data`` and
|
|
||||||
``application/x-www-form-urlencoded`` request bodies expand to one
|
|
||||||
parameter per schema property (``UploadFile`` / ``File`` / ``Form``)
|
|
||||||
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
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .loader import load_openapi
|
from .loader import load_openapi
|
||||||
|
|
||||||
_FORM_URLENCODED = "application/x-www-form-urlencoded"
|
|
||||||
_MULTIPART = "multipart/form-data"
|
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_identifier(name: str) -> str:
|
|
||||||
"""Convert an arbitrary path segment into a valid Python module name.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name (str): Raw resource name (first path segment).
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: A valid lower-case Python identifier (e.g. ``fetch-requests``
|
|
||||||
becomes ``fetch_requests``).
|
|
||||||
"""
|
|
||||||
ident = re.sub(r"\W", "_", name).strip("_").lower()
|
|
||||||
if not ident:
|
|
||||||
return "resource"
|
|
||||||
if ident[0].isdigit():
|
|
||||||
ident = f"_{ident}"
|
|
||||||
return ident
|
|
||||||
|
|
||||||
|
|
||||||
def generate_routes(
|
def generate_routes(
|
||||||
spec_path: Path,
|
spec_path: Path,
|
||||||
@@ -67,16 +32,13 @@ def generate_routes(
|
|||||||
*,
|
*,
|
||||||
use_models: bool = False,
|
use_models: bool = False,
|
||||||
models_module: str = "models",
|
models_module: str = "models",
|
||||||
inject_request: bool = False,
|
|
||||||
package: bool = False,
|
|
||||||
template_style: bool = False,
|
|
||||||
) -> list[Path]:
|
) -> list[Path]:
|
||||||
"""
|
"""
|
||||||
Generate route handler stubs from an OpenAPI specification.
|
Generate route handler stubs from an OpenAPI specification.
|
||||||
|
|
||||||
Creates one route module per resource in *output_dir*. Resources are
|
Creates one ``<resource>.py`` file per resource in *output_dir*.
|
||||||
derived from the first path segment (e.g. ``/pets`` and ``/pets/{id}``
|
Resources are derived from the first path segment (e.g. ``/pets``
|
||||||
both group under ``pets``) and sanitized to valid module identifiers.
|
and ``/pets/{id}`` both group under ``pets``).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
spec_path (Path):
|
spec_path (Path):
|
||||||
@@ -90,17 +52,6 @@ def generate_routes(
|
|||||||
models_module (str, optional):
|
models_module (str, optional):
|
||||||
Dotted Python module path from which to import models
|
Dotted Python module path from which to import models
|
||||||
(e.g. ``"models"``, ``"app.models"``).
|
(e.g. ``"models"``, ``"app.models"``).
|
||||||
inject_request (bool, optional):
|
|
||||||
If ``True``, prepend ``request: Request`` to every handler so the
|
|
||||||
stub can later resolve services from ``request.app.state``.
|
|
||||||
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:
|
Returns:
|
||||||
list[Path]:
|
list[Path]:
|
||||||
@@ -135,30 +86,17 @@ def generate_routes(
|
|||||||
|
|
||||||
generated_files: list[Path] = []
|
generated_files: list[Path] = []
|
||||||
|
|
||||||
if package:
|
|
||||||
_ensure_package(output_dir)
|
|
||||||
|
|
||||||
for resource in sorted(resources_map):
|
for resource in sorted(resources_map):
|
||||||
operations = resources_map[resource]
|
operations = resources_map[resource]
|
||||||
_validate_operations(resource, operations)
|
_validate_operations(resource, operations)
|
||||||
|
|
||||||
identifier = _sanitize_identifier(resource)
|
file_path = output_dir / f"{resource}.py"
|
||||||
if package:
|
|
||||||
resource_dir = output_dir / identifier
|
|
||||||
resource_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
_ensure_package(resource_dir)
|
|
||||||
file_path = resource_dir / "routes.py"
|
|
||||||
else:
|
|
||||||
file_path = output_dir / f"{identifier}.py"
|
|
||||||
|
|
||||||
content = _generate_resource_file(
|
content = _generate_resource_file(
|
||||||
resource=resource,
|
resource=resource,
|
||||||
operations=operations,
|
operations=operations,
|
||||||
spec_path=str(spec_path),
|
spec_path=str(spec_path),
|
||||||
use_models=use_models,
|
use_models=use_models,
|
||||||
models_module=models_module,
|
models_module=models_module,
|
||||||
inject_request=inject_request,
|
|
||||||
template_style=template_style,
|
|
||||||
)
|
)
|
||||||
file_path.write_text(content, encoding="utf-8")
|
file_path.write_text(content, encoding="utf-8")
|
||||||
generated_files.append(file_path)
|
generated_files.append(file_path)
|
||||||
@@ -180,13 +118,6 @@ _TYPE_MAP: dict[str, str] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _ensure_package(directory: Path) -> None:
|
|
||||||
"""Create an ``__init__.py`` in *directory* when it is missing."""
|
|
||||||
init = directory / "__init__.py"
|
|
||||||
if not init.exists():
|
|
||||||
init.write_text("", encoding="utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def _validate_operations(
|
def _validate_operations(
|
||||||
resource: str, operations: list[tuple[str, str, dict[str, Any]]]
|
resource: str, operations: list[tuple[str, str, dict[str, Any]]]
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -221,55 +152,6 @@ def _get_request_body_schema(operation: dict[str, Any]) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_form_properties(
|
|
||||||
operation: dict[str, Any],
|
|
||||||
) -> tuple[str, dict[str, Any], list[str]] | None:
|
|
||||||
"""Return ``(media_type, properties, required)`` for a form-style body.
|
|
||||||
|
|
||||||
Handles ``multipart/form-data`` and ``application/x-www-form-urlencoded``
|
|
||||||
request bodies whose schema is an inline object with ``properties``.
|
|
||||||
Returns ``None`` for JSON bodies or bodies that use ``$ref``.
|
|
||||||
"""
|
|
||||||
request_body = operation.get("requestBody")
|
|
||||||
if not request_body:
|
|
||||||
return None
|
|
||||||
|
|
||||||
content = request_body.get("content", {})
|
|
||||||
for media_type, media_info in content.items():
|
|
||||||
base = media_type.split(";", 1)[0].strip()
|
|
||||||
if base not in (_MULTIPART, _FORM_URLENCODED):
|
|
||||||
continue
|
|
||||||
schema = media_info.get("schema", {})
|
|
||||||
if "$ref" in schema:
|
|
||||||
return None
|
|
||||||
properties = schema.get("properties")
|
|
||||||
if not isinstance(properties, dict) or not properties:
|
|
||||||
return None
|
|
||||||
return base, properties, list(schema.get("required", []))
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _is_binary(schema: dict[str, Any]) -> bool:
|
|
||||||
"""Whether a schema represents an uploaded binary payload."""
|
|
||||||
if schema.get("format") == "binary":
|
|
||||||
return True
|
|
||||||
if schema.get("type") == "array":
|
|
||||||
return _is_binary(schema.get("items", {}))
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _form_annotation(schema: dict[str, Any]) -> str:
|
|
||||||
"""Map a form field schema to its Python annotation."""
|
|
||||||
if schema.get("type") == "array" and _is_binary(schema.get("items", {})):
|
|
||||||
return "list[UploadFile]"
|
|
||||||
if _is_binary(schema):
|
|
||||||
return "UploadFile"
|
|
||||||
ref = schema.get("$ref", "")
|
|
||||||
if ref:
|
|
||||||
return str(ref.rsplit("/", 1)[-1])
|
|
||||||
return _resolve_type(schema)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_success_status(operation: dict[str, Any]) -> str:
|
def _get_success_status(operation: dict[str, Any]) -> str:
|
||||||
"""Return the primary success status code (200, 201, 204, or default)."""
|
"""Return the primary success status code (200, 201, 204, or default)."""
|
||||||
responses = operation.get("responses", {})
|
responses = operation.get("responses", {})
|
||||||
@@ -302,40 +184,9 @@ def _generate_resource_file(
|
|||||||
spec_path: str,
|
spec_path: str,
|
||||||
use_models: bool,
|
use_models: bool,
|
||||||
models_module: str,
|
models_module: str,
|
||||||
inject_request: bool = False,
|
|
||||||
template_style: bool = False,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Assemble the full Python source for a single resource file."""
|
"""Assemble the full Python source for a single resource file."""
|
||||||
op_ids = [op.get("operationId", "") for _, _, op in operations]
|
op_ids = [op.get("operationId", "") for _, _, op in operations]
|
||||||
# Windows paths would inject invalid escapes (e.g. ``\U``) into the
|
|
||||||
# generated docstring; normalize to forward slashes for the header.
|
|
||||||
spec_path = spec_path.replace("\\", "/")
|
|
||||||
|
|
||||||
need_upload = False
|
|
||||||
need_file_form = False
|
|
||||||
need_form = False
|
|
||||||
for _, _, op in operations:
|
|
||||||
form = _get_form_properties(op)
|
|
||||||
if not form:
|
|
||||||
continue
|
|
||||||
_, properties, _ = form
|
|
||||||
for prop_schema in properties.values():
|
|
||||||
if _is_binary(prop_schema):
|
|
||||||
need_upload = True
|
|
||||||
need_file_form = True
|
|
||||||
else:
|
|
||||||
need_form = True
|
|
||||||
|
|
||||||
fastapi_names = ["Response", "HTTPException"]
|
|
||||||
if inject_request:
|
|
||||||
fastapi_names.insert(0, "Request")
|
|
||||||
if need_upload:
|
|
||||||
fastapi_names.append("UploadFile")
|
|
||||||
if need_file_form:
|
|
||||||
fastapi_names.append("File")
|
|
||||||
if need_form:
|
|
||||||
fastapi_names.append("Form")
|
|
||||||
|
|
||||||
lines: list[str] = [
|
lines: list[str] = [
|
||||||
f'"""Route handlers for the {resource} resource.',
|
f'"""Route handlers for the {resource} resource.',
|
||||||
"",
|
"",
|
||||||
@@ -343,7 +194,7 @@ def _generate_resource_file(
|
|||||||
f'Bind via operationIds: {", ".join(op_ids)}',
|
f'Bind via operationIds: {", ".join(op_ids)}',
|
||||||
'"""',
|
'"""',
|
||||||
"",
|
"",
|
||||||
f"from fastapi import {', '.join(fastapi_names)}",
|
"from fastapi import Response, HTTPException",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Conditional typing import
|
# Conditional typing import
|
||||||
@@ -354,8 +205,6 @@ def _generate_resource_file(
|
|||||||
if use_models:
|
if use_models:
|
||||||
schemas_needed: set[str] = set()
|
schemas_needed: set[str] = set()
|
||||||
for _, _, op in operations:
|
for _, _, op in operations:
|
||||||
if _get_form_properties(op):
|
|
||||||
continue
|
|
||||||
schema = _get_request_body_schema(op)
|
schema = _get_request_body_schema(op)
|
||||||
if schema:
|
if schema:
|
||||||
schemas_needed.add(schema)
|
schemas_needed.add(schema)
|
||||||
@@ -368,17 +217,7 @@ def _generate_resource_file(
|
|||||||
|
|
||||||
for path, http_method, operation in operations:
|
for path, http_method, operation in operations:
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.extend(
|
lines.extend(_generate_handler(path, http_method, operation, use_models))
|
||||||
_generate_handler(
|
|
||||||
path,
|
|
||||||
http_method,
|
|
||||||
operation,
|
|
||||||
use_models,
|
|
||||||
inject_request,
|
|
||||||
resource,
|
|
||||||
template_style,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
lines.append("")
|
lines.append("")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
@@ -389,14 +228,10 @@ def _generate_handler(
|
|||||||
http_method: str,
|
http_method: str,
|
||||||
operation: dict[str, Any],
|
operation: dict[str, Any],
|
||||||
use_models: bool,
|
use_models: bool,
|
||||||
inject_request: bool = False,
|
|
||||||
resource: str = "",
|
|
||||||
template_style: bool = False,
|
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Build the source lines for a single handler function."""
|
"""Build the source lines for a single handler function."""
|
||||||
operation_id = operation.get("operationId", "")
|
operation_id = operation.get("operationId", "")
|
||||||
summary = operation.get("summary", f"{http_method.upper()} {path}")
|
summary = operation.get("summary", f"{http_method.upper()} {path}")
|
||||||
body_param = "body" if template_style else "payload"
|
|
||||||
|
|
||||||
params: list[str] = []
|
params: list[str] = []
|
||||||
path_params: list[str] = []
|
path_params: list[str] = []
|
||||||
@@ -422,31 +257,16 @@ def _generate_handler(
|
|||||||
query_params.append(f"{name}: {param_type} = None")
|
query_params.append(f"{name}: {param_type} = None")
|
||||||
# header / cookie params could be extended here
|
# header / cookie params could be extended here
|
||||||
|
|
||||||
if inject_request:
|
|
||||||
params.append("request: Request")
|
|
||||||
params.extend(path_params)
|
params.extend(path_params)
|
||||||
params.extend(query_params)
|
params.extend(query_params)
|
||||||
|
|
||||||
# Request body
|
# Request body
|
||||||
form = _get_form_properties(operation)
|
|
||||||
schema_name = _get_request_body_schema(operation)
|
schema_name = _get_request_body_schema(operation)
|
||||||
is_form = False
|
if operation.get("requestBody"):
|
||||||
if form:
|
|
||||||
is_form = True
|
|
||||||
_, properties, required_fields = form
|
|
||||||
for field_name, field_schema in properties.items():
|
|
||||||
annotation = _form_annotation(field_schema)
|
|
||||||
is_file = _is_binary(field_schema)
|
|
||||||
factory = "File" if is_file else "Form"
|
|
||||||
if field_name in required_fields:
|
|
||||||
params.append(f"{field_name}: {annotation} = {factory}(...)")
|
|
||||||
else:
|
|
||||||
params.append(f"{field_name}: {annotation} = {factory}(None)")
|
|
||||||
elif operation.get("requestBody"):
|
|
||||||
if use_models and schema_name:
|
if use_models and schema_name:
|
||||||
params.append(f"{body_param}: {schema_name}")
|
params.append(f"payload: {schema_name}")
|
||||||
else:
|
else:
|
||||||
params.append(f"{body_param}: dict")
|
params.append("payload: dict")
|
||||||
|
|
||||||
# Inject Response for non-200 success codes
|
# Inject Response for non-200 success codes
|
||||||
success_status = _get_success_status(operation)
|
success_status = _get_success_status(operation)
|
||||||
@@ -456,8 +276,7 @@ def _generate_handler(
|
|||||||
# Build function body
|
# Build function body
|
||||||
lines: list[str] = []
|
lines: list[str] = []
|
||||||
param_str = ", ".join(params)
|
param_str = ", ".join(params)
|
||||||
prefix = "async def" if template_style else "def"
|
lines.append(f"def {operation_id}({param_str}):")
|
||||||
lines.append(f"{prefix} {operation_id}({param_str}):")
|
|
||||||
lines.append(f' """{summary}')
|
lines.append(f' """{summary}')
|
||||||
|
|
||||||
# Document parameters
|
# Document parameters
|
||||||
@@ -484,29 +303,17 @@ def _generate_handler(
|
|||||||
else:
|
else:
|
||||||
lines.append(f" {pname} : {ptype}")
|
lines.append(f" {pname} : {ptype}")
|
||||||
|
|
||||||
if is_form:
|
if operation.get("requestBody"):
|
||||||
_, properties, _ = form # type: ignore[misc]
|
|
||||||
lines.append("")
|
|
||||||
for field_name, field_schema in properties.items():
|
|
||||||
lines.append(f" {field_name} : {_form_annotation(field_schema)}")
|
|
||||||
lines.append(" Multipart form field.")
|
|
||||||
elif operation.get("requestBody"):
|
|
||||||
if use_models and schema_name:
|
if use_models and schema_name:
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append(f" {body_param} : {schema_name}")
|
lines.append(f" payload : {schema_name}")
|
||||||
lines.append(" Request body.")
|
lines.append(" Request body.")
|
||||||
else:
|
else:
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append(f" {body_param} : dict")
|
lines.append(" payload : dict")
|
||||||
lines.append(" Request body.")
|
lines.append(" Request body.")
|
||||||
|
|
||||||
lines.append(' """')
|
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")
|
lines.append(" raise NotImplementedError")
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|||||||
@@ -6,7 +6,4 @@ def generate_routes(
|
|||||||
*,
|
*,
|
||||||
use_models: bool = ...,
|
use_models: bool = ...,
|
||||||
models_module: str = ...,
|
models_module: str = ...,
|
||||||
inject_request: bool = ...,
|
|
||||||
package: bool = ...,
|
|
||||||
template_style: bool = ...,
|
|
||||||
) -> list[Path]: ...
|
) -> list[Path]: ...
|
||||||
|
|||||||
@@ -40,14 +40,6 @@ class MissingOperationHandler(OpenAPIFirstError):
|
|||||||
"""
|
"""
|
||||||
Raised when an OpenAPI operation cannot be resolved to a handler.
|
Raised when an OpenAPI operation cannot be resolved to a handler.
|
||||||
|
|
||||||
Args:
|
|
||||||
path (str):
|
|
||||||
The HTTP path declared in the OpenAPI specification.
|
|
||||||
method (str):
|
|
||||||
The HTTP method (as declared in the OpenAPI spec).
|
|
||||||
operation_id (str | None):
|
|
||||||
The operationId declared in the OpenAPI spec, if present.
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
**Scenarios:**
|
**Scenarios:**
|
||||||
|
|
||||||
@@ -63,6 +55,17 @@ class MissingOperationHandler(OpenAPIFirstError):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *, path: str, method: str, operation_id: str | None = None):
|
def __init__(self, *, path: str, method: str, operation_id: str | None = None):
|
||||||
|
"""
|
||||||
|
Initialize the error.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str):
|
||||||
|
The HTTP path declared in the OpenAPI specification.
|
||||||
|
method (str):
|
||||||
|
The HTTP method (as declared in the OpenAPI spec).
|
||||||
|
operation_id (str | None):
|
||||||
|
The operationId declared in the OpenAPI spec, if present.
|
||||||
|
"""
|
||||||
if operation_id:
|
if operation_id:
|
||||||
message = (
|
message = (
|
||||||
f"Missing handler for operationId '{operation_id}' "
|
f"Missing handler for operationId '{operation_id}' "
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ Scaffolding via CLI
|
|||||||
|
|
||||||
Create a new CRUD example service using the bundled template:
|
Create a new CRUD example service using the bundled template:
|
||||||
|
|
||||||
openapi-first scaffold crud_app
|
openapi-first crud_app
|
||||||
|
|
||||||
Create the service in a custom directory:
|
Create the service in a custom directory:
|
||||||
|
|
||||||
openapi-first scaffold crud_app my-crud-service
|
openapi-first crud_app my-crud-service
|
||||||
|
|
||||||
List all available application templates:
|
List all available application templates:
|
||||||
|
|
||||||
openapi-first scaffold --list
|
openapi-first --list
|
||||||
|
|
||||||
The CLI copies template files verbatim into the target directory.
|
The CLI copies template files verbatim into the target directory.
|
||||||
No code is generated or modified beyond the copied scaffold.
|
No code is generated or modified beyond the copied scaffold.
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ _items: Dict[int, dict] = {
|
|||||||
_next_id = 3
|
_next_id = 3
|
||||||
|
|
||||||
|
|
||||||
def list_items() -> list[dict]:
|
def list_items():
|
||||||
"""
|
"""
|
||||||
Return all items in the data store.
|
Return all items in the data store.
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ def list_items() -> list[dict]:
|
|||||||
return list(_items.values())
|
return list(_items.values())
|
||||||
|
|
||||||
|
|
||||||
def get_item(item_id: int) -> dict:
|
def get_item(item_id: int):
|
||||||
"""
|
"""
|
||||||
Retrieve a single item by ID.
|
Retrieve a single item by ID.
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ def get_item(item_id: int) -> dict:
|
|||||||
return _items[item_id]
|
return _items[item_id]
|
||||||
|
|
||||||
|
|
||||||
def create_item(payload: dict) -> dict:
|
def create_item(payload: dict):
|
||||||
"""
|
"""
|
||||||
Create a new item in the data store.
|
Create a new item in the data store.
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ def create_item(payload: dict) -> dict:
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def update_item(item_id: int, payload: dict) -> dict:
|
def update_item(item_id: int, payload: dict):
|
||||||
"""
|
"""
|
||||||
Replace an existing item in the data store.
|
Replace an existing item in the data store.
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from data import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def list_items() -> list[dict]:
|
def list_items():
|
||||||
"""
|
"""
|
||||||
List all items.
|
List all items.
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ def list_items() -> list[dict]:
|
|||||||
return _list_items()
|
return _list_items()
|
||||||
|
|
||||||
|
|
||||||
def get_item(item_id: int) -> dict:
|
def get_item(item_id: int):
|
||||||
"""
|
"""
|
||||||
Retrieve a single item by ID.
|
Retrieve a single item by ID.
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ def get_item(item_id: int) -> dict:
|
|||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
|
|
||||||
|
|
||||||
def create_item(payload: dict, response: Response) -> dict:
|
def create_item(payload: dict, response: Response):
|
||||||
"""
|
"""
|
||||||
Create a new item.
|
Create a new item.
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ def create_item(payload: dict, response: Response) -> dict:
|
|||||||
----------
|
----------
|
||||||
payload : dict
|
payload : dict
|
||||||
Item attributes excluding the ``id`` field.
|
Item attributes excluding the ``id`` field.
|
||||||
response : Response
|
response : fastapi.Response
|
||||||
Response object used to set the HTTP status code.
|
Response object used to set the HTTP status code.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -96,7 +96,7 @@ def create_item(payload: dict, response: Response) -> dict:
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def update_item(item_id: int, payload: dict) -> dict:
|
def update_item(item_id: int, payload: dict):
|
||||||
"""
|
"""
|
||||||
Update an existing item.
|
Update an existing item.
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ def update_item(item_id: int, payload: dict) -> dict:
|
|||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
|
|
||||||
|
|
||||||
def delete_item(item_id: int, response: Response) -> None:
|
def delete_item(item_id: int, response: Response):
|
||||||
"""
|
"""
|
||||||
Delete an existing item.
|
Delete an existing item.
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ def delete_item(item_id: int, response: Response) -> None:
|
|||||||
----------
|
----------
|
||||||
item_id : int
|
item_id : int
|
||||||
Identifier of the item to delete.
|
Identifier of the item to delete.
|
||||||
response : Response
|
response : fastapi.Response
|
||||||
Response object used to set the HTTP status code.
|
Response object used to set the HTTP status code.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ Scaffolding via CLI
|
|||||||
Create a new OpenAPI-first health check service using the bundled
|
Create a new OpenAPI-first health check service using the bundled
|
||||||
template:
|
template:
|
||||||
|
|
||||||
openapi-first scaffold health_app
|
openapi-first health_app
|
||||||
|
|
||||||
Create the service in a custom directory:
|
Create the service in a custom directory:
|
||||||
|
|
||||||
openapi-first scaffold health_app my-health-service
|
openapi-first health_app my-health-service
|
||||||
|
|
||||||
List all available application templates:
|
List all available application templates:
|
||||||
|
|
||||||
openapi-first scaffold --list
|
openapi-first --list
|
||||||
|
|
||||||
The CLI copies template files verbatim into the target directory.
|
The CLI copies template files verbatim into the target directory.
|
||||||
No code is generated or modified beyond the copied scaffold.
|
No code is generated or modified beyond the copied scaffold.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ This module serves solely as an operationId namespace.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def get_health() -> dict:
|
def get_health():
|
||||||
"""
|
"""
|
||||||
Health check operation handler.
|
Health check operation handler.
|
||||||
|
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ Scaffolding via CLI
|
|||||||
|
|
||||||
Create a new model-based CRUD example service using the bundled template:
|
Create a new model-based CRUD example service using the bundled template:
|
||||||
|
|
||||||
openapi-first scaffold model_app
|
openapi-first model_app
|
||||||
|
|
||||||
Create the service in a custom directory:
|
Create the service in a custom directory:
|
||||||
|
|
||||||
openapi-first scaffold model_app my-model-service
|
openapi-first model_app my-model-service
|
||||||
|
|
||||||
List all available application templates:
|
List all available application templates:
|
||||||
|
|
||||||
openapi-first scaffold --list
|
openapi-first --list
|
||||||
|
|
||||||
The CLI copies template files verbatim into the target directory.
|
The CLI copies template files verbatim into the target directory.
|
||||||
No code is generated or modified beyond the copied scaffold.
|
No code is generated or modified beyond the copied scaffold.
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ def get_item(item_id: int) -> Item:
|
|||||||
KeyError
|
KeyError
|
||||||
If the item does not exist.
|
If the item does not exist.
|
||||||
"""
|
"""
|
||||||
if item_id not in _items:
|
|
||||||
raise KeyError(item_id)
|
|
||||||
return _items[item_id]
|
return _items[item_id]
|
||||||
|
|
||||||
|
|
||||||
@@ -136,6 +134,4 @@ def delete_item(item_id: int) -> None:
|
|||||||
KeyError
|
KeyError
|
||||||
If the item does not exist.
|
If the item does not exist.
|
||||||
"""
|
"""
|
||||||
if item_id not in _items:
|
|
||||||
raise KeyError(item_id)
|
|
||||||
del _items[item_id]
|
del _items[item_id]
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from data import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def list_items() -> list[Item]:
|
def list_items():
|
||||||
"""
|
"""
|
||||||
List all items.
|
List all items.
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ def list_items() -> list[Item]:
|
|||||||
return _list_items()
|
return _list_items()
|
||||||
|
|
||||||
|
|
||||||
def get_item(item_id: int) -> Item:
|
def get_item(item_id: int):
|
||||||
"""
|
"""
|
||||||
Retrieve a single item by ID.
|
Retrieve a single item by ID.
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ def get_item(item_id: int) -> Item:
|
|||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
|
|
||||||
|
|
||||||
def create_item(payload: ItemCreate, response: Response) -> Item:
|
def create_item(payload: ItemCreate, response: Response):
|
||||||
"""
|
"""
|
||||||
Create a new item.
|
Create a new item.
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ def create_item(payload: ItemCreate, response: Response) -> Item:
|
|||||||
----------
|
----------
|
||||||
payload : ItemCreate
|
payload : ItemCreate
|
||||||
Request body describing the item to create.
|
Request body describing the item to create.
|
||||||
response : Response
|
response : fastapi.Response
|
||||||
Response object used to set the HTTP status code.
|
Response object used to set the HTTP status code.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -93,7 +93,7 @@ def create_item(payload: ItemCreate, response: Response) -> Item:
|
|||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
def update_item(item_id: int, payload: ItemCreate) -> Item:
|
def update_item(item_id: int, payload: ItemCreate):
|
||||||
"""
|
"""
|
||||||
Update an existing item.
|
Update an existing item.
|
||||||
|
|
||||||
@@ -123,7 +123,7 @@ def update_item(item_id: int, payload: ItemCreate) -> Item:
|
|||||||
raise HTTPException(status_code=404, detail="Item not found")
|
raise HTTPException(status_code=404, detail="Item not found")
|
||||||
|
|
||||||
|
|
||||||
def delete_item(item_id: int, response: Response) -> None:
|
def delete_item(item_id: int, response: Response):
|
||||||
"""
|
"""
|
||||||
Delete an existing item.
|
Delete an existing item.
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ def delete_item(item_id: int, response: Response) -> None:
|
|||||||
----------
|
----------
|
||||||
item_id : int
|
item_id : int
|
||||||
Identifier of the item to delete.
|
Identifier of the item to delete.
|
||||||
response : Response
|
response : fastapi.Response
|
||||||
Response object used to set the HTTP status code.
|
Response object used to set the HTTP status code.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ Scaffolding via CLI
|
|||||||
|
|
||||||
Create a new vet clinic service using the bundled template:
|
Create a new vet clinic service using the bundled template:
|
||||||
|
|
||||||
openapi-first scaffold vet_app
|
openapi-first vet_app
|
||||||
|
|
||||||
Create the service in a custom directory:
|
Create the service in a custom directory:
|
||||||
|
|
||||||
openapi-first scaffold vet_app my-vet-clinic
|
openapi-first vet_app my-vet-clinic
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
Client Usage Example
|
Client Usage Example
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ from data import (
|
|||||||
create_pet as _create_pet,
|
create_pet as _create_pet,
|
||||||
update_pet as _update_pet,
|
update_pet as _update_pet,
|
||||||
delete_pet as _delete_pet,
|
delete_pet as _delete_pet,
|
||||||
|
get_pet as _get_pet,
|
||||||
list_appointments as _list_appointments,
|
list_appointments as _list_appointments,
|
||||||
get_appointment as _get_appointment,
|
get_appointment as _get_appointment,
|
||||||
create_appointment as _create_appointment,
|
create_appointment as _create_appointment,
|
||||||
@@ -56,7 +57,7 @@ from data import (
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def list_parents(limit: int = 20, offset: int = 0) -> dict:
|
def list_parents(limit: int = 20, offset: int = 0):
|
||||||
"""List parents (paginated).
|
"""List parents (paginated).
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -75,15 +76,13 @@ def list_parents(limit: int = 20, offset: int = 0) -> dict:
|
|||||||
return {"total": len(items), "items": items[offset:offset + limit] if limit else items[offset:]}
|
return {"total": len(items), "items": items[offset:offset + limit] if limit else items[offset:]}
|
||||||
|
|
||||||
|
|
||||||
def create_parent(payload: ParentCreate, response: Response) -> Parent:
|
def create_parent(payload: ParentCreate, response: Response):
|
||||||
"""Create a parent.
|
"""Create a parent.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
payload : ParentCreate
|
payload : ParentCreate
|
||||||
Parent data excluding the ``id`` field.
|
Parent data excluding the ``id`` field.
|
||||||
response : Response
|
|
||||||
Response object used to set the HTTP status code.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@@ -95,7 +94,7 @@ def create_parent(payload: ParentCreate, response: Response) -> Parent:
|
|||||||
return parent
|
return parent
|
||||||
|
|
||||||
|
|
||||||
def get_parent(id: int) -> Parent:
|
def get_parent(id: int):
|
||||||
"""Retrieve a single parent by ID.
|
"""Retrieve a single parent by ID.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -119,7 +118,7 @@ def get_parent(id: int) -> Parent:
|
|||||||
raise HTTPException(status_code=404, detail="Parent not found")
|
raise HTTPException(status_code=404, detail="Parent not found")
|
||||||
|
|
||||||
|
|
||||||
def update_parent(id: int, payload: ParentCreate) -> Parent:
|
def update_parent(id: int, payload: ParentCreate):
|
||||||
"""Update an existing parent.
|
"""Update an existing parent.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@@ -145,15 +144,13 @@ def update_parent(id: int, payload: ParentCreate) -> Parent:
|
|||||||
raise HTTPException(status_code=404, detail="Parent not found")
|
raise HTTPException(status_code=404, detail="Parent not found")
|
||||||
|
|
||||||
|
|
||||||
def delete_parent(id: int, response: Response) -> None:
|
def delete_parent(id: int, response: Response):
|
||||||
"""Delete an existing parent.
|
"""Delete an existing parent.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
id : int
|
id : int
|
||||||
Identifier of the parent.
|
Identifier of the parent.
|
||||||
response : Response
|
|
||||||
Response object used to set the HTTP status code.
|
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
@@ -215,7 +212,7 @@ def delete_vet(id: int, response: Response):
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def list_treatments() -> list[Treatment]:
|
def list_treatments():
|
||||||
"""List treatments (catalogue).
|
"""List treatments (catalogue).
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
@@ -301,7 +298,7 @@ def delete_pet(id: int, response: Response):
|
|||||||
response.status_code = 204
|
response.status_code = 204
|
||||||
|
|
||||||
|
|
||||||
def upload_pet_photo(id: int, file: UploadFile) -> dict:
|
def upload_pet_photo(id: int, file: UploadFile):
|
||||||
"""Upload a pet photo.
|
"""Upload a pet photo.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "openapi-first"
|
name = "openapi-first"
|
||||||
version = "0.0.7"
|
version = "0.0.6"
|
||||||
description = "Strict OpenAPI-first application bootstrap for FastAPI."
|
description = "Strict OpenAPI-first application bootstrap for FastAPI."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
@@ -183,14 +183,6 @@ module = [
|
|||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
|
||||||
[tool.pydoclint]
|
|
||||||
style = "google"
|
|
||||||
exclude = '\.pyi$|tests/'
|
|
||||||
allow-init-docstring = true
|
|
||||||
skip-checking-raises = true
|
|
||||||
check-class-attributes = false
|
|
||||||
|
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
source = ["openapi_first"]
|
source = ["openapi_first"]
|
||||||
omit = [
|
omit = [
|
||||||
|
|||||||
@@ -1,284 +0,0 @@
|
|||||||
"""Smoke tests for openapi_first.codegen_routes."""
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from openapi_first.codegen_routes import generate_routes
|
|
||||||
|
|
||||||
|
|
||||||
def _write_spec(tmp_path, spec: dict):
|
|
||||||
path = tmp_path / "openapi.json"
|
|
||||||
path.write_text(json.dumps(spec), encoding="utf-8")
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def _spec(paths: dict, schemas: dict | None = None) -> dict:
|
|
||||||
spec = {
|
|
||||||
"openapi": "3.0.3",
|
|
||||||
"info": {"title": "Codegen Test API", "version": "1.0.0"},
|
|
||||||
"paths": paths,
|
|
||||||
}
|
|
||||||
if schemas is not None:
|
|
||||||
spec["components"] = {"schemas": schemas}
|
|
||||||
return spec
|
|
||||||
|
|
||||||
|
|
||||||
def test_flat_output_names_files_by_resource(tmp_path):
|
|
||||||
spec = _write_spec(
|
|
||||||
tmp_path,
|
|
||||||
_spec(
|
|
||||||
{
|
|
||||||
"/items": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "list_items",
|
|
||||||
"responses": {"200": {"description": "OK"}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
files = generate_routes(spec, tmp_path / "routes")
|
|
||||||
assert [f.name for f in files] == ["items.py"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_resource_name_is_sanitized_to_identifier(tmp_path):
|
|
||||||
spec = _write_spec(
|
|
||||||
tmp_path,
|
|
||||||
_spec(
|
|
||||||
{
|
|
||||||
"/fetch-requests": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "list_fetch_requests",
|
|
||||||
"responses": {"200": {"description": "OK"}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
files = generate_routes(spec, tmp_path / "routes")
|
|
||||||
assert [f.name for f in files] == ["fetch_requests.py"]
|
|
||||||
source = files[0].read_text(encoding="utf-8")
|
|
||||||
compile(source, str(files[0]), "exec")
|
|
||||||
|
|
||||||
|
|
||||||
def test_inject_request_prepends_request_param(tmp_path):
|
|
||||||
spec = _write_spec(
|
|
||||||
tmp_path,
|
|
||||||
_spec(
|
|
||||||
{
|
|
||||||
"/items": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "list_items",
|
|
||||||
"responses": {"200": {"description": "OK"}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
files = generate_routes(spec, tmp_path / "routes", inject_request=True)
|
|
||||||
source = files[0].read_text(encoding="utf-8")
|
|
||||||
assert "from fastapi import Request" in source or "Request" in source
|
|
||||||
assert "def list_items(request: Request):" in source
|
|
||||||
|
|
||||||
|
|
||||||
def test_package_layout_emits_routes_package(tmp_path):
|
|
||||||
spec = _write_spec(
|
|
||||||
tmp_path,
|
|
||||||
_spec(
|
|
||||||
{
|
|
||||||
"/items/{id}": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "get_item",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": True,
|
|
||||||
"schema": {"type": "string"},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {"200": {"description": "OK"}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
out = tmp_path / "interfaces"
|
|
||||||
files = generate_routes(spec, out, package=True)
|
|
||||||
assert files[0] == out / "items" / "routes.py"
|
|
||||||
assert (out / "items" / "__init__.py").exists()
|
|
||||||
assert (out / "__init__.py").exists()
|
|
||||||
assert "def get_item(id: str):" in files[0].read_text(encoding="utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
def test_multipart_body_expands_to_uploadfile(tmp_path):
|
|
||||||
spec = _write_spec(
|
|
||||||
tmp_path,
|
|
||||||
_spec(
|
|
||||||
{
|
|
||||||
"/pets/{id}/photo": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "upload_pet_photo",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"in": "path",
|
|
||||||
"required": True,
|
|
||||||
"schema": {"type": "string"},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"required": True,
|
|
||||||
"content": {
|
|
||||||
"multipart/form-data": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"required": ["file"],
|
|
||||||
"properties": {
|
|
||||||
"file": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "binary",
|
|
||||||
},
|
|
||||||
"caption": {"type": "string"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"responses": {"200": {"description": "OK"}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
files = generate_routes(spec, tmp_path / "routes")
|
|
||||||
source = files[0].read_text(encoding="utf-8")
|
|
||||||
assert "UploadFile" in source
|
|
||||||
assert "file: UploadFile = File(...)" in source
|
|
||||||
assert "caption: str = Form(None)" in source
|
|
||||||
assert "payload: dict" not in source
|
|
||||||
compile(source, str(files[0]), "exec")
|
|
||||||
|
|
||||||
|
|
||||||
def test_json_body_unchanged_by_default(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")
|
|
||||||
source = files[0].read_text(encoding="utf-8")
|
|
||||||
assert "payload: dict" in source
|
|
||||||
assert "response: Response" in source
|
|
||||||
|
|
||||||
|
|
||||||
def test_missing_operation_id_raises(tmp_path):
|
|
||||||
spec = _write_spec(
|
|
||||||
tmp_path,
|
|
||||||
_spec({"/items": {"get": {"responses": {"200": {"description": "OK"}}}}}),
|
|
||||||
)
|
|
||||||
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