Files
docs/openapi-first/wiki/01_overview/index.html

1131 lines
35 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="prev" href="..">
<link rel="next" href="../02_components/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.6.23">
<title>Overview - openapi_first</title>
<link rel="stylesheet" href="../assets/stylesheets/main.84d31ad4.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.06af60db.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CJetBrains+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"JetBrains Mono"}</style>
<link rel="stylesheet" href="https://unpkg.com/dracula-prism/dist/css/dracula-prism.css">
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="blue-grey" data-md-color-accent="teal">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#overview-the-openapi-first-mental-model" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" 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">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 7H5a2 2 0 0 0-2 2v8h2v-4h2v4h2V9a2 2 0 0 0-2-2m0 4H5V9h2m7-2h-4v10h2v-4h2a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2m0 4h-2V9h2m6 0v6h1v2h-4v-2h1V9h-1V7h4v2Z"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
openapi_first
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Overview
</span>
</div>
</div>
</div>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</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>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<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">
<a href=".." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="./" class="md-tabs__link">
Overview
</a>
</li>
<li class="md-tabs__item">
<a href="../02_components/" class="md-tabs__link">
Components
</a>
</li>
<li class="md-tabs__item">
<a href="../03_use_cases/01_quickstart/" class="md-tabs__link">
Use Cases
</a>
</li>
<li class="md-tabs__item">
<a href="../04_design/" class="md-tabs__link">
Design
</a>
</li>
<li class="md-tabs__item">
<a href="../05_security/" class="md-tabs__link">
Security
</a>
</li>
<li class="md-tabs__item">
<a href="../06_error_handling/" class="md-tabs__link">
Error Handling
</a>
</li>
<li class="md-tabs__item">
<a href="../07_testing/" class="md-tabs__link">
Testing
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted md-nav--integrated" 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">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M7 7H5a2 2 0 0 0-2 2v8h2v-4h2v4h2V9a2 2 0 0 0-2-2m0 4H5V9h2m7-2h-4v10h2v-4h2a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2m0 4h-2V9h2m6 0v6h1v2h-4v-2h1V9h-1V7h4v2Z"/></svg>
</a>
openapi_first
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Overview
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Overview
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#1-the-mental-model" class="md-nav__link">
<span class="md-ellipsis">
🧠 1. The Mental Model
</span>
</a>
<nav class="md-nav" aria-label="🧠 1. The Mental Model">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#11-one-source-of-truth" class="md-nav__link">
<span class="md-ellipsis">
1.1 One source of truth
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#12-operationid-is-the-binding-key" class="md-nav__link">
<span class="md-ellipsis">
1.2 operationId is the binding key
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#13-fail-fast-by-design" class="md-nav__link">
<span class="md-ellipsis">
1.3 Fail-fast by design
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#2-what-it-looks-like" class="md-nav__link">
<span class="md-ellipsis">
⚙️ 2. What It Looks Like
</span>
</a>
<nav class="md-nav" aria-label="⚙️ 2. What It Looks Like">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#21-scaffold-an-application" class="md-nav__link">
<span class="md-ellipsis">
2.1 Scaffold an application
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#22-bootstrap-the-server" class="md-nav__link">
<span class="md-ellipsis">
2.2 Bootstrap the server
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#23-talk-to-it-with-the-client" class="md-nav__link">
<span class="md-ellipsis">
2.3 Talk to it with the client
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#3-where-things-live" class="md-nav__link">
<span class="md-ellipsis">
🧩 3. Where Things Live
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#4-the-non-goals" class="md-nav__link">
<span class="md-ellipsis">
📄 4. The Non-Goals
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#5-path-forward" class="md-nav__link">
<span class="md-ellipsis">
🧭 5. Path Forward
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#related" class="md-nav__link">
<span class="md-ellipsis">
Related
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../02_components/" class="md-nav__link">
<span class="md-ellipsis">
Components
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<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">
<span class="md-ellipsis">
Use Cases
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_4_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_4">
<span class="md-nav__icon md-icon"></span>
Use Cases
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../03_use_cases/01_quickstart/" class="md-nav__link">
<span class="md-ellipsis">
Quickstart
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../03_use_cases/02_templates/" class="md-nav__link">
<span class="md-ellipsis">
Templates
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../03_use_cases/03_client/" class="md-nav__link">
<span class="md-ellipsis">
Client
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../03_use_cases/04_codegen/" class="md-nav__link">
<span class="md-ellipsis">
Codegen
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../04_design/" class="md-nav__link">
<span class="md-ellipsis">
Design
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../05_security/" class="md-nav__link">
<span class="md-ellipsis">
Security
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../06_error_handling/" class="md-nav__link">
<span class="md-ellipsis">
Error Handling
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../07_testing/" class="md-nav__link">
<span class="md-ellipsis">
Testing
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="overview-the-openapi-first-mental-model">Overview — The OpenAPI-First Mental Model</h1>
<p><code>openapi-first</code> inverts the usual FastAPI workflow. Instead of decorating routes in code and letting FastAPI <em>invent</em> an OpenAPI document for you, you write <strong>one OpenAPI document first</strong> and let the library assemble both the application <strong>and</strong> the client from it. The spec is the contract; code is the implementation.</p>
<hr />
<h2 id="1-the-mental-model">🧠 1. The Mental Model</h2>
<h3 id="11-one-source-of-truth">1.1 One source of truth</h3>
<p>Your OpenAPI document (<code>openapi.yaml</code> or <code>openapi.json</code>) is the <strong>single authoritative contract</strong>:</p>
<div class="language-yaml highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-0-1"> 1</a></span>
<span class="normal"><a href="#__codelineno-0-2"> 2</a></span>
<span class="normal"><a href="#__codelineno-0-3"> 3</a></span>
<span class="normal"><a href="#__codelineno-0-4"> 4</a></span>
<span class="normal"><a href="#__codelineno-0-5"> 5</a></span>
<span class="normal"><a href="#__codelineno-0-6"> 6</a></span>
<span class="normal"><a href="#__codelineno-0-7"> 7</a></span>
<span class="normal"><a href="#__codelineno-0-8"> 8</a></span>
<span class="normal"><a href="#__codelineno-0-9"> 9</a></span>
<span class="normal"><a href="#__codelineno-0-10">10</a></span>
<span class="normal"><a href="#__codelineno-0-11">11</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1"></a><span class="nt">paths</span><span class="p">:</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2"></a><span class="w"> </span><span class="nt">/health</span><span class="p">:</span>
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3"></a><span class="w"> </span><span class="nt">get</span><span class="p">:</span>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4"></a><span class="w"> </span><span class="nt">operationId</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">get_health</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5"></a><span class="w"> </span><span class="nt">responses</span><span class="p">:</span>
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6"></a><span class="w"> </span><span class="s">"200"</span><span class="p p-Indicator">:</span>
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7"></a><span class="w"> </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">OK</span>
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8"></a><span class="w"> </span><span class="nt">content</span><span class="p">:</span>
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9"></a><span class="w"> </span><span class="nt">application/json</span><span class="p">:</span>
</span><span id="__span-0-10"><a id="__codelineno-0-10" name="__codelineno-0-10"></a><span class="w"> </span><span class="nt">schema</span><span class="p">:</span>
</span><span id="__span-0-11"><a id="__codelineno-0-11" name="__codelineno-0-11"></a><span class="w"> </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l l-Scalar l-Scalar-Plain">object</span>
</span></code></pre></div></td></tr></table></div>
<p>Every route, method, parameter, schema, and security requirement lives <strong>here</strong> — and only here. Code never declares routes.</p>
<h3 id="12-operationid-is-the-binding-key">1.2 <code>operationId</code> is the binding key</h3>
<p>The only bridge between the spec and your Python code is the <code>operationId</code>. Each operation maps, <strong>by name</strong>, to exactly one plain callable:</p>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-1-1">1</a></span>
<span class="normal"><a href="#__codelineno-1-2">2</a></span>
<span class="normal"><a href="#__codelineno-1-3">3</a></span>
<span class="normal"><a href="#__codelineno-1-4">4</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-1-1"><a id="__codelineno-1-1" name="__codelineno-1-1"></a><span class="c1"># routes.py</span>
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a><span class="k">def</span><span class="w"> </span><span class="nf">get_health</span><span class="p">():</span>
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3"></a><span class="w"> </span><span class="sd">"""Health check operation handler."""</span>
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4"></a> <span class="k">return</span> <span class="p">{</span><span class="s2">"status"</span><span class="p">:</span> <span class="s2">"ok"</span><span class="p">}</span>
</span></code></pre></div></td></tr></table></div>
<p><code>openapi_first</code> resolves <code>operationId: get_health</code><code>routes.get_health</code> and registers the route. <strong>No decorators, no <code>@app.get</code>, no routing metadata in code.</strong></p>
<p>Guarantees this binding provides:</p>
<ul>
<li>🚫 No undocumented route can exist — every route <em>must</em> be in the spec</li>
<li>🚫 No spec operation can go unhandled — every <code>operationId</code> <em>must</em> resolve at startup</li>
<li>🔒 No auth can be bypassed — security is injected from <code>securitySchemes</code> + per-operation <code>security</code>, spec-driven</li>
<li>🧱 No drift possible — the server and client are built from the <em>same</em> document</li>
</ul>
<h3 id="13-fail-fast-by-design">1.3 Fail-fast by design</h3>
<p>Contract violations are detected <strong>at application startup</strong> (or client construction), never silently at request time:</p>
<table>
<thead>
<tr>
<th>What goes wrong</th>
<th>When it fails</th>
</tr>
</thead>
<tbody>
<tr>
<td>Invalid / unloadable spec</td>
<td><code>load_openapi</code> at startup</td>
</tr>
<tr>
<td>Spec fails OpenAPI 3.x validation</td>
<td><code>load_openapi</code> at startup</td>
</tr>
<tr>
<td><code>operationId</code> missing a handler</td>
<td><code>bind_routes</code> at startup</td>
</tr>
<tr>
<td>Operation declared but no <code>operationId</code></td>
<td><code>bind_routes</code> at startup</td>
</tr>
<tr>
<td>Missing / duplicate <code>operationId</code> in client</td>
<td><code>OpenAPIClient(...)</code> construction</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="2-what-it-looks-like">⚙️ 2. What It Looks Like</h2>
<h3 id="21-scaffold-an-application">2.1 Scaffold an application</h3>
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-2-1">1</a></span>
<span class="normal"><a href="#__codelineno-2-2">2</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1"></a>openapi-first<span class="w"> </span>scaffold<span class="w"> </span>health_app<span class="w"> </span>my-health-service
</span><span id="__span-2-2"><a id="__codelineno-2-2" name="__codelineno-2-2"></a>openapi-first<span class="w"> </span>scaffold<span class="w"> </span>--list
</span></code></pre></div></td></tr></table></div>
<p><code>scaffold</code> copies a bundled template (verbatim — no code generation, no mutation) into a directory of your choice.</p>
<h3 id="22-bootstrap-the-server">2.2 Bootstrap the server</h3>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-3-1">1</a></span>
<span class="normal"><a href="#__codelineno-3-2">2</a></span>
<span class="normal"><a href="#__codelineno-3-3">3</a></span>
<span class="normal"><a href="#__codelineno-3-4">4</a></span>
<span class="normal"><a href="#__codelineno-3-5">5</a></span>
<span class="normal"><a href="#__codelineno-3-6">6</a></span>
<span class="normal"><a href="#__codelineno-3-7">7</a></span>
<span class="normal"><a href="#__codelineno-3-8">8</a></span>
<span class="normal"><a href="#__codelineno-3-9">9</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-3-1"><a id="__codelineno-3-1" name="__codelineno-3-1"></a><span class="c1"># main.py</span>
</span><span id="__span-3-2"><a id="__codelineno-3-2" name="__codelineno-3-2"></a><span class="kn">from</span><span class="w"> </span><span class="nn">openapi_first.app</span><span class="w"> </span><span class="kn">import</span> <span class="n">OpenAPIFirstApp</span>
</span><span id="__span-3-3"><a id="__codelineno-3-3" name="__codelineno-3-3"></a><span class="kn">import</span><span class="w"> </span><span class="nn">routes</span>
</span><span id="__span-3-4"><a id="__codelineno-3-4" name="__codelineno-3-4"></a>
</span><span id="__span-3-5"><a id="__codelineno-3-5" name="__codelineno-3-5"></a><span class="n">app</span> <span class="o">=</span> <span class="n">OpenAPIFirstApp</span><span class="p">(</span>
</span><span id="__span-3-6"><a id="__codelineno-3-6" name="__codelineno-3-6"></a> <span class="n">openapi_path</span><span class="o">=</span><span class="s2">"openapi.yaml"</span><span class="p">,</span>
</span><span id="__span-3-7"><a id="__codelineno-3-7" name="__codelineno-3-7"></a> <span class="n">routes_module</span><span class="o">=</span><span class="n">routes</span><span class="p">,</span>
</span><span id="__span-3-8"><a id="__codelineno-3-8" name="__codelineno-3-8"></a> <span class="n">title</span><span class="o">=</span><span class="s2">"My Service"</span><span class="p">,</span>
</span><span id="__span-3-9"><a id="__codelineno-3-9" name="__codelineno-3-9"></a><span class="p">)</span>
</span></code></pre></div></td></tr></table></div>
<p>Run with your FastAPI-compatible server (ASGI):</p>
<div class="language-bash highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-4-1">1</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-4-1"><a id="__codelineno-4-1" name="__codelineno-4-1"></a>uvicorn<span class="w"> </span>main:app<span class="w"> </span>--reload
</span></code></pre></div></td></tr></table></div>
<p>FastAPI itself drives the server; every route, response model, and security dependency comes from the spec. <code>/openapi.json</code> and Swagger UI always reflect the spec, byte-for-byte.</p>
<h3 id="23-talk-to-it-with-the-client">2.3 Talk to it with the client</h3>
<div class="language-python highlight"><table class="highlighttable"><tr><td class="linenos"><div class="linenodiv"><pre><span></span><span class="normal"><a href="#__codelineno-5-1"> 1</a></span>
<span class="normal"><a href="#__codelineno-5-2"> 2</a></span>
<span class="normal"><a href="#__codelineno-5-3"> 3</a></span>
<span class="normal"><a href="#__codelineno-5-4"> 4</a></span>
<span class="normal"><a href="#__codelineno-5-5"> 5</a></span>
<span class="normal"><a href="#__codelineno-5-6"> 6</a></span>
<span class="normal"><a href="#__codelineno-5-7"> 7</a></span>
<span class="normal"><a href="#__codelineno-5-8"> 8</a></span>
<span class="normal"><a href="#__codelineno-5-9"> 9</a></span>
<span class="normal"><a href="#__codelineno-5-10">10</a></span></pre></div></td><td class="code"><div><pre><span></span><code><span id="__span-5-1"><a id="__codelineno-5-1" name="__codelineno-5-1"></a><span class="c1"># client-side</span>
</span><span id="__span-5-2"><a id="__codelineno-5-2" name="__codelineno-5-2"></a><span class="kn">from</span><span class="w"> </span><span class="nn">openapi_first.loader</span><span class="w"> </span><span class="kn">import</span> <span class="n">load_openapi</span>
</span><span id="__span-5-3"><a id="__codelineno-5-3" name="__codelineno-5-3"></a><span class="kn">from</span><span class="w"> </span><span class="nn">openapi_first.client</span><span class="w"> </span><span class="kn">import</span> <span class="n">OpenAPIClient</span>
</span><span id="__span-5-4"><a id="__codelineno-5-4" name="__codelineno-5-4"></a>
</span><span id="__span-5-5"><a id="__codelineno-5-5" name="__codelineno-5-5"></a><span class="n">spec</span> <span class="o">=</span> <span class="n">load_openapi</span><span class="p">(</span><span class="s2">"openapi.yaml"</span><span class="p">)</span>
</span><span id="__span-5-6"><a id="__codelineno-5-6" name="__codelineno-5-6"></a><span class="n">client</span> <span class="o">=</span> <span class="n">OpenAPIClient</span><span class="p">(</span><span class="n">spec</span><span class="p">)</span>
</span><span id="__span-5-7"><a id="__codelineno-5-7" name="__codelineno-5-7"></a>
</span><span id="__span-5-8"><a id="__codelineno-5-8" name="__codelineno-5-8"></a><span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get_health</span><span class="p">()</span> <span class="c1"># operationId-driven call</span>
</span><span id="__span-5-9"><a id="__codelineno-5-9" name="__codelineno-5-9"></a><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">)</span> <span class="c1"># 200</span>
</span><span id="__span-5-10"><a id="__codelineno-5-10" name="__codelineno-5-10"></a><span class="nb">print</span><span class="p">(</span><span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">())</span> <span class="c1"># {"status": "ok"}</span>
</span></code></pre></div></td></tr></table></div>
<p><code>OpenAPIClient</code> builds one callable per <code>operationId</code> from the <em>same</em> spec, so the client can never drift from the server.</p>
<hr />
<h2 id="3-where-things-live">🧩 3. Where Things Live</h2>
<table>
<thead>
<tr>
<th>Concern</th>
<th>Module</th>
</tr>
</thead>
<tbody>
<tr>
<td>Load + validate spec</td>
<td><code>openapi_first.loader</code></td>
</tr>
<tr>
<td>Boot FastAPI app</td>
<td><code>openapi_first.app</code></td>
</tr>
<tr>
<td>Bind routes by opId</td>
<td><code>openapi_first.binder</code></td>
</tr>
<tr>
<td>HTTP client</td>
<td><code>openapi_first.client</code></td>
</tr>
<tr>
<td>Error hierarchy</td>
<td><code>openapi_first.errors</code></td>
</tr>
<tr>
<td>Security dependencies</td>
<td><code>openapi_first.security</code></td>
</tr>
<tr>
<td>Pydantic model codegen</td>
<td><code>openapi_first.codegen</code> (uses <code>datamodel_code_generator</code>)</td>
</tr>
<tr>
<td>Route stub codegen</td>
<td><code>openapi_first.codegen_routes</code></td>
</tr>
<tr>
<td>Models / routes CLI</td>
<td><code>openapi_first.cli</code></td>
</tr>
<tr>
<td>Bundled templates</td>
<td><code>openapi_first.templates</code></td>
</tr>
</tbody>
</table>
<p>Jurisdictions:</p>
<ul>
<li><code>loader</code>, <code>app</code>, <code>binder</code>, <code>client</code>, <code>errors</code>, <code>security</code> are the <strong>library API surface</strong> — stable, tested, documented.</li>
<li><code>templates</code> are <strong>copyable scaffolds</strong> — not part of the library API; excluded from lint/format/type gates, never imported at runtime.</li>
</ul>
<hr />
<h2 id="4-the-non-goals">📄 4. The Non-Goals</h2>
<p><code>openapi-first</code> deliberately does <strong>not</strong>:</p>
<ul>
<li>Parse decorators to generate an OpenAPI schema (that's default FastAPI behavior — the inverse)</li>
<li>Generate models from code at runtime (only at <strong>build time</strong> via CLI, from spec → Pydantic)</li>
<li>Validate request/response bodies against the spec at runtime (contract is enforced at startup / client construction; FastAPI + Pydantic handle runtime coercion)</li>
<li>Invent routing from path conventions — <code>operationId</code> binding only</li>
<li>Ship a production feature set in the bundled templates (they're demos/scaffolds: in-memory stores, no concurrency, no auth configured)</li>
</ul>
<hr />
<h2 id="5-path-forward">🧭 5. Path Forward</h2>
<p>New here? Start with <a href="../03_use_cases/01_quickstart/">01 Quickstart</a>. Want to copy a runnable app? Jump to <a href="../03_use_cases/02_templates/">02 Templates</a>. Digging into internals? See <a href="../04_design/">Design</a>.</p>
<hr />
<h2 id="related">Related</h2>
<ul>
<li><a href="../03_use_cases/01_quickstart/">01 Quickstart</a> · <a href="../02_components/">02 Components</a> · <a href="./">01 Overview</a></li>
</ul>
</article>
</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>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<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", "navigation.tabs", "toc.integrate", "header.autohide", "announce.dismiss", "footer.social", "content.code.select", "content.code.line_numbers", "content.tooltips"], "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>
</body>
</html>