Skip to content

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 docs

Anatomy 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.py

See the module authoring guide for the full contract.

Where things get generated

FileGenerated byWhen
host/client_app/modules.generated.tsmake gen-pages (auto via make dev)Every time you add/remove a module page
host/client_app/modules.manifest.jsonmake gen-pagesSame
host/client_app/modules.generated.cssmake gen-pagesSame
host/migrations/versions/XXXX_*.pymake 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 in host/migrations/versions/. Autogenerate discovers every installed module's metadata via build_module_metadata() in host/alembic/env.py. Each module's first migration sets a branch_labels marker to enable alembic downgrade <module>@base.
  • No host-level api/ folder. REST endpoints are attached by each module via register_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's contracts/ so other modules import them by name — the reverse of a monolith's "shared schemas" directory.

Tools config that affects everyone

FileControls
pyproject.toml (root)Ruff rules, ty config, pytest markers (asyncio_mode=auto, -m 'not e2e'), SQLModel-related ty-false-positive suppressions
biome.jsonJS/TS linting + formatting
vitest.config.tsJS unit-test setup
MakefileThe interface. If it's not make <target>, it's not a day-to-day operation.
scripts/check_file_size.py300-line cap on .py/.ts/.tsx (exempts vendored shadcn components)

Released under the MIT License.