Your project structure¶
sandloom new myapp creates a standalone project. Your code lives in it, and the
framework arrives as a dependency in pyproject.toml, so an upgrade changes a version
rather than your files.
myapp/
pyproject.toml # dependencies: sandloom, the standard modules
.env # local settings, not committed
.env.example # the template for .env
.env.test # settings for the tests
myapp/
asgi.py # the composition root: builds the application
modules/
example/ # your first module; each module is one package here
shared/ # generated: contract stubs, DTO copies, event schemas
deploy/
docker-compose.dev.yml # Postgres and Redis for development and tests
docker-compose.yml # the monolith: migrations, app, worker, beat
Dockerfile
architecture_rules.yml # the import boundaries `sandloom check` enforces
.github/workflows/ci.yml # the same checks in CI
.pre-commit-config.yaml
AGENTS.md, CLAUDE.md # instructions for coding agents
.agents/, .claude/ # skills for coding agents
.sandloom/inspection.json # generated: what the inspection commands read
What a module contains has its own page.
What you edit, and what you leave alone¶
| Files | Who changes them |
|---|---|
myapp/modules/ except the snapshots |
you, or sandloom add as a starting point |
.env, pyproject.toml, deploy/ |
you |
myapp/asgi.py |
rarely: a new module needs no change here |
myapp/shared/, each module's module.snapshot.json and routes.snapshot.json, .sandloom/inspection.json |
sandloom generate. Commit them and never edit them by hand |
sandloom test checks the generated files and never writes them. If it reports them out
of date, run sandloom generate.
What comes from the framework¶
| Package | What it gives you |
|---|---|
sandloom |
sandloom.sdk, the only framework API your modules import, plus the CLI |
sandloom-identity, sandloom-tenancy, sandloom-billing |
the standard modules, if you chose them |
Choose the standard modules when you create the project: --with identity,tenancy,billing
for any subset, or --no-batteries for none. All options are in the
CLI reference.
A standard module runs in your application but stays in its package. Your modules reach it
through the contract stubs in myapp/shared/, the same way they reach each other. To
change its behaviour, see Customize a standard module.
To move to a new framework version, see Upgrade an application.
How the application starts¶
myapp/asgi.py loads every package in myapp/modules that defines register, plus the
standard modules you chose, so a new module is picked up on its own. Set
SANDLOOM_MODULES to load a subset, which is how one codebase
runs as several services.