updated docs strings and added README.md
This commit is contained in:
@@ -1,71 +1,77 @@
|
||||
"""
|
||||
# Summary
|
||||
|
||||
FastAPI OpenAPI First — strict OpenAPI-first application bootstrap for FastAPI.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
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.
|
||||
handlers explicitly via `operationId`.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
# Installation
|
||||
|
||||
Install using pip:
|
||||
|
||||
pip install openapi-first
|
||||
```bash
|
||||
pip install openapi-first
|
||||
```
|
||||
|
||||
Or with Poetry:
|
||||
|
||||
poetry add openapi-first
|
||||
```bash
|
||||
poetry add openapi-first
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
# Quick Start
|
||||
|
||||
Minimal OpenAPI-first FastAPI application:
|
||||
|
||||
from openapi_first import app
|
||||
import my_service.routes as routes
|
||||
```python
|
||||
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",
|
||||
)
|
||||
api = app.OpenAPIFirstApp(
|
||||
openapi_path="openapi.yaml",
|
||||
routes_module=routes,
|
||||
title="My Service",
|
||||
version="1.0.0",
|
||||
)
|
||||
```
|
||||
|
||||
OperationId-driven HTTP client:
|
||||
|
||||
from openapi_first.loader import load_openapi
|
||||
from openapi_first.client import OpenAPIClient
|
||||
```python
|
||||
from openapi_first.loader import load_openapi
|
||||
from openapi_first.client import OpenAPIClient
|
||||
|
||||
spec = load_openapi("openapi.yaml")
|
||||
client = OpenAPIClient(spec)
|
||||
spec = load_openapi("openapi.yaml")
|
||||
client = OpenAPIClient(spec)
|
||||
|
||||
response = client.get_health()
|
||||
response = client.get_health()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
# Architecture
|
||||
|
||||
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
|
||||
- `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.
|
||||
|
||||
---
|
||||
|
||||
## Public API
|
||||
# Public API
|
||||
|
||||
The supported public API consists of the following top-level modules:
|
||||
|
||||
@@ -77,14 +83,14 @@ The supported public API consists of the following top-level modules:
|
||||
|
||||
---
|
||||
|
||||
## Design Guarantees
|
||||
# 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
|
||||
- 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.
|
||||
|
||||
---
|
||||
"""
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
"""
|
||||
# Summary
|
||||
|
||||
OpenAPI-first application bootstrap for FastAPI.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This module provides `OpenAPIFirstApp`, a thin but strict abstraction
|
||||
that enforces OpenAPI as the single source of truth for a FastAPI service.
|
||||
|
||||
Notes:
|
||||
**Core Principles:**
|
||||
|
||||
- The OpenAPI specification (JSON or YAML) defines the entire API surface
|
||||
- Every operationId in the OpenAPI spec must have a corresponding Python handler function
|
||||
- Handlers are plain Python callables (no FastAPI decorators)
|
||||
- FastAPI route registration is derived exclusively from the spec
|
||||
- FastAPI's autogenerated OpenAPI schema is fully overridden
|
||||
- The OpenAPI specification (JSON or YAML) defines the entire API surface.
|
||||
- Every `operationId` in the OpenAPI spec must have a corresponding
|
||||
Python handler function.
|
||||
- Handlers are plain Python callables (no FastAPI decorators).
|
||||
- FastAPI route registration is derived exclusively from the spec.
|
||||
- FastAPI's autogenerated OpenAPI schema is fully overridden.
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Loads and validates an OpenAPI 3.x specification
|
||||
- Dynamically binds HTTP routes to handler functions using operationId
|
||||
- Registers routes with FastAPI at application startup
|
||||
- Ensures runtime behavior matches the OpenAPI contract exactly
|
||||
- Loads and validates an OpenAPI 3.x specification.
|
||||
- Dynamically binds HTTP routes to handler functions using `operationId`.
|
||||
- Registers routes with FastAPI at application startup.
|
||||
- Ensures runtime behavior matches the OpenAPI contract exactly.
|
||||
|
||||
**Constraints:**
|
||||
|
||||
- This module intentionally does NOT: Generate OpenAPI specs, generate client code, introduce a new framework or lifecycle, or alter FastAPI dependency injection semantics.
|
||||
- This module intentionally does NOT:
|
||||
- Generate OpenAPI specs.
|
||||
- Generate client code.
|
||||
- Introduce a new framework or lifecycle.
|
||||
- Alter FastAPI dependency injection semantics.
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI
|
||||
@@ -42,16 +45,19 @@ class OpenAPIFirstApp(FastAPI):
|
||||
Notes:
|
||||
**Responsibilities:**
|
||||
|
||||
- `OpenAPIFirstApp` subclasses FastAPI and replaces manual route registration with OpenAPI-driven binding
|
||||
- All routes are derived from the provided OpenAPI specification, and each operationId is mapped to a Python function in the supplied routes module
|
||||
- `OpenAPIFirstApp` subclasses `FastAPI` and replaces manual route
|
||||
registration with OpenAPI-driven binding.
|
||||
- All routes are derived from the provided OpenAPI specification,
|
||||
and each `operationId` is mapped to a Python function in the
|
||||
supplied routes module.
|
||||
|
||||
**Guarantees:**
|
||||
|
||||
- No route can exist without an OpenAPI declaration
|
||||
- No OpenAPI operation can exist without a handler
|
||||
- Swagger UI and `/openapi.json` always reflect the provided spec
|
||||
- Handler functions remain framework-agnostic and testable
|
||||
|
||||
- No route can exist without an OpenAPI declaration.
|
||||
- No OpenAPI operation can exist without a handler.
|
||||
- Swagger UI and `/openapi.json` always reflect the provided spec.
|
||||
- Handler functions remain framework-agnostic and testable.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from openapi_first import OpenAPIFirstApp
|
||||
@@ -77,15 +83,20 @@ class OpenAPIFirstApp(FastAPI):
|
||||
|
||||
Args:
|
||||
openapi_path (str):
|
||||
Filesystem path to the OpenAPI 3.x specification file. This specification is treated as the authoritative API contract.
|
||||
Filesystem path to the OpenAPI 3.x specification file. This
|
||||
specification is treated as the authoritative API contract.
|
||||
routes_module (module):
|
||||
Python module containing handler functions whose names correspond exactly to OpenAPI operationId values.
|
||||
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).
|
||||
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.
|
||||
If the OpenAPI specification is invalid, or if any declared
|
||||
`operationId` does not have a corresponding handler function.
|
||||
"""
|
||||
# Initialize FastAPI normally
|
||||
super().__init__(**fastapi_kwargs)
|
||||
|
||||
@@ -1,33 +1,35 @@
|
||||
"""
|
||||
# Summary
|
||||
|
||||
OpenAPI-driven route binding for FastAPI.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This module is responsible for translating an OpenAPI 3.x specification
|
||||
into concrete FastAPI routes. It enforces a strict one-to-one mapping
|
||||
between OpenAPI operations and Python handler functions using operationId.
|
||||
between OpenAPI operations and Python handler functions using `operationId`.
|
||||
|
||||
Notes:
|
||||
**Core Responsibility:**
|
||||
|
||||
- Read path + method definitions from an OpenAPI specification
|
||||
- Resolve each operationId to a Python callable
|
||||
- Register routes with FastAPI using APIRoute
|
||||
- Fail fast when contract violations are detected
|
||||
- Read path + method definitions from an OpenAPI specification.
|
||||
- Resolve each `operationId` to a Python callable.
|
||||
- Register routes with FastAPI using `APIRoute`.
|
||||
- Fail fast when contract violations are detected.
|
||||
|
||||
**Design Constraints:**
|
||||
|
||||
- All routes MUST be declared in the OpenAPI specification
|
||||
- All OpenAPI operations MUST define an operationId
|
||||
- Every operationId MUST resolve to a handler function
|
||||
- Handlers are plain Python callables (no decorators required)
|
||||
- No implicit route creation or inference is allowed
|
||||
- All routes MUST be declared in the OpenAPI specification.
|
||||
- All OpenAPI operations MUST define an `operationId`.
|
||||
- Every `operationId` MUST resolve to a handler function.
|
||||
- Handlers are plain Python callables (no decorators required).
|
||||
- No implicit route creation or inference is allowed.
|
||||
|
||||
**Constraints:**
|
||||
|
||||
- This module intentionally does NOT: Perform request or response validation, generate Pydantic models, modify FastAPI dependency injection, or interpret OpenAPI semantics beyond routing metadata.
|
||||
- This module intentionally does NOT:
|
||||
- Perform request or response validation.
|
||||
- Generate Pydantic models.
|
||||
- Modify FastAPI dependency injection.
|
||||
- Interpret OpenAPI semantics beyond routing metadata.
|
||||
"""
|
||||
|
||||
from fastapi.routing import APIRoute
|
||||
@@ -45,21 +47,26 @@ def bind_routes(app, spec: dict, routes_module) -> None:
|
||||
spec (dict):
|
||||
Parsed OpenAPI 3.x specification dictionary.
|
||||
routes_module (module):
|
||||
Python module containing handler functions. Each handler's name MUST exactly match an OpenAPI operationId.
|
||||
Python module containing handler functions. Each handler's name MUST
|
||||
exactly match an OpenAPI `operationId`.
|
||||
|
||||
Raises:
|
||||
MissingOperationHandler:
|
||||
If an operationId is missing from the spec or if no corresponding handler function exists in the routes module.
|
||||
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
|
||||
- Iterates through the OpenAPI specification paths and methods.
|
||||
- Resolves each `operationId` to a handler function, and registers
|
||||
a corresponding `APIRoute` on the FastAPI application.
|
||||
|
||||
**Guarantees:**
|
||||
|
||||
- Route registration is deterministic and spec-driven. No route decorators are required or supported. Handler resolution errors surface at application startup.
|
||||
- Route registration is deterministic and spec-driven. No route
|
||||
decorators are required or supported. Handler resolution errors
|
||||
surface at application startup.
|
||||
"""
|
||||
|
||||
paths = spec.get("paths", {})
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""
|
||||
# Summary
|
||||
|
||||
OpenAPI-first HTTP client for contract-driven services.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This module provides `OpenAPIClient`, a thin, strict HTTP client that
|
||||
derives all callable operations directly from an OpenAPI 3.x specification.
|
||||
|
||||
@@ -49,20 +47,22 @@ class OpenAPIClientError(OpenAPIFirstError):
|
||||
|
||||
class OpenAPIClient:
|
||||
"""
|
||||
OpenAPI-first HTTP client (httpx-based).
|
||||
OpenAPI-first HTTP client (`httpx`-based).
|
||||
|
||||
Notes:
|
||||
**Responsibilities:**
|
||||
|
||||
- This client derives all callable methods directly from an OpenAPI 3.x specification. Each operationId becomes a method on the client instance.
|
||||
- This client derives all callable methods directly from an
|
||||
OpenAPI 3.x specification. Each `operationId` becomes a method
|
||||
on the client instance.
|
||||
|
||||
**Guarantees:**
|
||||
|
||||
- One callable per operationId
|
||||
- Explicit parameters (path, query, headers, body)
|
||||
- No implicit schema inference or mutation
|
||||
- Returns raw `httpx.Response` objects
|
||||
- No response validation or deserialization
|
||||
- One callable per `operationId`.
|
||||
- Explicit parameters (path, query, headers, body).
|
||||
- No implicit schema inference or mutation.
|
||||
- Returns raw `httpx.Response` objects.
|
||||
- No response validation or deserialization.
|
||||
|
||||
Example:
|
||||
```python
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""
|
||||
# Summary
|
||||
|
||||
Exceptions for OpenAPI-first FastAPI applications.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This module defines a small hierarchy of explicit, intention-revealing
|
||||
exceptions used to signal contract violations between an OpenAPI
|
||||
specification and its Python implementation.
|
||||
@@ -12,10 +10,10 @@ specification and its Python implementation.
|
||||
Notes:
|
||||
**Design Principles:**
|
||||
|
||||
- Errors represent programmer mistakes, not runtime conditions
|
||||
- All errors are raised during application startup
|
||||
- Messages are actionable and suitable for CI/CD output
|
||||
- Exceptions are explicit rather than reused from generic built-ins
|
||||
- Errors represent programmer mistakes, not runtime conditions.
|
||||
- All errors are raised during application startup.
|
||||
- Messages are actionable and suitable for CI/CD output.
|
||||
- Exceptions are explicit rather than reused from generic built-ins.
|
||||
|
||||
These errors should normally cause immediate application failure.
|
||||
"""
|
||||
@@ -27,8 +25,11 @@ class OpenAPIFirstError(Exception):
|
||||
Notes:
|
||||
**Responsibilities:**
|
||||
|
||||
- This exception exists to allow callers, test suites, and CI pipelines to catch and distinguish OpenAPI contract violations from unrelated runtime errors
|
||||
- All exceptions raised by the OpenAPI-first core should inherit from this type
|
||||
- This exception exists to allow callers, test suites, and CI
|
||||
pipelines to catch and distinguish OpenAPI contract violations
|
||||
from unrelated runtime errors.
|
||||
- All exceptions raised by the OpenAPI-first core should inherit
|
||||
from this type.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -40,12 +41,15 @@ class MissingOperationHandler(OpenAPIFirstError):
|
||||
Notes:
|
||||
**Scenarios:**
|
||||
|
||||
- An OpenAPI operation does not define an operationId
|
||||
- An operationId is defined but no matching function exists in the provided routes module
|
||||
- An OpenAPI operation does not define an `operationId`.
|
||||
- An `operationId` is defined but no matching function exists in
|
||||
the provided routes module.
|
||||
|
||||
**Guarantees:**
|
||||
|
||||
- This represents a violation of the OpenAPI-first contract and indicates that the specification and implementation are out of sync
|
||||
- This represents a violation of the OpenAPI-first contract and
|
||||
indicates that the specification and implementation are out of
|
||||
sync.
|
||||
"""
|
||||
|
||||
def __init__(self, *, path: str, method: str, operation_id: str | None = None):
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""
|
||||
# Summary
|
||||
|
||||
OpenAPI specification loading and validation utilities.
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
This module is responsible for loading an OpenAPI 3.x specification
|
||||
from disk and validating it before it is used by the application.
|
||||
|
||||
@@ -14,14 +12,18 @@ must never reach the routing or runtime layers.
|
||||
Notes:
|
||||
**Design Principles:**
|
||||
|
||||
- OpenAPI is treated as an authoritative contract
|
||||
- Invalid specifications fail fast at application startup
|
||||
- Supported formats are JSON and YAML
|
||||
- Validation errors are surfaced clearly and early
|
||||
- OpenAPI is treated as an authoritative contract.
|
||||
- Invalid specifications fail fast at application startup.
|
||||
- Supported formats are JSON and YAML.
|
||||
- Validation errors are surfaced clearly and early.
|
||||
|
||||
**Constraints:**
|
||||
|
||||
- This module intentionally does NOT: Modify the OpenAPI document, infer missing fields, generate models or code, or perform request/response validation at runtime.
|
||||
- This module intentionally does NOT:
|
||||
- Modify the OpenAPI document.
|
||||
- Infer missing fields.
|
||||
- Generate models or code.
|
||||
- Perform request/response validation at runtime.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -41,7 +43,8 @@ class OpenAPISpecLoadError(OpenAPIFirstError):
|
||||
Notes:
|
||||
**Guarantees:**
|
||||
|
||||
- This error indicates that the OpenAPI document is unreadable, malformed, or violates the OpenAPI 3.x specification
|
||||
- This error indicates that the OpenAPI document is unreadable,
|
||||
malformed, or violates the OpenAPI 3.x specification.
|
||||
"""
|
||||
pass
|
||||
|
||||
@@ -52,7 +55,8 @@ def load_openapi(path: str | Path) -> dict[str, Any]:
|
||||
|
||||
Args:
|
||||
path (str | Path):
|
||||
Filesystem path to an OpenAPI specification file. Supported extensions: .json, .yaml, .yml.
|
||||
Filesystem path to an OpenAPI specification file. Supported
|
||||
extensions: `.json`, `.yaml`, `.yml`.
|
||||
|
||||
Returns:
|
||||
dict[str, Any]:
|
||||
@@ -60,13 +64,16 @@ def load_openapi(path: str | Path) -> dict[str, Any]:
|
||||
|
||||
Raises:
|
||||
OpenAPISpecLoadError:
|
||||
If the file does not exist, cannot be parsed, or fails OpenAPI schema validation.
|
||||
If the file does not exist, cannot be parsed, or fails OpenAPI
|
||||
schema validation.
|
||||
|
||||
Notes:
|
||||
**Guarantees:**
|
||||
|
||||
- The specification is parsed based on file extension and validated using a strict OpenAPI schema validator
|
||||
- Any error results in an immediate exception, preventing application startup
|
||||
- The specification is parsed based on file extension and validated
|
||||
using a strict OpenAPI schema validator.
|
||||
- Any error results in an immediate exception, preventing
|
||||
application startup.
|
||||
"""
|
||||
spec_path = Path(path)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user