Repository navigation
refactor: organize app.state into typed namespaces - #31
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidates 15+ loose
app.state.*attributes into one typed slot per owner: framework getsapp.state.sm: Services(a frozendataclass), each module with state getsapp.state.<module>: <Module>Services. Drops three duplicate locale fields and runtime drift-detection. Storage stays onapp.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.services.pyper new module viamake new-module.Kept loose on
app.state(intentional, documented)app.state.inertia_dependency— fastapi-inertia's request-scopedDependsfactory.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 ofsettings.i18n_*.state_before/state_afterdrift-detection block increate_appthat used to back SM012.app.state.inertia_configloose write —setup_inertianow returns the config;create_apppasses it directly intoServices(...).Diagnostic update
SM012re-aimed: warns whenregister_settingsis overridden butapp.state.<module_lower>wasn't populated.Rollout shape
Shipped in 19 commits, each independently revertable, keeping
maingreen at every boundary:Services+ populateapp.state.smalongside existing writes.middleware,_error_handlers,health,inertia_deps,i18n_deps,permissions, lifespan block).docs/framework-conventions.md.Docs-to-CLI check:
make doctorstill clean; no new framework diagnostics.Test plan
make ci-python-lint— ruff format + check, 205 files cleanmake ci-python-typecheck— ty, all checks passedmake ci-js-lint— biome, 117 files cleanmake ci-js-typecheck— tsc for client app + 5 tsconfig projectsmake ci-check-file-size— no files exceed 300 linesmake test-py— 562 passed, 4 deselectedmake test-js— 8 passedmake doctor— exit 0, zero diagnosticsmake build— vite build greenmake test-e2e— not run locally (needs Docker + dev server +uv run playwright install chromium); CI will exerciseNotes for reviewers
request.app.state.<x>→request.app.state.sm.<x>(framework) or.users.<x>(module). Most commits are small and read straightforwardly.UsersServicesis deliberate —on_startupsetsmailer/rate_limiter/roles_cacheafterregister_settings. Convention (documented inframework-conventions.md) says "set once during boot, read-only after" without enforcement.make new-module name=...today adopts it out of the box.