- Restrict mkdocstrings generation to real Python packages (require __init__.py) - Add explicit documentation section for CLI scaffolding and templates - Generalize CLI to support multiple templates with dynamic discovery - Package templates correctly for importlib.resources access - Add fully documented health_app template (app entry point and handlers) - Fix setuptools package-data configuration for bundled templates These changes make documentation import-safe, clarify package boundaries, and provide a deterministic, OpenAPI-first scaffolding workflow via CLI.
223 lines
6.7 KiB
Python
223 lines
6.7 KiB
Python
"""
|
|
FastAPI OpenAPI First — strict OpenAPI-first application bootstrap for FastAPI.
|
|
|
|
FastAPI OpenAPI First is a **contract-first infrastructure library** that
|
|
enforces OpenAPI as the single source of truth for FastAPI services.
|
|
|
|
The library removes decorator-driven routing and replaces it with
|
|
deterministic, spec-driven application assembly. Every HTTP route,
|
|
method, and operation is defined in OpenAPI first and bound to Python
|
|
handlers explicitly via operationId.
|
|
|
|
The package is intentionally minimal and layered. Each module has a
|
|
single responsibility and exposes explicit contracts rather than
|
|
convenience facades.
|
|
|
|
----------------------------------------------------------------------
|
|
Architecture Overview
|
|
----------------------------------------------------------------------
|
|
|
|
The library is structured around four core responsibilities:
|
|
|
|
- loader: load and validate OpenAPI 3.x specifications (JSON/YAML)
|
|
- binder: bind OpenAPI operations to FastAPI routes via operationId
|
|
- app: OpenAPI-first FastAPI application bootstrap
|
|
- client: OpenAPI-first HTTP client driven by the same specification
|
|
- errors: explicit error hierarchy for contract violations
|
|
|
|
The package root acts as a **namespace**, not a facade. Consumers are
|
|
expected to import functionality explicitly from the appropriate module.
|
|
|
|
----------------------------------------------------------------------
|
|
Installation
|
|
----------------------------------------------------------------------
|
|
|
|
Install using pip:
|
|
|
|
pip install openapi-first
|
|
|
|
Or with Poetry:
|
|
|
|
poetry add openapi-first
|
|
|
|
Runtime dependencies are intentionally minimal:
|
|
- fastapi (server-side)
|
|
- httpx (client-side)
|
|
- openapi-spec-validator
|
|
- pyyaml (optional, for YAML specs)
|
|
|
|
The ASGI server (e.g., uvicorn) is an application-level dependency and is
|
|
not bundled with this library.
|
|
|
|
----------------------------------------------------------------------
|
|
Command-Line Interface (Scaffolding, Templates)
|
|
----------------------------------------------------------------------
|
|
|
|
FastAPI OpenAPI First ships with a small CLI for bootstrapping
|
|
OpenAPI-first FastAPI applications from bundled templates.
|
|
|
|
List available application templates:
|
|
|
|
openapi-first --list
|
|
|
|
Create a new application using the default template:
|
|
|
|
openapi-first
|
|
|
|
Create a new application using a specific template:
|
|
|
|
openapi-first health_app
|
|
|
|
Create a new application in a custom directory:
|
|
|
|
openapi-first health_app my-service
|
|
|
|
The CLI copies template files verbatim into the target directory.
|
|
No code is generated or modified beyond the copied scaffold.
|
|
|
|
----------------------------------------------------------------------
|
|
Server-Side Usage (OpenAPI → FastAPI)
|
|
----------------------------------------------------------------------
|
|
|
|
Minimal OpenAPI-first FastAPI application:
|
|
|
|
from openapi_first import app
|
|
import my_service.routes as routes
|
|
|
|
api = app.OpenAPIFirstApp(
|
|
openapi_path="openapi.yaml",
|
|
routes_module=routes,
|
|
title="My Service",
|
|
version="1.0.0",
|
|
)
|
|
|
|
# Run with:
|
|
# uvicorn my_service.main:api
|
|
|
|
Handler definitions (no decorators):
|
|
|
|
def get_health():
|
|
return {"status": "ok"}
|
|
|
|
OpenAPI snippet:
|
|
|
|
paths:
|
|
/health:
|
|
get:
|
|
operationId: get_health
|
|
responses:
|
|
"200":
|
|
description: OK
|
|
|
|
The binder guarantees:
|
|
- Every OpenAPI operationId has exactly one handler
|
|
- No undocumented routes exist
|
|
- All mismatches fail at application startup
|
|
|
|
----------------------------------------------------------------------
|
|
Client-Side Usage (OpenAPI → HTTP Client)
|
|
----------------------------------------------------------------------
|
|
|
|
The same OpenAPI specification can be used to construct a strict,
|
|
operationId-driven HTTP client.
|
|
|
|
Client construction:
|
|
|
|
from openapi_first.loader import load_openapi
|
|
from openapi_first.client import OpenAPIClient
|
|
|
|
spec = load_openapi("openapi.yaml")
|
|
|
|
client = OpenAPIClient(spec)
|
|
|
|
Calling operations (operationId is the API):
|
|
|
|
response = client.get_health()
|
|
assert response.status_code == 200
|
|
assert response.json() == {"status": "ok"}
|
|
|
|
Path parameters must match the OpenAPI specification exactly:
|
|
|
|
response = client.get_item(
|
|
path_params={"item_id": 1}
|
|
)
|
|
|
|
Request bodies are passed explicitly:
|
|
|
|
response = client.create_item(
|
|
body={"name": "Orange", "price": 0.8}
|
|
)
|
|
|
|
Client guarantees:
|
|
- One callable per OpenAPI operationId
|
|
- No hardcoded URLs or HTTP methods in user code
|
|
- Path and body parameters must match the spec exactly
|
|
- Invalid or incomplete OpenAPI specs fail at client construction time
|
|
- No schema inference or mutation is performed
|
|
|
|
The client is transport-level only and returns `httpx.Response`
|
|
objects directly. Response interpretation and validation are left to
|
|
the consumer or higher-level layers.
|
|
|
|
----------------------------------------------------------------------
|
|
Extensibility Model
|
|
----------------------------------------------------------------------
|
|
|
|
FastAPI OpenAPI First is designed to be extended via **explicit contracts**:
|
|
|
|
- Users MAY extend OpenAPI loading behavior (e.g. multi-file specs)
|
|
by wrapping or replacing `loader.load_openapi`
|
|
- Users MAY extend route binding behavior by building on top of
|
|
`binder.bind_routes`
|
|
- Users MAY layer additional validation (e.g. signature checks)
|
|
without modifying core modules
|
|
|
|
Users SHOULD NOT rely on FastAPI decorators for routing when using this
|
|
library. Mixing decorator-driven routes with OpenAPI-first routing
|
|
defeats the contract guarantees and is explicitly unsupported.
|
|
|
|
----------------------------------------------------------------------
|
|
Public API Surface
|
|
----------------------------------------------------------------------
|
|
|
|
The supported public API consists of the following top-level modules:
|
|
|
|
- openapi_first.app
|
|
- openapi_first.binder
|
|
- openapi_first.loader
|
|
- openapi_first.client
|
|
- openapi_first.errors
|
|
|
|
Classes and functions should be imported explicitly from these modules.
|
|
No individual symbols are re-exported at the package root.
|
|
|
|
----------------------------------------------------------------------
|
|
Design Guarantees
|
|
----------------------------------------------------------------------
|
|
|
|
- OpenAPI is the single source of truth
|
|
- No undocumented routes can exist
|
|
- No OpenAPI operation can exist without a handler or client callable
|
|
- All contract violations fail at application startup or client creation
|
|
- No hidden FastAPI magic or implicit behavior
|
|
- Deterministic, testable application assembly
|
|
- CI-friendly failure modes
|
|
|
|
FastAPI OpenAPI First favors correctness, explicitness, and contract
|
|
enforcement over convenience shortcuts.
|
|
"""
|
|
|
|
from . import app
|
|
from . import binder
|
|
from . import loader
|
|
from . import client
|
|
from . import errors
|
|
|
|
__all__ = [
|
|
"app",
|
|
"binder",
|
|
"loader",
|
|
"client",
|
|
"errors",
|
|
]
|