Configuration
SPLENT uses a layered configuration system. Each layer can add or override keys, and the framework tracks the origin of every value so you can inspect exactly where a setting came from.
Table of contents
- Configuration hierarchy
- Framework defaults
- Product configuration
- Feature configuration
- Inspecting the resolved configuration
- Environment variables
- See also
Configuration hierarchy
Configuration is applied in this order. Later layers override earlier ones:
| Order | Layer | Source | Example |
|---|---|---|---|
| 1 | Framework defaults | splent_framework.configuration.default_config |
SECRET_KEY, SQLALCHEMY_TRACK_MODIFICATIONS |
| 2 | Product config | <product>/src/<product>/config.py |
Database name, custom flags |
| 3 | Feature config | <feature>/config.py → inject_config(app) |
REDIS_URL, MAIL_SERVER, SESSION_TYPE |
Every key set at any layer is recorded in app.extensions["splent_config_trace"] with its value and source. Use product:config to view the trace.
Framework defaults
File: splent_framework/src/splent_framework/configuration/default_config.py
The base class Config provides sensible defaults. Environment-specific classes inherit from it:
class Config:
SECRET_KEY = os.getenv("SECRET_KEY", "dev_test_key_...")
SQLALCHEMY_TRACK_MODIFICATIONS = False
TEMPLATES_AUTO_RELOAD = True
UPLOAD_FOLDER = "uploads"
def __init__(self):
self.TIMEZONE = os.getenv("TIMEZONE", "Europe/Madrid")
class DevelopmentConfig(Config):
DEBUG = True
def __init__(self):
super().__init__()
self.SQLALCHEMY_DATABASE_URI = _build_db_uri("MARIADB_DATABASE", "default_db")
class TestingConfig(Config):
TESTING = True
WTF_CSRF_ENABLED = False
def __init__(self):
super().__init__()
self.SQLALCHEMY_DATABASE_URI = _build_db_uri("MARIADB_TEST_DATABASE", "default_test_db")
class ProductionConfig(Config):
DEBUG = False
def __init__(self):
super().__init__()
if not os.getenv("SECRET_KEY"):
raise RuntimeError("SECRET_KEY environment variable must be set in production.")
self.SQLALCHEMY_DATABASE_URI = _build_db_uri("MARIADB_DATABASE", "default_db")
Key points:
SECRET_KEYdefaults to a dev value in dev/test, butProductionConfigraises an error if it’s not set via env var.SESSION_TYPEis intentionally not set here — it must come from a session feature (splent_feature_session_redisorsplent_feature_session_filesystem).- Database URIs are built at instantiation time from env vars (
MARIADB_USER,MARIADB_PASSWORD,MARIADB_HOSTNAME,MARIADB_DATABASE).
Product configuration
File: <product>/src/<product>/config.py
A product config extends the framework’s environment-specific classes. The scaffold generates a minimal file — only override what your product actually needs:
from splent_framework.configuration.default_config import (
DevelopmentConfig as BaseDev,
TestingConfig as BaseTest,
ProductionConfig as BaseProd,
)
class DevelopmentConfig(BaseDev):
pass
class TestingConfig(BaseTest):
pass
class ProductionConfig(BaseProd):
pass
How it’s loaded
ConfigManagerreadsSPLENT_APP(e.g.,my_first_app).- It imports
my_first_app.config. - It selects the class matching the environment:
DevelopmentConfig,TestingConfig, orProductionConfig. - All uppercase attributes are injected into
app.config.
If a product does not provide a config.py, the framework falls back to its own defaults.
What to customize
Override only what differs from the framework defaults:
import os
from splent_framework.configuration.default_config import (
DevelopmentConfig as BaseDev,
TestingConfig as BaseTest,
ProductionConfig as BaseProd,
)
class DevelopmentConfig(BaseDev):
# Enable Flask's template loading explanation for debugging
EXPLAIN_TEMPLATE_LOADING = True
class TestingConfig(BaseTest):
# Use a specific test database for this product
def __init__(self):
super().__init__()
self.SQLALCHEMY_DATABASE_URI = (
f"mysql+pymysql://{os.getenv('MARIADB_USER', 'default_user')}:"
f"{os.getenv('MARIADB_PASSWORD', 'default_password')}@"
f"{os.getenv('MARIADB_HOSTNAME', 'localhost')}:3306/"
f"{os.getenv('MARIADB_TEST_DATABASE', 'my_product_test_db')}"
)
class ProductionConfig(BaseProd):
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
Do not import
_build_db_urifrom the framework — it is an internal helper (underscore prefix). If you need a custom database URI, build it directly as shown above, or just inherit the default from the framework.
What NOT to do
# BAD: repeating what the framework already provides
class DevelopmentConfig(BaseDev):
DEBUG = True # already True in BaseDev
TEMPLATES_AUTO_RELOAD = True # already True in Config
# BAD: importing private helpers
from splent_framework.configuration.default_config import _build_db_uri
# BAD: hardcoding session type (this belongs to a session feature)
class TestingConfig(BaseTest):
SESSION_TYPE = "filesystem" # should come from splent_feature_session_filesystem
Feature configuration
File: <feature>/src/<namespace>/<feature>/config.py
Features inject configuration via an inject_config(app) function. This runs after the product config, so features can add keys or override product-level values.
import os
def inject_config(app):
app.config.update({
"KEY": os.getenv("KEY", "default"),
})
Real examples
splent_feature_redis — provides REDIS_URL for other features:
import os
def inject_config(app):
app.config.update({
"REDIS_URL": os.getenv("REDIS_URL", "redis://redis:6379"),
})
splent_feature_session_redis — reads REDIS_URL (set by redis feature) and configures Flask-Session:
import os
import redis
def inject_config(app):
redis_url = app.config.get("REDIS_URL") or os.getenv("REDIS_URL", "redis://redis:6379")
app.config.update({
"SESSION_TYPE": "redis",
"SESSION_PERMANENT": False,
"SESSION_REDIS": redis.from_url(redis_url),
})
splent_feature_mail — injects SMTP settings from env vars:
import os
def inject_config(app):
app.config.update({
"MAIL_SERVER": os.getenv("MAIL_SERVER", "smtp.example.com"),
"MAIL_PORT": int(os.getenv("MAIL_PORT", "587")),
"MAIL_USE_TLS": os.getenv("MAIL_USE_TLS", "True").lower() == "true",
"MAIL_USE_SSL": os.getenv("MAIL_USE_SSL", "False").lower() == "true",
"MAIL_USERNAME": os.getenv("MAIL_USERNAME", ""),
"MAIL_PASSWORD": os.getenv("MAIL_PASSWORD", ""),
"MAIL_DEFAULT_SENDER": os.getenv("MAIL_DEFAULT_SENDER", os.getenv("MAIL_USERNAME", "noreply@example.com")),
})
splent_feature_mailhog — overrides mail’s SMTP settings for local development. Loads after mail because the UVL declares mailhog => mail:
import os
def inject_config(app):
app.config.update({
"MAIL_SERVER": os.getenv("MAILHOG_HOST", "splent_feature_mailhog"),
"MAIL_PORT": int(os.getenv("MAILHOG_SMTP_PORT", "1025")),
"MAIL_USE_TLS": False,
"MAIL_USE_SSL": False,
"MAIL_USERNAME": "",
"MAIL_PASSWORD": "",
"MAIL_DEFAULT_SENDER": os.getenv("MAIL_DEFAULT_SENDER", "dev@splent.local"),
})
Key rules for feature config
- Always read from env vars. Use
os.getenv()with sensible defaults. Never hardcode secrets or host-specific values. - Use
app.config.get()to read values set by other features. This is howsession_redisreadsREDIS_URLfromredis. - Feature load order determines override order. UVL constraints control which feature loads first. If
mailhog => mail, then mailhog’sinject_configruns after mail’s and overwrites the same keys. - Do not set
SESSION_TYPEfrom a product config. Session configuration is the responsibility of a session feature.
Auto-generating config.py
If a feature uses os.getenv() directly in services or routes but lacks a config.py, the framework cannot trace those values. Use feature:inject-config to auto-generate config.py from your source code:
splent feature:inject-config splent_feature_notes
This scans all os.getenv() calls in the feature’s source and generates a config.py with the appropriate inject_config(app) function.
Inspecting the resolved configuration
product:config
Shows the final resolved configuration with full origin tracing:
splent product:config
Each key shows its current value and which layer set it:
SECRET_KEY = "dev_test_key_..." (product)
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://..." (product)
REDIS_URL = "redis://redis:6379" (feature: splent_feature_redis)
SESSION_TYPE = "redis" (feature: splent_feature_session_redis)
MAIL_SERVER = "splent_feature_mailhog" (feature: splent_feature_mailhog)
If a feature overwrites a product-level key, a warning is displayed:
Overwritten keys:
MAIL_SERVER: 'smtp.example.com' (feature: splent_feature_mail) -> 'splent_feature_mailhog' (feature: splent_feature_mailhog)
Filter by feature or key name:
splent product:config --feature redis # Only keys set by redis-related features
splent product:config --key session # Only keys containing "session"
check:env
Validates that all required environment variables are set:
splent check:env
product:validate
Runs a comprehensive validation including configuration consistency:
splent product:validate
Environment variables
The framework and features read configuration from environment variables defined in the .env file. Key variables:
| Variable | Default | Used by |
|---|---|---|
SPLENT_APP |
— | ConfigManager (selects product) |
SPLENT_ENV |
dev |
Determines which config class to load. Not in workspace .env — injected by product:env --merge (dev) or product:build/product:deploy (prod). product:containers auto-detects from the running web container. |
SECRET_KEY |
dev fallback | Config base class |
MARIADB_USER |
default_user |
Database URI builder |
MARIADB_PASSWORD |
default_password |
Database URI builder |
MARIADB_HOSTNAME |
localhost |
Database URI builder |
MARIADB_DATABASE |
default_db |
Dev/prod database name |
MARIADB_TEST_DATABASE |
default_test_db |
Test database name |
TIMEZONE |
Europe/Madrid |
Config base class |
REDIS_URL |
redis://redis:6379 |
splent_feature_redis |
MAIL_SERVER |
smtp.example.com |
splent_feature_mail |
The
.envfile is git-ignored and generated from.env.example. Never commit credentials. See Environment for the full variable reference.
See also
- Feature anatomy: config.py — how features define
inject_config feature:inject-config— auto-generate config.py from source codeproduct:config— inspect resolved configurationcheck:env— validate environment variables- Application initialization — the full startup sequence