Project structure
text
simple_module_python/
├── framework/ # the framework itself (no knowledge of plugin modules)
│ ├── core/ # module system: discovery, events, diagnostics, ModuleBase
│ ├── db/ # create_module_base, mixins, session management, listeners
│ ├── hosting/ # create_app, middleware, settings, Inertia glue
│ └── testing/ # shared pytest fixtures distributed as a package
│
├── modules/ # plugin modules — one Python package each
│ ├── auth/ # session cookie, CSRF defences
│ ├── background_tasks/ # Celery broker + worker integration
│ ├── dashboard/ # authenticated landing page
│ ├── feature_flags/ # admin UI for flag toggles
│ ├── file_storage/ # pluggable storage backends (local, S3)
│ ├── permissions/ # role/permission admin UI
│ ├── settings/ # DB-backed module settings + admin UI
│ └── users/ # email+password auth, invites, bootstrap
│
├── host/ # the runnable application
│ ├── main.py # FastAPI entry point — create_app + lifespan
│ ├── routes.py # host-level routes (landing page)
│ ├── client_app/ # Vite + React root
│ │ ├── src/ # app shell, shared layout
│ │ ├── pages/ # host-level Inertia pages (Landing, NotFound)
│ │ └── modules.generated.ts # auto-generated page map — DO NOT EDIT
│ ├── migrations/ # Alembic migrations for ALL modules
│ ├── locales/ # host-level translation namespaces
│ └── alembic.ini # Alembic config — lives at repo root
│
├── packages/ # shared JS packages (npm workspaces)
│ └── ui/ # shadcn-based component library, layouts
│
├── scripts/
│ ├── new_module.py # module scaffolder — invoked by `make new-module`
│ └── check_file_size.py # 300-line cap enforcer
│
├── tests/
│ ├── e2e/ # Playwright tests (behind `-m e2e`)
│ └── … # framework-level pytest tests
│
├── docs/ # this documentation site (VitePress)
│ ├── .vitepress/ # nav/sidebar config
│ ├── guide/ # getting-started guides
│ ├── framework/ # framework deep dives
│ ├── database/ # DB model & migration docs
│ ├── frontend/ # Inertia, pages, shared props
│ ├── testing/ # pytest fixtures, E2E
│ ├── reference/ # commands, env vars, diagnostics
│ ├── plans/ # dated design docs (most recent = intended state)
│ ├── superpowers/ # spec/plan pairs from design sessions
│ └── release-notes/ # per-release notes
│
├── conftest.py # root pytest fixtures — app, db_session, client, …
├── Makefile # every day-to-day command lives here
├── pyproject.toml # workspace root (uv)
├── package.json # workspace root (npm)
├── docker-compose.yml # Postgres + Redis for dev
└── CLAUDE.md # assistant guidance — same conventions as these docsAnatomy of a module
Everything under modules/<name>/ follows the same shape:
text
modules/orders/
├── pyproject.toml # entry point: simple_module = "orders.module:OrdersModule"
├── package.json # npm workspace member (for page TSX + Biome)
├── tsconfig.json # TypeScript project for the pages
└── orders/
├── __init__.py
├── module.py # ModuleBase subclass with meta = ModuleMeta(...)
├── models.py # SQLModel tables (optional — some modules are UI-only)
├── contracts/ # SQLModel DTOs — the PUBLIC surface for other modules
│ └── schemas.py
├── service.py # business logic — takes AsyncSession, returns DTOs
├── deps.py # FastAPI dependencies (auth requirements, etc.)
├── endpoints/
│ ├── api.py # REST (JSON) endpoints
│ └── views.py # Inertia view endpoints
├── pages/ # *.tsx — auto-discovered by Vite via modules.generated.ts
│ ├── Browse.tsx
│ ├── Create.tsx
│ └── Edit.tsx
├── locales/
│ └── en.json # translations, namespaced by module name
└── tests/
├── test_api.py
└── test_service.pySee the module authoring guide for the full contract.
Where things get generated
| File | Generated by | When |
|---|---|---|
host/client_app/modules.generated.ts | make gen-pages (auto via make dev) | Every time you add/remove a module page |
host/client_app/modules.manifest.json | make gen-pages | Same |
host/client_app/modules.generated.css | make gen-pages | Same |
host/migrations/versions/XXXX_*.py | make migration msg="…" | When you add/change SQLModel tables |
modules/<name>/** | make new-module name=<name> | Scaffolding a new module |
Never hand-edit the .generated.* files — they are overwritten on the next make gen-pages.
Where things intentionally don't live
- No per-module
migrations/folder. All migrations live inhost/migrations/versions/. Autogenerate discovers every installed module's metadata viabuild_module_metadata()inhost/alembic/env.py. Each module's first migration sets abranch_labelsmarker to enablealembic downgrade <module>@base. - No host-level
api/folder. REST endpoints are attached by each module viaregister_routes(api_router, view_router)./api/*is the union of every module's API router. - No
schemas/top-level folder. DTOs live inside the owning module'scontracts/so other modules import them by name — the reverse of a monolith's "shared schemas" directory.
Tools config that affects everyone
| File | Controls |
|---|---|
pyproject.toml (root) | Ruff rules, ty config, pytest markers (asyncio_mode=auto, -m 'not e2e'), SQLModel-related ty-false-positive suppressions |
biome.json | JS/TS linting + formatting |
vitest.config.ts | JS unit-test setup |
Makefile | The interface. If it's not make <target>, it's not a day-to-day operation. |
scripts/check_file_size.py | 300-line cap on .py/.ts/.tsx (exempts vendored shadcn components) |