Files
docs/standards/wiki/search/search_index.json

1 line
11 KiB
JSON

{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"The Aetos Standard App Blueprint","text":"<p>A single, reusable architecture for backend applications in the Aetos ecosystem.</p> <p>Every application built with this blueprint has the same shape:</p> <ul> <li>a spec-first HTTP layer driven by an OpenAPI document;</li> <li>a domain layer of pure models, ports, and services;</li> <li>an infrastructure layer of adapters (MongoDB, cache, workers, LLM, mail);</li> <li>a composition root that wires everything in exactly one place;</li> <li>an optional pipeline layer for data-driven extraction/enrichment;</li> <li>an optional background worker that runs long-running jobs and reports progress.</li> </ul> <p>This blueprints is deliberately app-agnostic. It never names a concrete business domain. Any application \u2014 expenses, health metrics, inventory, analytics \u2014 is produced by filling in two axes only: the resource model and (if needed) the pipeline family.</p>"},{"location":"#how-to-read-this","title":"How to read this","text":"Page What it covers Overview The philosophy and the one-way dependency rule Layering Rules Which package may import which Composition Root The single wiring point Spec-First Routing OpenAPI as the source of truth CRUD Template The canonical resource slice Pipeline Scaffold Data-driven processing families Worker &amp; Progress Async jobs, status machine, streaming progress Human-in-the-Loop Pausing a job to ask a question Infra Adapters MongoDB, cache, stores, LLM, mail Testing Integration + unit conventions Build Recipe The ordered checklist to generate a new app Genericity Matrix What is reusable vs what you must write"},{"location":"#for-ai-agents","title":"For AI agents","text":"<p>This content is served as MCP resources under <code>docs://index</code>, <code>docs://nav</code>, and <code>docs://modules/{module}</code>. To build a new app:</p> <ol> <li>Read <code>build_recipe</code> first.</li> <li>Read <code>genericity_matrix</code> to know what you must author.</li> <li>Query the library registry (<code>find_library</code>) before writing any infrastructure yourself.</li> <li>Copy the <code>templates/</code> skeleton and fill the two app-specific axes.</li> </ol>"},{"location":"01_overview/","title":"Overview","text":""},{"location":"01_overview/#summary","title":"Summary","text":"<p>The blueprint is a hexagonal (ports and adapters) application with a spec-first web edge and a single composition root. It favors explicit wiring over implicit magic, and it prefers existing internal libraries over hand-written infrastructure.</p>"},{"location":"01_overview/#the-three-layers","title":"The three layers","text":"<pre><code>interfaces/ \u2190 HTTP edge: thin handlers bound to an OpenAPI operationId\ndomain/ \u2190 business core: models, ports (interfaces), services, orchestrators\ninfra/ \u2190 adapters: databases, caches, external clients, hardware\n</code></pre> <p>Plus two optional layers:</p> <pre><code>pipelines/ \u2190 data-driven transformation graphs (extraction, enrichment)\nscripts/ \u2190 operator scripts that reuse the composition root\n</code></pre>"},{"location":"01_overview/#core-principles","title":"Core principles","text":"<ol> <li>One-way dependencies. <code>interfaces -&gt; domain -&gt; infra</code> is allowed; the reverse is forbidden. The domain never imports a framework it does not own.</li> <li>Ports in the domain, adapters in infra. A service depends on an abstract port; the composition root injects a concrete adapter.</li> <li>One composition root. Nothing constructs its own dependencies. All wiring lives in one module (<code>init.py</code>) and is exposed as a flat resource registry.</li> <li>Spec-first. The HTTP contract is declared in an OpenAPI document. Route handlers are looked up by <code>operationId</code>. There are no decorator routers.</li> <li>Reuse the libraries. Pipeline orchestration, DI, Mongo repositories, ingestion, auth, and docs are provided by internal libraries. Do not reimplement them \u2014 see the library registry.</li> <li>Explicit state machines. Long-running work moves through explicit, durable statuses and can pause for human input.</li> </ol>"},{"location":"01_overview/#runtime-processes","title":"Runtime processes","text":"<p>A standard app may run as up to three processes that share the same composition root:</p> Process Entry point Responsibility API <code>main.py</code> serve HTTP; start/stop resources via lifespan Worker <code>worker.py</code> poll durable jobs, run pipelines, publish progress Scripts <code>python -m scripts.&lt;name&gt;</code> one-shot operator tasks"},{"location":"10_testing/","title":"Testing","text":""},{"location":"10_testing/#summary","title":"Summary","text":"<p>Two test suites: integration (the HTTP edge with fakes) and unit (pure domain and pipeline logic). Both run under pytest with asyncio auto mode.</p>"},{"location":"10_testing/#layout","title":"Layout","text":"<pre><code>tests/\n integration/ # TestClient(app) + mocked resources\n unit/ # models, converters, pipeline stages/roots\n conftest.py # fixtures\n</code></pre>"},{"location":"10_testing/#integration-tests","title":"Integration tests","text":"<ul> <li>Build the app with <code>TestClient</code>.</li> <li>Replace <code>app.state.resources</code> with fake repositories/services so no database is required.</li> <li>Patch the auth introspection call (<code>httpx.AsyncClient.post</code>) to return a canned identity.</li> <li>Assert status codes and bodies for list/get/create/update/delete, plus <code>400</code>, <code>404</code>, <code>204</code>, and the streaming endpoint's fallback.</li> </ul> <pre><code>client = TestClient(app)\napp.state.resources = {\"item_svc\": FakeItemService(), ...}\n\ndef test_create_item():\n r = client.post(\"/items\", json={\"name\": \"x\"})\n assert r.status_code == 200\n</code></pre>"},{"location":"10_testing/#unit-tests","title":"Unit tests","text":"<ul> <li>Domain models and converters: pure, no I/O.</li> <li>Pipeline stages: feed a fixture state, assert the transformed state.</li> <li>Assembled pipeline roots: fixture parity \u2014 replay a stored fixture input and compare against a golden output file.</li> </ul>"},{"location":"10_testing/#config-consistency-tests","title":"Config-consistency tests","text":"<p>For rule-driven pipelines, add a test that cross-references rules against the declared pattern registry: every flag emitted must be defined; every defined flag must be reachable. This catches drift without running the pipeline.</p>"},{"location":"10_testing/#conventions","title":"Conventions","text":"<ul> <li><code>asyncio_mode = \"auto\"</code>; no manual event loops.</li> <li>Fixtures are checked into the repo (inputs + golden outputs).</li> <li>Tests skip (not fail) when a required fixture is absent.</li> <li>Do not mock the domain; mock only the infrastructure edge.</li> </ul>"},{"location":"11_build_recipe/","title":"Build Recipe","text":""},{"location":"11_build_recipe/#summary","title":"Summary","text":"<p>Follow this ordered checklist to generate a new application of the standard shape. Every step is deterministic; only steps 2 and 7 require app-specific authoring.</p>"},{"location":"11_build_recipe/#0-query-the-registry","title":"0. Query the registry","text":"<p>Before writing anything, read the library registry and note which internal libraries provide DB, DI, routing, ingestion, auth, and docs. Do not reimplement them.</p>"},{"location":"11_build_recipe/#1-scaffold-the-repository","title":"1. Scaffold the repository","text":"<p>Copy the <code>templates/</code> skeleton. It provides: <code>pyproject.toml</code>, <code>openapi.yaml</code>, <code>main.py</code>, <code>init.py</code>, <code>worker.py</code>, <code>config.py</code>, <code>Dockerfile</code>, <code>docker-compose.yaml</code>, <code>.env.example</code>, and the package tree (<code>domain/ infra/ interfaces/ pipelines/ tests/ scripts/</code>).</p>"},{"location":"11_build_recipe/#2-author-the-spec-app-specific","title":"2. Author the spec (app-specific)","text":"<p>Write <code>openapi.yaml</code>: resources, schemas, <code>operationId</code>s, security, and any extension metadata. This is the contract.</p>"},{"location":"11_build_recipe/#3-write-each-crud-slice-app-specific","title":"3. Write each CRUD slice (app-specific)","text":"<p>For each resource, create the four-file slice (see CRUD Template): <code>models.py</code>, <code>ports/db.py</code>, <code>ports/service.py</code>, optional <code>converters.py</code>.</p>"},{"location":"11_build_recipe/#4-wire-the-composition-root","title":"4. Wire the composition root","text":"<p>In <code>init.py</code>: build leaf repositories, then dependent repositories, then services; add infrastructure adaptors (cache store, progress bus, stores, fetchers). Add <code>get_svc</code> overloads.</p>"},{"location":"11_build_recipe/#5-add-routes","title":"5. Add routes","text":"<p>Create <code>interfaces/&lt;resource&gt;/routes.py</code> (thin handlers) and re-export them in <code>interfaces/openapi.py</code>. Add a cache decorator to read endpoints.</p>"},{"location":"11_build_recipe/#6-bootstrap-the-app-and-worker","title":"6. Bootstrap the app and worker","text":"<p>Confirm <code>main.py</code> wires the OpenAPI-first app with the lifespan. If the app has long-running jobs, adapt <code>worker.py</code> to poll the relevant job collection.</p>"},{"location":"11_build_recipe/#7-add-a-pipeline-family-only-if-data-driven","title":"7. Add a pipeline family (only if data-driven)","text":"<p>Create <code>pipelines/&lt;family&gt;/</code> with <code>abc.py</code>, <code>impl.py</code>, <code>pipeline.yaml</code>, <code>resources/</code>, and register the root in <code>registry.py</code>. Add fixtures and a parity test.</p>"},{"location":"11_build_recipe/#8-add-infrastructure-as-needed","title":"8. Add infrastructure as needed","text":"<p>Only where the standard adaptors do not cover the need. Prefer library-backed adapters.</p>"},{"location":"11_build_recipe/#9-tests","title":"9. Tests","text":"<p>Add integration tests with fakes and unit tests for domain/pipeline logic; add a config-consistency test if using rule-driven pipelines.</p>"},{"location":"11_build_recipe/#10-scripts-and-seeds","title":"10. Scripts and seeds","text":"<p>Add operator scripts under <code>scripts/</code> that use <code>init()</code> / <code>get_svc()</code> / <code>shutdown()</code>.</p>"},{"location":"11_build_recipe/#11-docs","title":"11. Docs","text":"<p>Author GSDFC docstrings, generate the type stubs (<code>.pyi</code>), mark the package <code>py.typed</code>, and build docs.</p>"},{"location":"11_build_recipe/#definition-of-done","title":"Definition of done","text":"<ul> <li><code>python -c \"import main\"</code> succeeds.</li> <li><code>pytest</code> passes.</li> <li>The app serves the spec's operations; the worker drains its queue.</li> <li>No domain module imports a web framework.</li> <li>No infrastructure was reimplemented that a library already provides.</li> </ul>"}]}