Skip to content

refactor: organize app.state into typed namespaces - #31

Merged
antosubash merged 22 commits into
mainfrom
feature/flamboyant-ardinghelli-753294
Apr 17, 2026
Merged

antosubash merged 22 commits into
mainfrom
feature/flamboyant-ardinghelli-753294

Conversation

@antosubash

Copy link
Copy Markdown
Owner

Summary

Consolidates 15+ loose app.state.* attributes into one typed slot per owner: framework gets app.state.sm: Services (a frozen dataclass), each module with state gets app.state.<module>: <Module>Services. Drops three duplicate locale fields and runtime drift-detection. Storage stays on app.state — only its shape changes.

Full design and rationale: docs/superpowers/specs/2026-04-17-app-state-organization-design.md.
Step-by-step execution plan: docs/superpowers/plans/2026-04-17-app-state-organization.md.

What changed

New API

  • simple_module_core.Services — frozen + slotted dataclass, 10 fields (settings, db, event_bus, menu_registry, permissions, feature_flags, health_registry, i18n_registry, inertia_config, modules).
  • users.services.UsersServices — module-scoped container for settings + mailer + rate limiters + roles cache.
  • Scaffolding now emits a services.py per new module via make new-module.

Kept loose on app.state (intentional, documented)

  • app.state.inertia_dependency — fastapi-inertia's request-scoped Depends factory.
  • app.state.migration — dev-only boot-time check result set in lifespan.

Deleted

  • app.state.settings_default_locale, settings_supported_locales, settings_cookie_name — pure duplicates of settings.i18n_*.
  • The state_before/state_after drift-detection block in create_app that used to back SM012.
  • app.state.inertia_config loose write — setup_inertia now returns the config; create_app passes it directly into Services(...).

Diagnostic update

  • SM012 re-aimed: warns when register_settings is overridden but app.state.<module_lower> wasn't populated.

Rollout shape

Shipped in 19 commits, each independently revertable, keeping main green at every boundary:

  1. Introduce Services + populate app.state.sm alongside existing writes.
  2. Migrate framework consumers (middleware, _error_handlers, health, inertia_deps, i18n_deps, permissions, lifespan block).
  3. Migrate module consumers (users module settings, mailer, rate limiters, roles cache, bootstrap).
  4. Delete the loose keys + duplicates + drift detection.
  5. Update scaffolding templates + docs/framework-conventions.md.

Docs-to-CLI check: make doctor still clean; no new framework diagnostics.

Test plan

  • make ci-python-lint — ruff format + check, 205 files clean
  • make ci-python-typecheck — ty, all checks passed
  • make ci-js-lint — biome, 117 files clean
  • make ci-js-typecheck — tsc for client app + 5 tsconfig projects
  • make ci-check-file-size — no files exceed 300 lines
  • make test-py — 562 passed, 4 deselected
  • make test-js — 8 passed
  • make doctor — exit 0, zero diagnostics
  • make build — vite build green
  • make test-e2e — not run locally (needs Docker + dev server + uv run playwright install chromium); CI will exercise

Notes for reviewers

  • The migration touches ~40 call-sites but the mechanical shape is uniform: request.app.state.<x> → request.app.state.sm.<x> (framework) or .users.<x> (module). Most commits are small and read straightforwardly.
  • Non-frozen UsersServices is deliberate — on_startup sets mailer/rate_limiter/roles_cache after register_settings. Convention (documented in framework-conventions.md) says "set once during boot, read-only after" without enforcement.
  • The scaffolding templates include the new pattern, so any make new-module name=... today adopts it out of the box.

Consolidate 15+ loose app.state attributes into one typed slot per
owner (framework + each module), delete three duplicate locale fields,
and drop runtime drift detection. Storage stays on app.state; only
the shape changes.
14-task staged migration from loose app.state keys to typed slots
(app.state.sm for framework + app.state.<module> for each module).
Each task is a single revertable commit.
Migrate all users-module consumers from loose app.state keys
(mailer, users_settings, rate_limiter, auth_throughput_limiter, event_bus, db)
to the structured UsersServices slot introduced in Task 7. Update two test
helpers that patched the old loose keys to patch app.state.users.* instead.
- Update conftest fixtures to access app.state.sm.db and app.state.sm.settings
- Update get_db FastAPI dependency to use app.state.sm.db
- Update test mocks and fixtures to use app.state.sm.* paths
- Update dashboard stats and products deps to use app.state.sm.*
- Update test assertions to verify sm instance instead of loose keys
Add services.py.tpl template and register_settings scaffolding so new
modules store settings inside a typed Services dataclass at
app.state.<module_lower> rather than the old app.state.<module>_settings
flat attribute. Update framework-conventions.md with Framework state and
Module settings subsections reflecting the new pattern.
@antosubash
antosubash merged commit 04b133f into main Apr 17, 2026
8 checks passed
@antosubash
antosubash deleted the feature/flamboyant-ardinghelli-753294 branch October 8, 2026 11:40
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