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

  1. Configuration hierarchy
  2. Framework defaults
  3. Product configuration
    1. How it’s loaded
    2. What to customize
    3. What NOT to do
  4. Feature configuration
    1. Real examples
    2. Key rules for feature config
    3. Auto-generating config.py
  5. Inspecting the resolved configuration
    1. product:config
    2. check:env
    3. product:validate
  6. Environment variables
  7. 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.pyinject_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_KEY defaults to a dev value in dev/test, but ProductionConfig raises an error if it’s not set via env var.
  • SESSION_TYPE is intentionally not set here — it must come from a session feature (splent_feature_session_redis or splent_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

  1. ConfigManager reads SPLENT_APP (e.g., my_first_app).
  2. It imports my_first_app.config.
  3. It selects the class matching the environment: DevelopmentConfig, TestingConfig, or ProductionConfig.
  4. 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_uri from 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

  1. Always read from env vars. Use os.getenv() with sensible defaults. Never hardcode secrets or host-specific values.
  2. Use app.config.get() to read values set by other features. This is how session_redis reads REDIS_URL from redis.
  3. Feature load order determines override order. UVL constraints control which feature loads first. If mailhog => mail, then mailhog’s inject_config runs after mail’s and overwrites the same keys.
  4. Do not set SESSION_TYPE from 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 .env file is git-ignored and generated from .env.example. Never commit credentials. See Environment for the full variable reference.


See also


Back to top

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