add spec-driven auth dependency injection

- security.py: parse securitySchemes, resolve {ENV_VAR} from env,
  generate FastAPI Depends for Bearer JWT introspection
- app.py: extract schemes, build deps, pass to binder at init
- binder.py: inject Depends() per operation based on spec's security
- __init__.py: export security module
- pyproject.toml: add httpx dependency
This commit is contained in:
2026-07-17 21:00:23 +05:30
parent 7d075b3904
commit a2169b2bb1
5 changed files with 186 additions and 11 deletions

View File

@@ -32,12 +32,19 @@ Notes:
- Interpret OpenAPI semantics beyond routing metadata.
"""
from typing import Any
from fastapi.routing import APIRoute
from .errors import MissingOperationHandler
def bind_routes(app, spec: dict, routes_module) -> None:
def bind_routes(
app,
spec: dict,
routes_module,
security_deps: dict[str, list[Any]] | None = None,
) -> None:
"""
Bind OpenAPI operations to FastAPI routes.
@@ -49,18 +56,24 @@ def bind_routes(app, spec: dict, routes_module) -> None:
routes_module (module):
Python module containing handler functions. Each handler's name MUST
exactly match an OpenAPI `operationId`.
security_deps (dict | None):
Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``
generated from the spec's ``securitySchemes`` and per-operation
``security`` fields.
Raises:
MissingOperationHandler:
If an `operationId` is missing from the spec or if no corresponding
If an ``operationId`` is missing from the spec or if no corresponding
handler function exists in the routes module.
Notes:
**Responsibilities:**
- Iterates through the OpenAPI specification paths and methods.
- Resolves each `operationId` to a handler function, and registers
a corresponding `APIRoute` on the FastAPI application.
- Resolves each ``operationId`` to a handler function, and registers
a corresponding ``APIRoute`` on the FastAPI application.
- Injects FastAPI ``Depends()`` for each security requirement found
on the operation or inherited from the top-level ``security`` field.
**Guarantees:**
@@ -70,6 +83,7 @@ def bind_routes(app, spec: dict, routes_module) -> None:
"""
paths = spec.get("paths", {})
security_deps = security_deps or {}
for path, methods in paths.items():
for http_method, operation in methods.items():
@@ -90,10 +104,14 @@ def bind_routes(app, spec: dict, routes_module) -> None:
operation_id=operation_id,
)
key = f"{http_method.upper()}:{path}"
deps = security_deps.get(key, [])
route = APIRoute(
path=path,
endpoint=endpoint,
methods=[http_method.upper()],
dependencies=deps,
name=operation_id,
)