feat(standards): fix F11-F16 and rebuild bundle (43 components) + trial findings
- F11 PopulateRule field_name/collection_name corrections - F12 service adaptor update handles PopulatingRepository - F13 nested-references section; F14 authless /users/me guidance - F15 template ruff-clean; F16 stop vendoring .ruff_cache, ship .gitignore - document --template-style in build_recipe/spec_first_routing; refresh collected openapi-first + standards bundle (12 modules, 43 components) - add trial.md and trial_findings.md (F1-F16, re-trial A/B evidence)
This commit is contained in:
@@ -76,11 +76,14 @@ Then:
|
||||
```bash
|
||||
openapi-first models openapi.yaml -o _gen/models.py
|
||||
openapi-first routes openapi.yaml -o _gen/routes \
|
||||
--use-models --models-module _gen.models --inject-request --package
|
||||
--use-models --models-module _gen.models --inject-request --package --template-style
|
||||
```
|
||||
`_gen/models.py` is the **wire contract** (Pydantic, one class per
|
||||
`components.schemas` entry). `_gen/routes/<resource>/routes.py` lists every
|
||||
`operationId` with its exact parameter signature. Treat `_gen/` as generated
|
||||
`operationId` with its exact parameter signature. `--template-style` makes
|
||||
those stubs `async def` with a `body` parameter, matching the handlers you
|
||||
keep in `interfaces/` (without it they are sync `def` stubs taking
|
||||
`payload`; both bind the same way). Treat `_gen/` as generated
|
||||
(never hand-edit it) — the canonical handlers live in `interfaces/`.
|
||||
|
||||
4. **Write each CRUD slice (app-specific)** — per resource create the four-file
|
||||
@@ -168,6 +171,10 @@ Rules of the road:
|
||||
`domain/<resource>/models.py` is your business shape. If the spec's schema has
|
||||
a required `id: str`, keep it off the domain model and let the document get its
|
||||
id from `BaseDocument`.
|
||||
- **Use `--template-style`.** Stubs are then `async def ...(request, body: ...)`
|
||||
matching `interfaces/`; without it they are sync `def ...(payload: ...)`.
|
||||
Either binds (the router introspects the signature), but `--template-style`
|
||||
keeps the scaffold consistent with the code you keep.
|
||||
- **Resource names are sanitized.** `/fetch-requests` → `fetch_requests.py`.
|
||||
- **Multipart bodies expand** to `UploadFile = File(...)` / `Form(...)` params.
|
||||
- **`_gen/` is disposable.** Regenerate on every spec change; `git diff` shows
|
||||
|
||||
@@ -61,6 +61,14 @@ async def create_item(request: Request, body: dict):
|
||||
return await svc.create(Item.model_validate(body))
|
||||
```
|
||||
|
||||
**Generated stubs.** `openapi-first routes` emits this exact shape (`async def`
|
||||
handlers taking `body`) when given `--template-style`; without that flag the
|
||||
stub is `def create_item(payload: ItemInput)` — sync, with a `payload`
|
||||
parameter. Both shapes bind identically: the router resolves handlers by
|
||||
`operationId` and introspects the FastAPI signature, and it never inspects the
|
||||
request body. Pass `--template-style` so the generated signatures match the
|
||||
handlers you keep in `interfaces/`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- `operationId` naming: `list_<resource>`, `create_<resource>`,
|
||||
@@ -84,6 +92,11 @@ just omit the security keys:
|
||||
global `security` block exists. Leaving every operation without a `security`
|
||||
key is the cleanest authless spec.
|
||||
|
||||
Profile endpoints (`/users/me`) and profile headers (`x-profile`) still exist in
|
||||
an authless app. Define a stand-in identity in the composition root — for
|
||||
example `get_current_user` returns or lazily creates a fixed "Demo User" — so
|
||||
handlers stay auth-agnostic and only the wiring changes when real auth arrives.
|
||||
|
||||
The handlers, services, and composition root are unchanged. When auth is needed
|
||||
later, add the global `security` block and an introspecting dependency; only
|
||||
handlers that must opt out get `security: []`.
|
||||
@@ -96,7 +109,7 @@ handlers that must opt out get `security: []`.
|
||||
```bash
|
||||
openapi-first models openapi.yaml -o _gen/models.py
|
||||
openapi-first routes openapi.yaml -o _gen/routes \
|
||||
--use-models --models-module _gen.models --inject-request --package
|
||||
--use-models --models-module _gen.models --inject-request --package --template-style
|
||||
```
|
||||
|
||||
Prefer codegen over hand-writing boilerplate. The result is the wire contract
|
||||
|
||||
@@ -69,11 +69,26 @@ def create_item_repo(*, owner_repo=None) -> ItemRepository:
|
||||
return repo_factory(
|
||||
collection="items",
|
||||
document=ItemDocument,
|
||||
populate_rules=[PopulateRule(field="owner", collection="users")],
|
||||
populate_rules=[PopulateRule(field_name="owner", collection_name="users")],
|
||||
cache=None, # or a CachedBaseRepository config
|
||||
)
|
||||
```
|
||||
|
||||
## Nested references
|
||||
|
||||
The wire contract often declares a reference as an expanded object
|
||||
(`expense.entity: Entity`), while the domain model stays id-free. Pick one
|
||||
convention and apply it consistently:
|
||||
|
||||
- **Embed (default for small, stable refs)** — store the object as a `dict` /
|
||||
`list[dict]` on the document; the API returns it verbatim. No populate wiring.
|
||||
- **Populate** — store an id and declare a `PopulateRule`; mongo-ops expands it
|
||||
on read. The referenced repo must be registered with the `PopulationEngine`.
|
||||
- **Id only** — store the id and let the client resolve it.
|
||||
|
||||
For populate-backed repositories change a reference with `update`, not `patch`:
|
||||
`patch` rejects changes to populated FK fields by design.
|
||||
|
||||
## ports/service.py
|
||||
|
||||
The service is built through the DI library, injecting the repository. The
|
||||
|
||||
@@ -27,7 +27,7 @@ Tier A / B / C split.
|
||||
- Referenced documents are described declaratively:
|
||||
|
||||
```python
|
||||
PopulateRule(field="owner", collection="users",
|
||||
PopulateRule(field_name="owner", collection_name="users",
|
||||
nested_rules=[], max_depth=2, projection=None)
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user