Build: 0.1.8
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-03-08 01:01:39 +05:30
parent 8701bf92ac
commit 9191de9dff
200 changed files with 130793 additions and 18277 deletions

View File

@@ -84,7 +84,9 @@
<header class="md-header" data-md-component="header">
<header class="md-header md-header--shadow" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href="." title="openapi_first" class="md-header__button md-logo" aria-label="openapi_first" data-md-component="logo">
@@ -149,12 +151,19 @@
</label>
<nav class="md-search__options" aria-label="Search">
<a href="javascript:void(0)" class="md-search__icon md-icon" title="Share" aria-label="Share" data-clipboard data-clipboard-text="" data-md-component="search-share" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.15c-.05.21-.08.43-.08.66 0 1.61 1.31 2.91 2.92 2.91s2.92-1.3 2.92-2.91A2.92 2.92 0 0 0 18 16.08"/></svg>
</a>
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
<div class="md-search__suggest" data-md-component="search-suggest"></div>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
@@ -180,96 +189,6 @@
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item md-tabs__item--active">
<a href="." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="app/" class="md-tabs__link">
Application Bootstrap
</a>
</li>
<li class="md-tabs__item">
<a href="loader/" class="md-tabs__link">
Core Utilities
</a>
</li>
<li class="md-tabs__item">
<a href="client/" class="md-tabs__link">
OpenAPI Client
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
@@ -283,10 +202,8 @@
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<nav class="md-nav md-nav--primary" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href="." title="openapi_first" class="md-nav__button md-logo" aria-label="openapi_first" data-md-component="logo">
@@ -366,9 +283,9 @@
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#openapi_first--architecture-overview" class="md-nav__link">
<a href="#openapi_first--summary" class="md-nav__link">
<span class="md-ellipsis">
Architecture Overview
Summary
</span>
</a>
@@ -384,45 +301,27 @@
</li>
<li class="md-nav__item">
<a href="#openapi_first--command-line-interface-scaffolding-templates" class="md-nav__link">
<a href="#openapi_first--quick-start" class="md-nav__link">
<span class="md-ellipsis">
Command-Line Interface (Scaffolding, Templates)
Quick start
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--server-side-usage-openapi-fastapi" class="md-nav__link">
<a href="#openapi_first--architecture" class="md-nav__link">
<span class="md-ellipsis">
Server-Side Usage (OpenAPI → FastAPI)
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--client-side-usage-openapi-http-client" class="md-nav__link">
<a href="#openapi_first--public-api" class="md-nav__link">
<span class="md-ellipsis">
Client-Side Usage (OpenAPI → HTTP Client)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--extensibility-model" class="md-nav__link">
<span class="md-ellipsis">
Extensibility Model
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--public-api-surface" class="md-nav__link">
<span class="md-ellipsis">
Public API Surface
Public API
</span>
</a>
@@ -435,48 +334,6 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--core-philosophy" class="md-nav__link">
<span class="md-ellipsis">
Core Philosophy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--documentation-design" class="md-nav__link">
<span class="md-ellipsis">
Documentation Design
</span>
</a>
<nav class="md-nav" aria-label="Documentation Design">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#openapi_first--for-humans" class="md-nav__link">
<span class="md-ellipsis">
For Humans
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--for-llms" class="md-nav__link">
<span class="md-ellipsis">
For LLMs
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
@@ -500,13 +357,21 @@
<li class="md-nav__item md-nav__item--nested">
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
@@ -515,7 +380,7 @@
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_2" >
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="">
@@ -596,13 +461,21 @@
<li class="md-nav__item md-nav__item--nested">
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
@@ -611,7 +484,7 @@
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_3" >
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="0">
<label class="md-nav__link" for="__nav_3" id="__nav_3_label" tabindex="">
@@ -692,13 +565,19 @@
<li class="md-nav__item md-nav__item--nested">
<li class="md-nav__item md-nav__item--section md-nav__item--nested">
@@ -707,7 +586,7 @@
<input class="md-nav__toggle md-toggle md-toggle--indeterminate" type="checkbox" id="__nav_4" >
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="0">
<label class="md-nav__link" for="__nav_4" id="__nav_4_label" tabindex="">
@@ -794,9 +673,9 @@
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#openapi_first--architecture-overview" class="md-nav__link">
<a href="#openapi_first--summary" class="md-nav__link">
<span class="md-ellipsis">
Architecture Overview
Summary
</span>
</a>
@@ -812,45 +691,27 @@
</li>
<li class="md-nav__item">
<a href="#openapi_first--command-line-interface-scaffolding-templates" class="md-nav__link">
<a href="#openapi_first--quick-start" class="md-nav__link">
<span class="md-ellipsis">
Command-Line Interface (Scaffolding, Templates)
Quick start
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--server-side-usage-openapi-fastapi" class="md-nav__link">
<a href="#openapi_first--architecture" class="md-nav__link">
<span class="md-ellipsis">
Server-Side Usage (OpenAPI → FastAPI)
Architecture
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--client-side-usage-openapi-http-client" class="md-nav__link">
<a href="#openapi_first--public-api" class="md-nav__link">
<span class="md-ellipsis">
Client-Side Usage (OpenAPI → HTTP Client)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--extensibility-model" class="md-nav__link">
<span class="md-ellipsis">
Extensibility Model
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--public-api-surface" class="md-nav__link">
<span class="md-ellipsis">
Public API Surface
Public API
</span>
</a>
@@ -863,48 +724,6 @@
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--core-philosophy" class="md-nav__link">
<span class="md-ellipsis">
Core Philosophy
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--documentation-design" class="md-nav__link">
<span class="md-ellipsis">
Documentation Design
</span>
</a>
<nav class="md-nav" aria-label="Documentation Design">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#openapi_first--for-humans" class="md-nav__link">
<span class="md-ellipsis">
For Humans
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#openapi_first--for-llms" class="md-nav__link">
<span class="md-ellipsis">
For LLMs
</span>
</a>
</li>
</ul>
</nav>
</li>
</ul>
@@ -936,7 +755,7 @@
<h2 id="openapi_first" class="doc doc-heading">
<span class="doc doc-object-name doc-module-name">openapi_first</span>
<code class="doc-symbol doc-symbol-heading doc-symbol-module"></code> <span class="doc doc-object-name doc-module-name">openapi_first</span>
</h2>
@@ -944,155 +763,78 @@
<div class="doc doc-contents first">
<p>FastAPI OpenAPI First — strict OpenAPI-first application bootstrap for FastAPI.</p>
<hr />
<h4 id="openapi_first--summary">Summary</h4>
<p>FastAPI OpenAPI First is a <strong>contract-first infrastructure library</strong> that
enforces OpenAPI as the single source of truth for FastAPI services.</p>
<p>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.</p>
<p>The package is intentionally minimal and layered. Each module has a
single responsibility and exposes explicit contracts rather than
convenience facades.</p>
<hr />
<h4 id="openapi_first--architecture-overview">Architecture Overview</h4>
<p>The library is structured around four core responsibilities:</p>
<ul>
<li>loader: load and validate OpenAPI 3.x specifications (JSON/YAML)</li>
<li>binder: bind OpenAPI operations to FastAPI routes via operationId</li>
<li>app: OpenAPI-first FastAPI application bootstrap</li>
<li>client: OpenAPI-first HTTP client driven by the same specification</li>
<li>errors: explicit error hierarchy for contract violations</li>
</ul>
<p>The package root acts as a <strong>namespace</strong>, not a facade. Consumers are
expected to import functionality explicitly from the appropriate module.</p>
<hr />
<h4 id="openapi_first--installation">Installation</h4>
<p>Install using pip:</p>
<pre><code>pip install openapi-first
</code></pre>
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>pip install openapi-first
</code></pre></div></td></tr></table></div>
<p>Or with Poetry:</p>
<pre><code>poetry add openapi-first
</code></pre>
<p>Runtime dependencies are intentionally minimal:
- fastapi (server-side)
- httpx (client-side)
- openapi-spec-validator
- pyyaml (optional, for YAML specs)</p>
<p>The ASGI server (e.g., uvicorn) is an application-level dependency and is
not bundled with this library.</p>
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1</span></pre></div></td><td class="code"><div><pre><span></span><code>poetry add openapi-first
</code></pre></div></td></tr></table></div>
<hr />
<h4 id="openapi_first--command-line-interface-scaffolding-templates">Command-Line Interface (Scaffolding, Templates)</h4>
<p>FastAPI OpenAPI First ships with a small CLI for bootstrapping
OpenAPI-first FastAPI applications from bundled templates.</p>
<p>List available application templates:</p>
<pre><code>openapi-first --list
</code></pre>
<p>Create a new application using the default template:</p>
<pre><code>openapi-first
</code></pre>
<p>Create a new application using a specific template:</p>
<pre><code>openapi-first health_app
</code></pre>
<p>Create a new application in a custom directory:</p>
<pre><code>openapi-first health_app my-service
</code></pre>
<p>The CLI copies template files verbatim into the target directory.
No code is generated or modified beyond the copied scaffold.</p>
<hr />
<h4 id="openapi_first--server-side-usage-openapi-fastapi">Server-Side Usage (OpenAPI → FastAPI)</h4>
<h4 id="openapi_first--quick-start">Quick start</h4>
<p>Minimal OpenAPI-first FastAPI application:</p>
<pre><code>from openapi_first import app
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span>
<span class="normal">4</span>
<span class="normal">5</span>
<span class="normal">6</span>
<span class="normal">7</span>
<span class="normal">8</span>
<span class="normal">9</span></pre></div></td><td class="code"><div><pre><span></span><code>from openapi_first import app
import my_service.routes as routes
api = app.OpenAPIFirstApp(
openapi_path="openapi.yaml",
openapi_path=&quot;openapi.yaml&quot;,
routes_module=routes,
title="My Service",
version="1.0.0",
title=&quot;My Service&quot;,
version=&quot;1.0.0&quot;,
)
# Run with:
# uvicorn my_service.main:api
</code></pre>
<p>Handler definitions (no decorators):</p>
<pre><code>def get_health():
return {"status": "ok"}
</code></pre>
<p>OpenAPI snippet:</p>
<pre><code>paths:
/health:
get:
operationId: get_health
responses:
"200":
description: OK
</code></pre>
<p>The binder guarantees:
- Every OpenAPI operationId has exactly one handler
- No undocumented routes exist
- All mismatches fail at application startup</p>
<hr />
<h4 id="openapi_first--client-side-usage-openapi-http-client">Client-Side Usage (OpenAPI → HTTP Client)</h4>
<p>The same OpenAPI specification can be used to construct a strict,
operationId-driven HTTP client.</p>
<p>Client construction:</p>
<pre><code>from openapi_first.loader import load_openapi
</code></pre></div></td></tr></table></div>
<p>OperationId-driven HTTP client:</p>
<div class="language-text highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal">1</span>
<span class="normal">2</span>
<span class="normal">3</span>
<span class="normal">4</span>
<span class="normal">5</span>
<span class="normal">6</span>
<span class="normal">7</span></pre></div></td><td class="code"><div><pre><span></span><code>from openapi_first.loader import load_openapi
from openapi_first.client import OpenAPIClient
spec = load_openapi("openapi.yaml")
spec = load_openapi(&quot;openapi.yaml&quot;)
client = OpenAPIClient(spec)
</code></pre>
<p>Calling operations (operationId is the API):</p>
<pre><code>response = client.get_health()
assert response.status_code == 200
assert response.json() == {"status": "ok"}
</code></pre>
<p>Path parameters must match the OpenAPI specification exactly:</p>
<pre><code>response = client.get_item(
path_params={"item_id": 1}
)
</code></pre>
<p>Request bodies are passed explicitly:</p>
<pre><code>response = client.create_item(
body={"name": "Orange", "price": 0.8}
)
</code></pre>
<p>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</p>
<p>The client is transport-level only and returns <code>httpx.Response</code>
objects directly. Response interpretation and validation are left to
the consumer or higher-level layers.</p>
response = client.get_health()
</code></pre></div></td></tr></table></div>
<hr />
<h4 id="openapi_first--extensibility-model">Extensibility Model</h4>
<p>FastAPI OpenAPI First is designed to be extended via <strong>explicit contracts</strong>:</p>
<h4 id="openapi_first--architecture">Architecture</h4>
<p>The library is structured around four core responsibilities:</p>
<ul>
<li>Users MAY extend OpenAPI loading behavior (e.g. multi-file specs)
by wrapping or replacing <code>loader.load_openapi</code></li>
<li>Users MAY extend route binding behavior by building on top of
<code>binder.bind_routes</code></li>
<li>Users MAY layer additional validation (e.g. signature checks)
without modifying core modules</li>
<li><strong>loader</strong>: Load and validate OpenAPI 3.x specifications (JSON/YAML)</li>
<li><strong>binder</strong>: Bind OpenAPI operations to FastAPI routes via operationId</li>
<li><strong>app</strong>: OpenAPI-first FastAPI application bootstrap</li>
<li><strong>client</strong>: OpenAPI-first HTTP client driven by the same specification</li>
<li><strong>errors</strong>: Explicit error hierarchy for contract violations</li>
</ul>
<p>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.</p>
<hr />
<h4 id="openapi_first--public-api-surface">Public API Surface</h4>
<h4 id="openapi_first--public-api">Public API</h4>
<p>The supported public API consists of the following top-level modules:</p>
<ul>
<li>openapi_first.app</li>
<li>openapi_first.binder</li>
<li>openapi_first.loader</li>
<li>openapi_first.client</li>
<li>openapi_first.errors</li>
<li><code>openapi_first.app</code></li>
<li><code>openapi_first.binder</code></li>
<li><code>openapi_first.loader</code></li>
<li><code>openapi_first.client</code></li>
<li><code>openapi_first.errors</code></li>
</ul>
<p>Classes and functions should be imported explicitly from these modules.
No individual symbols are re-exported at the package root.</p>
<hr />
<h4 id="openapi_first--design-guarantees">Design Guarantees</h4>
<ul>
@@ -1102,30 +844,8 @@ No individual symbols are re-exported at the package root.</p>
<li>All contract violations fail at application startup or client creation</li>
<li>No hidden FastAPI magic or implicit behavior</li>
<li>Deterministic, testable application assembly</li>
<li>CI-friendly failure modes</li>
</ul>
<p>FastAPI OpenAPI First favors correctness, explicitness, and contract
enforcement over convenience shortcuts.</p>
<h4 id="openapi_first--core-philosophy">Core Philosophy</h4>
<p><code>FastAPI OpenAPI First</code> operates on the <strong>Contract-as-Code</strong> principle:</p>
<ol>
<li><strong>Spec-Driven Routing</strong>: The OpenAPI document <em>is</em> the router. Code only exists to fulfill established contracts.</li>
<li><strong>Startup Fail-Fast</strong>: Binding mismatches (missing handlers or extra operations) are detected during app initialization, not at runtime.</li>
<li><strong>Decoupled Symmetry</strong>: The same specification drives both the FastAPI server and the <code>httpx</code>-based client, ensuring type-safe communication.</li>
</ol>
<h4 id="openapi_first--documentation-design">Documentation Design</h4>
<p>Follow these "AI-Native" docstring principles to maximize developer and agent productivity:</p>
<h5 id="openapi_first--for-humans">For Humans</h5>
<ul>
<li><strong>Logical Grouping</strong>: Document the Loader, Binder, and Client as distinct infrastructure layers.</li>
<li><strong>Spec Snippets</strong>: Always include the corresponding OpenAPI YAML/JSON snippet alongside Python examples.</li>
</ul>
<h5 id="openapi_first--for-llms">For LLMs</h5>
<ul>
<li><strong>Full Path Linking</strong>: Refer to cross-module dependencies using their full dotted paths (e.g., <code>openapi_first.loader.load_openapi</code>).</li>
<li><strong>Complete Stubs</strong>: Maintain high-fidelity <code>.pyi</code> stubs for all public interfaces to provide an optimized machine-context.</li>
<li><strong>Traceable Errors</strong>: Use specific <code>: description</code> pairs in <code>Raises</code> blocks to allow agents to accurately map errors to spec violations.</li>
</ul>
<hr />
@@ -1145,7 +865,9 @@ enforcement over convenience shortcuts.</p>
</div>
</div>
</div><ul>
<li><a href="openapi_first/">Openapi First</a></li>
</ul>
@@ -1163,6 +885,8 @@ enforcement over convenience shortcuts.</p>
</div>
<script>var tabs=__md_get("__tabs");if(Array.isArray(tabs))e:for(var set of document.querySelectorAll(".tabbed-set")){var labels=set.querySelector(".tabbed-labels");for(var tab of tabs)for(var label of labels.getElementsByTagName("label"))if(label.innerText.trim()===tab){var input=document.getElementById(label.htmlFor);input.checked=!0;continue e}}</script>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
@@ -1200,7 +924,7 @@ enforcement over convenience shortcuts.</p>
<script id="__config" type="application/json">{"base": ".", "features": ["navigation.tabs", "navigation.expand", "navigation.top", "navigation.instant", "content.code.copy", "content.code.annotate"], "search": "assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script id="__config" type="application/json">{"base": ".", "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"], "search": "assets/javascripts/workers/search.973d3a69.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="assets/javascripts/bundle.f55a23d4.min.js"></script>