pyproject.toml
Every moving part of SPLENT reads from pyproject.toml. It is the only place where you declare what something is, what version it is, and what it depends on.
Table of contents
Why it matters
SPLENT does not use separate config files for versioning, feature declarations, deployment metadata, UVL configuration, or feature contracts. Everything lives in pyproject.toml. This means:
- There is no version drift between what is declared and what is installed.
- The CLI, the framework, and the cache all read from the same file.
- A product’s entire composition — which features, at which version, for which environment — is readable in one place.
- A feature’s public interface (routes, blueprints, models, dependencies) is declared alongside its version.
In a product
A product’s pyproject.toml is the authoritative definition of what that product is and what it is made of.
[project]
name = "sample_splent_app"
version = "0.0.1"
requires-python = ">=3.13"
[project.optional-dependencies]
dev = ["watchdog"]
core = ["splent_framework"]
[tool.splent]
cli_version = "1.4.5"
spl = "sample_splent_spl"
# Features active in ALL environments
features = [
"splent-io/splent_feature_auth@v1.5.8",
"splent-io/splent_feature_public@v1.6.0",
"splent-io/splent_feature_redis@v1.5.6",
"splent-io/splent_feature_mail@v1.3.6",
"splent-io/splent_feature_confirmemail@v1.2.15",
"splent-io/splent_feature_profile@v1.5.7",
"splent-io/splent_feature_reset@v1.3.3",
"splent-io/splent_feature_session_redis@v1.0.7",
"splent-io/splent_feature_nginx@v1.0.0",
]
# Features active only in dev
features_dev = [
"splent-io/splent_feature_admin@v1.0.0",
"splent-io/splent_feature_phpmyadmin@v1.0.0",
"splent-io/splent_feature_mailhog@v1.0.7",
]
# Features active only in prod
features_prod = []
Feature groups
Features are declared under [tool.splent] — not in [project.optional-dependencies] — because SPLENT feature entries use the org/name@version format which is not PEP 508 compliant.
| Key | When loaded | Example |
|---|---|---|
features |
Always (all environments) | Core features needed everywhere |
features_dev |
Only when SPLENT_ENV=dev or --dev |
Debug panels, fake data generators, test seeders |
features_prod |
Only when SPLENT_ENV=prod or --prod |
Monitoring, caching, analytics |
When running with --dev, the effective feature list is features + features_dev.
When running with --prod, the effective feature list is features + features_prod.
Features present in both base and env-specific lists are deduplicated automatically.
Who reads this
| Consumer | What it reads | Why |
|---|---|---|
splent_framework |
[tool.splent].features + env-specific |
Discovers and loads features at runtime (in topological order) |
product:resolve |
[tool.splent].features + env-specific |
Clones and installs all declared features |
product:up / product:down |
[tool.splent].features + env-specific |
Starts/stops Docker containers for features |
feature:compile |
[tool.splent].features + env-specific |
Compiles webpack assets for active features |
feature:order |
[tool.splent].features + UVL |
Resolves and displays the topological load order |
db:upgrade / db:migrate |
[tool.splent].features + env-specific |
Applies migrations in dependency order |
db:seed |
[tool.splent].features + UVL |
Runs seeders in topological order |
feature:status |
[tool.splent].features + env-specific |
Shows which features from pyproject are tracked in manifest |
spl:* / product:validate / product:missing / product:auto-require |
[tool.splent].spl or [tool.splent.uvl] |
Resolves the UVL model from the catalog (preferred) or legacy per-product location |
doctor |
Multiple sections | Validates the full product configuration |
Legacy support: Products that still declare features under
[project.optional-dependencies].featurescontinue to work. The framework and CLI read from[tool.splent.features]first, then fall back to the legacy location.
In a feature
Each feature is its own Python package with its own pyproject.toml. It contains the package metadata and the feature contract — a machine-readable declaration of what the feature provides and requires.
[project]
name = "splent_feature_auth"
version = "1.2.2"
requires-python = ">=3.13"
[tool.setuptools]
package-dir = { "" = "src" }
[tool.setuptools.packages.find]
where = ["src"]
# ── Feature Contract (auto-generated) ─────────────────────────────────────────
[tool.splent.contract]
description = "Authentication feature: registration, login, logout, and session management"
[tool.splent.contract.provides]
routes = ["/login", "/logout", "/signup/"]
blueprints = ["auth_bp"]
models = ["User"]
commands = []
hooks = ["layout.anonymous_sidebar", "layout.authenticated_sidebar"]
services = ["AuthenticationService"]
signals = ["user-registered"]
translations = ["es"]
docker = []
[tool.splent.contract.requires]
features = []
env_vars = ["SECRET_KEY"]
signals = []
[tool.splent.contract.extensible]
services = ["AuthenticationService"]
models = ["User"]
templates = ["auth/login_form.html", "auth/signup_form.html"]
hooks = ["layout.anonymous_sidebar", "layout.authenticated_sidebar"]
routes = true
The feature contract
The [tool.splent.contract] section is auto-generated by splent feature:contract --write and kept up to date by splent feature:release. You do not need to maintain it manually.
| Field | Inferred from |
|---|---|
provides.routes |
@<bp>.route(...) decorators in routes.py |
provides.blueprints |
Blueprint variable names in __init__.py |
provides.models |
class <Name>(db.Model) in models.py |
provides.hooks |
register_template_hook("slot", func) calls in hooks.py |
provides.services |
class <Name>(BaseService) definitions in services.py |
provides.signals |
signal("name") definitions in services or signals.py |
provides.translations |
Language directories under translations/ |
provides.commands |
@click.command() definitions in commands.py |
provides.docker |
docker-compose*.yml / docker-compose*.yaml files at feature root |
requires.features |
Imports of other splent_feature_* packages |
requires.env_vars |
os.getenv(...) and os.environ[...] calls |
requires.signals |
@<signal>.connect subscriptions in signals.py |
The description field is preserved across releases — set it once and it stays.
env field
Features that only belong in a specific environment declare env in their contract:
[tool.splent.contract]
description = "phpMyAdmin database management UI for development"
env = "dev"
When env = "dev" is set:
feature:install,feature:add, andfeature:attachautomatically place the feature infeatures_dev(no need for--devflag)product:configureclassifies it correctlyproduct:buildexcludes it from production artifacts
Refinement section
Features that refine another feature declare it in [tool.splent.refinement]:
[tool.splent.refinement]
refines = "splent_feature_notes"
[tool.splent.refinement.extends]
models = [{ target = "Notes", mixin = "NotesTagsMixin" }]
[tool.splent.refinement.overrides]
services = [{ target = "NotesService", replacement = "NotesServiceWithTags" }]
See Refinement for the full explanation.
Docker contract
Features that ship Docker infrastructure declare it in [tool.splent.contract.docker]. This section is auto-inferred from the feature’s docker/docker-compose.yml by feature:contract --write and updated during feature:release.
[tool.splent.contract.docker]
services = ["splent_feature_redis"]
ports = ["${REDIS_HOST_PORT}:6379"]
volumes = []
networks = ["splent_network"]
build = false # true if the feature includes a Dockerfile
healthcheck = false # true if the service defines a health check
[tool.splent.contract.docker.depends_on]
services = [] # other feature Docker services this one requires
Port variables use the _HOST_PORT suffix so the port offset mechanism adjusts them automatically.
| Field | Description |
|---|---|
services |
Docker Compose service names defined by this feature |
ports |
Host-to-container port mappings ("host:container") |
volumes |
Named volumes declared by this feature |
networks |
Docker networks used or created |
build |
Whether the feature includes a custom Dockerfile |
healthcheck |
Whether the service defines a Docker health check |
depends_on.services |
Service names from other features that must be running first |
Example for nginx (with custom Dockerfile for prod):
[tool.splent.contract.docker]
services = ["splent_feature_nginx"]
ports = ["${NGINX_HTTP_HOST_PORT}:80", "${NGINX_HTTPS_HOST_PORT}:443"]
volumes = ["letsencrypt", "certbot_webroot"]
networks = ["splent_network"]
build = true
healthcheck = false
[tool.splent.contract.docker.depends_on]
services = []
check:infra reads these declarations to detect port conflicts, volume collisions, and missing health checks across the entire product.
Who reads the contract:
| Consumer | What it reads | Why |
|---|---|---|
feature:install |
requires.features, env |
Checks dependencies before installing; auto-detects dev/prod scope |
feature:add / feature:attach |
env |
Auto-classifies into features_dev or features_prod |
feature:remove / feature:detach |
requires.features |
Blocks removal if another feature depends on this one |
product:validate |
All contract fields | Detects conflicts between features |
product:build |
docker, env |
Merges compose files, excludes dev-only features |
export:puml |
All contract fields | Generates PlantUML diagrams |
In splent_cli and splent_framework
The CLI and the framework are themselves Python packages with their own pyproject.toml files inside the workspace.
[project]
name = "splent_cli"
version = "1.7.0"
Who reads this:
| Consumer | What it reads | Why |
|---|---|---|
version |
[project].version |
Reads directly from source — never from stale installed metadata |
doctor |
[project].version |
Cross-checks CLI and framework major versions for compatibility |
release:cli, release:framework |
[project].version |
Bumps the version before tagging and publishing |
The version command reads CLI and framework versions directly from their pyproject.toml files in the workspace, not from importlib.metadata. This ensures the version shown is always current, even if the editable install has not been refreshed since the last version bump.
Key sections reference
| Section | Used by | Purpose |
|---|---|---|
[project].name |
pip, framework, CLI | Package identity |
[project].version |
version, doctor, release commands |
Current version |
[project].requires-python |
pip | Python version constraint |
[project].dependencies |
doctor |
Runtime dependency validation |
[project.optional-dependencies].dev |
pip | Development dependencies |
[project.optional-dependencies].core |
pip | Core framework dependency |
[tool.splent].features |
framework, CLI, startup scripts | Base feature composition (all envs) |
[tool.splent].features_dev |
framework, CLI, startup scripts | Dev-only features |
[tool.splent].features_prod |
framework, CLI, startup scripts | Prod-only features |
[tool.splent].spl |
product:validate, product:missing, product:auto-require, doctor, feature:order |
SPL name — resolves the UVL from splent_catalog/ |
[tool.splent.uvl] |
product:validate, product:missing, product:auto-require, doctor, feature:order |
(Legacy) UVL model location and metadata — still supported, but spl is preferred |
[tool.splent.contract] |
feature:remove, product:validate, export:puml |
Feature public interface declaration |
[tool.setuptools] |
pip | Package discovery (src/ layout) |