You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(audit): correct DX matrix — 4/5 top gaps closed
Updates the 2026-04-18 FastAPI parity audit to reflect:
- Gap #1 (yield-dependencies) was INCORRECTLY flagged ⚠️ partial in
the original matrix. Second-pass re-verification confirmed full
support: _execute_dep_plan pushes generators onto a per-request
cleanup stack, app.py finalizer advances/closes each in reverse
order, test_generator_deps.py covers 6 cases. No code was needed.
- Gaps #2/#3/#5 are now ✅ (commits 14a7a28, 10b3655, de14afc).
- Gap #4 (OAuth2 scopes) remains the single open item from top-5.
Also adds a 'second-tier gaps' list of genuinely-missing items that
didn't make the top-5 cut, as a durable backlog for future passes.
HawkAPI currently **matches or exceeds FastAPI on ~85 % of the tutorial-level DX surface**, and meaningfully exceeds on differentiators FastAPI does not ship at all (API versioning, permission policies, built-in bulkhead/circuit-breaker/rate-limiter, observability, migration codemod, PEP 703 wheels).
11
+
HawkAPI **matches or exceeds FastAPI on ~90 % of the tutorial-level DX surface** and meaningfully exceeds on differentiators FastAPI does not ship at all (API versioning, permission policies, built-in bulkhead/circuit-breaker/rate-limiter, observability, migration codemod, PEP 703 wheels).
12
12
13
-
The five gaps below are the ones that hurt migration from a real FastAPI codebase the most — not because they are large engineering tasks, but because they are everywhere in FastAPI tutorials and copy-pasted into real production code:
13
+
The five gaps below were identified in the initial audit as the items hurting migration from a real FastAPI codebase the most. Four of the five are **closed** at this revision; one remains.
14
14
15
-
| # | Gap | Severity | Effort |
16
-
|---|---|---|---|
17
-
| 1 |**Yield-dependencies with per-request finalization**| Critical | M |
18
-
| 2 |**Route-level `dependencies=[Depends(...)]`** on decorators | Important | S |
19
-
| 3 |**`response_model_exclude_none/unset/defaults`** flags | Important | S |
20
-
| 4 |**OAuth2 scopes enforcement + OpenAPI reflection**| Important | M |
21
-
| 5 |**`status` module with HTTP_NNN constants**| Minor (cosmetic) | XS |
15
+
| # | Gap | Severity | Effort | Status |
16
+
|---|---|---|---|---|
17
+
| 1 |**Yield-dependencies with per-request finalization**| Critical | M | ✅ always shipped (see correction below) |
18
+
| 2 |**Route-level `dependencies=[Depends(...)]`** on decorators | Important | S | ✅ shipped (commit `14a7a28`) |
19
+
| 3 |**`response_model_exclude_none/unset/defaults`** flags | Important | S | ✅ shipped (commit `10b3655`) |
20
+
| 4 |**OAuth2 scopes enforcement + OpenAPI reflection**| Important | M | ❌ open — last remaining |
21
+
| 5 |**`status` module with HTTP_NNN constants**| Minor (cosmetic) | XS | ✅ shipped (commit `de14afc`) |
22
22
23
-
Everything else is either already present or a known out-of-scope differentiator. The non-gap surplus (built-in versioning, bulkhead, observability, codemod, etc.) is strong; the five items above close the last mile of "FastAPI users land on HawkAPI and nothing is missing."
23
+
**Correction (2026-04-18):** Gap #1 was incorrectly flagged ⚠️ partial in the original matrix. A second-pass re-verification confirmed yield-dependencies with per-request teardown are **fully working**: `src/hawkapi/di/resolver.py:_execute_dep_plan` pushes generators onto a per-request cleanup stack, and `src/hawkapi/app.py:534-548` advances (success) or closes (exception) every generator in reverse order. 6 tests in `tests/unit/test_generator_deps.py` cover sync/async generators, cleanup on error, and multi-gen ordering. No work needed for Gap #1.
|`response_model_exclude_none/unset/defaults`|[serialization/filters.py](../../src/hawkapi/serialization/filters.py) (commit `10b3655`) | ✅|Shipped in Gap #3; recursive over msgspec + Pydantic|
# After response is sent, session context manager exits; rolls back or commits.
194
-
```
195
-
196
-
This is THE pattern in FastAPI tutorials and production code for database sessions, HTTP clients, Redis connections, transactions. Every SQLAlchemy/Databases example depends on it.
197
-
198
-
**What HawkAPI has:** App-level `lifespan` context manager for long-lived resources. No per-request teardown.
199
-
200
-
**Work required:**
201
-
- Extend `src/hawkapi/di/param_plan.py` to detect generator/async-generator dependencies.
202
-
- Register each yield-dep on a per-request stack (push on entry, pop + run remaining code after the handler's response body is sent).
203
-
- Ensure cancellation and exception paths run the finalizers.
204
-
- Wire teardown to run AFTER response headers+body flushed (like FastAPI) so the client isn't blocked.
205
-
- Test: yield that raises during teardown — how should the error be reported? (FastAPI: logs, ignores.)
206
-
207
-
**Payoff:** Single biggest migration-friction item for any FastAPI user with a database.
208
-
209
-
---
210
-
211
-
### Gap #2 — Route-level `dependencies=[Depends(...)]`
Used pervasively for auth guards, audit-log writers, rate-limit increments that don't need a return value. Also accepted on `APIRouter(dependencies=[...])` for whole-router guards.
224
-
225
-
**What HawkAPI has:** Workaround via app-level hooks or middleware. No decorator-level kwarg.
226
-
227
-
**Work required:**
228
-
- Add `dependencies: Sequence[Depends] | None = None` kwarg to the route decorators in `src/hawkapi/routing/router.py`.
229
-
- Add the same kwarg on the `Router` class constructor and `include_router` call; merge router-level + route-level lists.
230
-
- On request: resolve each `Depends` before invoking the handler; any raised exception short-circuits (consistent with FastAPI behavior).
231
-
- Make the results *not* injected into the handler's signature — they're executed for side effects only.
232
-
- Tests: chain ordering, exceptions, interaction with yield-dependencies.
233
-
234
-
**Payoff:** Biggest ergonomic win for anyone who reads FastAPI auth examples and copy-pastes.
179
+
### Gaps #1, #2, #3, #5 — closed
235
180
236
-
---
237
-
238
-
### Gap #3 — `response_model_exclude_none / _unset / _defaults`
239
-
240
-
**Severity:** Important **Effort:** S
241
-
242
-
**What FastAPI has:**
243
-
244
-
```python
245
-
@app.get(
246
-
"/items/{id}",
247
-
response_model=Item,
248
-
response_model_exclude_none=True, # drop keys whose value is None
249
-
response_model_exclude_unset=True, # drop keys the user didn't set
250
-
response_model_exclude_defaults=True, # drop keys equal to their default
251
-
)
252
-
```
253
-
254
-
Used for:
255
-
- APIs where optional fields shouldn't serialize as `"field": null`
256
-
- Versioned responses where fields were added later and old clients shouldn't see them
257
-
- Admin vs public response shapes
258
-
259
-
**What HawkAPI has:**`response_model` accepted on routes; the three exclusion knobs are not wired through to the serializer.
260
-
261
-
**Work required:**
262
-
- Plumb three flags from route metadata → `src/hawkapi/serialization/encoder.py`.
263
-
- msgspec already supports field exclusion at encode time; map the flags onto its API.
264
-
- Tests per flag + combinations.
265
-
266
-
**Payoff:** Low-effort closure of a feature 70 % of FastAPI response_model users eventually reach for.
Pure cosmetic convenience re-exported from Starlette. Everyone uses it.
328
-
329
-
**What HawkAPI has:** Users hardcode `201`.
330
-
331
-
**Work required:**
332
-
- Create `src/hawkapi/status.py` re-exporting the standard HTTP constants (can copy from Starlette's `starlette.status`, or use `http.HTTPStatus`'s integer values).
333
-
- Export `status` from `hawkapi/__init__.py` so `from hawkapi import status` works.
334
-
- One-line unit test.
335
-
336
-
**Payoff:** Removes a paper cut. A one-hour job that eliminates a reason someone says "HawkAPI is missing things FastAPI has."
337
-
338
234
---
339
235
340
236
## Where HawkAPI already exceeds FastAPI
@@ -361,14 +257,28 @@ Not gaps — differentiators we should not accidentally dilute while closing the
361
257
362
258
---
363
259
364
-
## Follow-ups (not this project)
260
+
## Follow-ups
261
+
262
+
**Top-5 progress (4/5 closed):**
263
+
264
+
1. ✅ Gap #5 (`status` constants) — commit `de14afc`.
265
+
2. ✅ Gap #3 (`response_model_exclude_*`) — commit `10b3655`.
266
+
3. ✅ Gap #2 (`dependencies=[...]` kwarg) — commit `14a7a28`.
267
+
4. ✅ Gap #1 (yield-dependencies) — confirmed always shipped; no code change needed, audit corrected.
268
+
5. ❌ Gap #4 (OAuth2 scopes) — **next**.
365
269
366
-
Each of the top-5 gaps becomes its own design spec + implementation plan. Recommended order (smallest effort / highest ratio first, to accumulate shipped wins):
270
+
**Second-tier gaps (not in top-5 but worth tracking):**
367
271
368
-
1.**Gap #5** (`status` constants) — afternoon of work.
369
-
2.**Gap #3** (`response_model_exclude_*`) — one spec, small impl.
This order gives four visible DX-parity wins before the fifth (scopes) takes more effort. Each gets an independent cycle — spec → plan → implement — following the workflow used for Tier 1 and Tier 2.
284
+
Each becomes its own spec → plan → implement cycle if prioritized.
0 commit comments