Skip to content

refactor: extract DB mixins, middleware handlers, and lifecycle hooks#2

Merged
Nate-Soul merged 1 commit into
mainfrom
feat/modularize-mixins-middleware-events
Jul 13, 2026
Merged

refactor: extract DB mixins, middleware handlers, and lifecycle hooks#2
Nate-Soul merged 1 commit into
mainfrom
feat/modularize-mixins-middleware-events

Conversation

@Nate-Soul

Copy link
Copy Markdown
Owner

What & why

Three structural splits to keep the boilerplate readable as it grows. No behavior change — all endpoints, security headers, and error envelopes are byte-identical (verified by the existing suite + a runtime smoke test).

1. Composable DB mixins (database/mixins.py)

TimestampedBase bundled UUID PK + timestamps + soft delete into one abstract base. Split into three independently-usable mixins — UUIDPrimaryKeyMixin, TimestampMixin, SoftDeleteMixin — with TimestampedBase now composing all three. base.py keeps only the primitives (Base, GUID, UTC helpers). Future models can pick columns à la carte:

class Widget(UUIDPrimaryKeyMixin, TimestampMixin, Base):  # no soft delete

2. CORS + exception handlers out of main.py

  • middleware/cors.py → configure_cors(app)
  • middleware/error_handler.py → register_exception_handlers(app) (the envelope helper + all four handlers + the slowapi 429 handler)
  • main.py is now a thin assembly file.

3. Lifecycle hooks (core/events.py)

Extracted the inline ASGI lifespan into named _startup / _shutdown hooks, now wrapped in try/finally so shutdown (engine dispose) runs even on teardown-after-error. Documented convention for slotting in Redis / queues / model warm-up later as fail-open steps.

Verification

  • ruff check . ✅
  • mypy app --ignore-missing-imports ✅
  • alembic upgrade head ✅ (schema unchanged — Alembic still reads Base.metadata)
  • pytest --cov=app --cov-fail-under=70 ✅ — 19 passed, 85% coverage
  • Runtime smoke: lifespan startup/shutdown fire, CORS + X-Request-ID + CSP headers present, error envelope intact

Three structural splits to keep the boilerplate readable as it grows — no
behavior change (all endpoints, headers, and error envelopes are byte-identical).

- database/mixins.py: split TimestampedBase into composable UUIDPrimaryKeyMixin,
  TimestampMixin, SoftDeleteMixin; TimestampedBase now composes all three. base.py
  keeps only Base + GUID + UTC helpers. Models can pick mixins a la carte.
- middleware/cors.py + middleware/error_handler.py: move the inline CORS setup and
  exception handlers out of main.py behind configure_cors(app) and
  register_exception_handlers(app).
- core/events.py: extract the ASGI lifespan into named _startup/_shutdown hooks,
  now wrapped in try/finally so shutdown runs on teardown-after-error. Shaped for
  Redis/queues/warm-up to slot in as fail-open steps later.

main.py is now a thin assembly file. README updated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Nate-Soul
Nate-Soul merged commit fee0c8f into main Jul 13, 2026
1 check passed
@Nate-Soul
Nate-Soul deleted the feat/modularize-mixins-middleware-events branch July 13, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant