feat(standards): codify codegen, ship generic Tier-B infra, add get_app_template + prompts; fix docs:// regressions

P0: fix doc:// -> docs:// across standards src/server/registry and regenerate stale mongo-ops bundle. P1: build recipe + spec-first routing now document the openapi-first codegen fast path (models/routes with --inject-request/--package). P4: CRUD template documents the model-vs-document id rule (BaseDocument owns id). P5: authless guidance (omit security keys). P6: template gains generic infra (http_cache, keyed SSE bus, KV port+in-memory+Redis), interfaces/openapi.py + .dockerignore + generic build test registered as components, and a get_app_template tool; genericity matrix marks LLM pool + human-in-loop as reference-app patterns. P7: intake/build/start_app/trial prompts. P8: template ships tests/integration/test_openapi_app_builds.py. Rebuilt standards + openapi-first bundles and re-collected.
This commit is contained in:
2026-10-13 03:26:51 +05:30
parent ef951a1913
commit fced40af59
109 changed files with 4082 additions and 5885 deletions

View File

@@ -655,12 +655,27 @@
</li>
<li class="md-nav__item">
<a href="#3-route-generation-verification" class="md-nav__link">
<a href="#3-route-generation" class="md-nav__link">
<span class="md-ellipsis">
🔁 3. Route Generation / Verification
🔁 3. Route Generation
</span>
</a>
<nav class="md-nav" aria-label="🔁 3. Route Generation">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#resource-names-and-extra-media-types" class="md-nav__link">
<span class="md-ellipsis">
Resource names and extra media types
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
@@ -803,21 +818,21 @@
<h1 id="codegen-generate-models-routes-from-your-spec">Codegen — Generate Models &amp; Routes From Your Spec</h1>
<blockquote>
<p><strong>TL;DR</strong> — point <code>openapi-first codegen</code> at your spec and get <code>models</code> + <code>routes</code> scaffolds you can bind with one line. The codegen output is deterministic, and it's a <em>starting point</em> — not a maintained artifact.</p>
<p><strong>TL;DR</strong> — point <code>openapi-first models</code> / <code>openapi-first routes</code> at your spec and get Pydantic models plus operation-matching route stubs. The output is deterministic and is a <em>starting point</em> — not a maintained artifact.</p>
</blockquote>
<hr />
<h2 id="1-why-celebrate-codegen">🧭 1. Why Celebrate Codegen?</h2>
<p>Two pain points kill OpenAPI projects:</p>
<ol>
<li><strong>The "write the spec, then write the same thing as Pydantic models" step</strong> — exactly where server/client param types drift (your route says <code>item_id: int</code>, your client sends a string…).</li>
<li><strong>Writing the docs/spec catalog by hand</strong>, once you have more than a handful of operations.</li>
<li><strong>Typing handler names by hand</strong> when <code>bind_routes</code> requires each <code>operationId</code> to resolve to a function of the exact same name.</li>
</ol>
<p>Codegen collapses both. One command, one source file, same generated shapes everywhere.</p>
<p>Codegen collapses both. One command, one source file, the same shapes everywhere.</p>
<hr />
<h2 id="2-model-generation">🚀 2. Model Generation</h2>
<div class="language-bash 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></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>openapi-first<span class="w"> </span>codegen<span class="w"> </span>models<span class="w"> </span>--module<span class="w"> </span>my_project.models<span class="w"> </span>--input<span class="w"> </span>openapi.yaml
<div class="language-bash 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></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>openapi-first<span class="w"> </span>models<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>app/_gen/models.py
</span></code></pre></div></td></tr></table></div>
<p>The generated model module mirrors the spec's <code>components.schemas</code> <strong>by name</strong>:</p>
<p>The generated module mirrors the spec's <code>components.schemas</code> <strong>by name</strong>:</p>
<table>
<thead>
<tr>
@@ -835,33 +850,61 @@
<td><code>class Item(BaseModel)</code></td>
</tr>
<tr>
<td>every <code>$ref</code> (schema)</td>
<td>a <code>type: ClassVar</code> alias ✓</td>
</tr>
<tr>
<td><code>required + type</code> from schema</td>
<td>Pydantic <code>Field(...)</code> / type hints ✓</td>
</tr>
</tbody>
</table>
<p><strong>Never hand-edit those files.</strong> If the spec changes, regenerate — just like you'd re-run <code>cargo build</code> after editing <code>Cargo.toml</code>.</p>
<p><strong>Never hand-edit those files.</strong> If the spec changes, regenerate — just like re-running a compiler after editing source.</p>
<h3 id="why-constructor-time-validation-still-applies">Why constructor-time validation still applies</h3>
<p>Codegen doesn't change the design: the generated models are plain Pydantic, and the <strong>client/server still validate against the spec</strong> at startup. Codegen is a <em>convenience accelerator</em> on top of the fail-fast guarantees in <a href="../../04_design/">04 – Design</a> and <a href="../../06_error_handling/">06 – Error Handling</a>.</p>
<blockquote>
<p><strong>Wire model vs. domain model.</strong> Generated models are the <strong>wire contract</strong>. Keep them separate from your persisted/domain model (see the standards <em>CRUD Template</em>): give the wire schema an <code>id: str</code>, but model the Mongo document so the id is optional.</p>
</blockquote>
<hr />
<h2 id="3-route-generation-verification">🔁 3. Route Generation / Verification</h2>
<h2 id="3-route-generation">🔁 3. Route Generation</h2>
<div class="language-bash 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>
<span class="normal"><a href="#__codelineno-1-5">5</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"># dry-run verification against your routes module</span>
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a>openapi-first<span class="w"> </span>codegen<span class="w"> </span>routes<span class="w"> </span>--module<span class="w"> </span>my_routes<span class="w"> </span>--input<span class="w"> </span>openapi.yaml<span class="w"> </span>--check
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3"></a>
</span><span id="__span-1-4"><a id="__codelineno-1-4" name="__codelineno-1-4"></a><span class="c1"># scaffold an operation skeleton (generates the handler with a TODO)</span>
</span><span id="__span-1-5"><a id="__codelineno-1-5" name="__codelineno-1-5"></a>openapi-first<span class="w"> </span>codegen<span class="w"> </span>routes<span class="w"> </span>--module<span class="w"> </span>my_routes<span class="w"> </span>--input<span class="w"> </span>openapi.yaml
<span class="normal"><a href="#__codelineno-1-3">3</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>openapi-first<span class="w"> </span>routes<span class="w"> </span>openapi.yaml<span class="w"> </span>-o<span class="w"> </span>app/interfaces<span class="w"> </span><span class="se">\</span>
</span><span id="__span-1-2"><a id="__codelineno-1-2" name="__codelineno-1-2"></a><span class="w"> </span>--use-models<span class="w"> </span>--models-module<span class="w"> </span>app._gen.models<span class="w"> </span><span class="se">\</span>
</span><span id="__span-1-3"><a id="__codelineno-1-3" name="__codelineno-1-3"></a><span class="w"> </span>--inject-request<span class="w"> </span>--package
</span></code></pre></div></td></tr></table></div>
<p>Why bother? Because <code>bind_routes</code> (in <a href="../../02_components/">02 – Components</a>) needs an <code>operationId</code> → handler <strong>exactly</strong> matching the spec. Codegen guarantees you never type a handler name wrong — it wears the same <code>operationId</code> as the spec says.</p>
<p>One file per resource (derived from the first path segment). Flags:</p>
<table>
<thead>
<tr>
<th>Flag</th>
<th>Effect</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--use-models</code></td>
<td>Import request-body <code>$ref</code> models from <code>--models-module</code></td>
</tr>
<tr>
<td><code>--models-module MOD</code></td>
<td>Dotted module for those imports (default <code>models</code>)</td>
</tr>
<tr>
<td><code>--inject-request</code></td>
<td>Prepend <code>request: Request</code> to every handler (DI convention)</td>
</tr>
<tr>
<td><code>--package</code></td>
<td>Emit <code>&lt;resource&gt;/routes.py</code> packages (with <code>__init__.py</code>) instead of flat <code>&lt;resource&gt;.py</code></td>
</tr>
</tbody>
</table>
<p>Why bother? Because <code>bind_routes</code> (in <a href="../../02_components/">02 – Components</a>) needs an <code>operationId</code> → handler <strong>exactly</strong> matching the spec. Codegen guarantees you never type a handler name wrong — it wears the same <code>operationId</code> the spec declares.</p>
<h3 id="resource-names-and-extra-media-types">Resource names and extra media types</h3>
<ul>
<li>A path such as <code>/fetch-requests</code> yields a <strong>sanitized</strong> module name <code>fetch_requests.py</code> (a raw hyphen could never be imported).</li>
<li><code>multipart/form-data</code> and <code>application/x-www-form-urlencoded</code> bodies expand to one parameter per schema property: <code>UploadFile = File(...)</code> for <code>format: binary</code>, <code>Form(...)</code> otherwise — not an opaque <code>payload: dict</code>. Fields are required only when listed in the schema's <code>required</code>.</li>
</ul>
<p>The stubs <code>raise NotImplementedError</code>; fill each body in (typically by resolving a service from <code>request.app.state</code>).</p>
<blockquote>
<p><strong>Note:</strong> <code>codegen</code> is build-time tooling. It does <strong>not</strong> run at runtime, and templates (the Bake-your-stuff section of <a href="../02_templates/">02 – Templates</a>) make scaffolding a server out-of-the-box even simpler for greenfield projects.</p>
<p><strong>Note:</strong> <code>models</code> / <code>routes</code> are build-time tooling. They do <strong>not</strong> run at runtime, and templates (the Bake-your-stuff section of <a href="../02_templates/">02 – Templates</a>) scaffold a working server out-of-the-box for greenfield projects.</p>
</blockquote>
<hr />
<h2 id="4-idempotent-output">🧪 4. Idempotent Output</h2>