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

  1. Why it matters
  2. In a product
    1. Feature groups
    2. Who reads this
  3. In a feature
    1. The feature contract
    2. env field
    3. Refinement section
    4. Docker contract
  4. In splent_cli and splent_framework
  5. Key sections reference

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].features continue 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, and feature:attach automatically place the feature in features_dev (no need for --dev flag)
  • product:configure classifies it correctly
  • product:build excludes 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)

Back to top

splent. Distributed by an LGPL license v3. Contact us: drorganvidez@us.es