Application initialization

Every SPLENT product boots through a single function: create_splent_app(). It creates the Flask app, runs a fixed initialization pipeline, and returns a fully configured application — no global state, no side effects outside the factory.


Table of contents

  1. The old way vs the new way
    1. Before: 8 manual manager imports
    2. Now: 1 import, 5 lines
  2. The initialization pipeline
    1. 1. Namespace initialization
    2. 2. Configuration loading
    3. 3. Database initialization
    4. 4. Session initialization
    5. 5. Logging
    6. 6. Error handlers
    7. 7. Jinja context
    8. 8. Feature registration
  3. Pipeline order summary
  4. strict mode
  5. Testing mode

The old way vs the new way

Before: 8 manual manager imports

Previous versions of SPLENT required every product to import and wire each manager individually:

import os
from flask import Flask
from splent_framework.managers.namespace_manager import NamespaceManager
from splent_framework.managers.config_manager import ConfigManager
from splent_framework.managers.migration_manager import MigrationManager
from splent_framework.managers.session_manager import SessionManager
from splent_framework.managers.jinja_manager import JinjaManager
from splent_framework.managers.error_handler_manager import ErrorHandlerManager
from splent_framework.managers.feature_manager import FeatureManager


def create_app(config_name="development"):
    app = Flask(__name__)

    _init_namespaces(app)
    _load_config(app, config_name)
    _init_db(app)
    _init_sessions(app)
    _init_logging(app)
    _init_error_handlers(app)
    _init_jinja_context(app)
    _init_features(app)

    return app

This was verbose, error-prone (wrong order broke things silently), and created drift between products.

Now: 1 import, 5 lines

from splent_framework import create_splent_app


def create_app(config_name="development"):
    return create_splent_app(__name__, config_name)

That is the entire <product>/src/<product>/__init__.py. The framework owns the pipeline — products just call it.


The initialization pipeline

create_splent_app() runs 8 steps in a fixed order. The pipeline cannot be reordered — each step depends on the previous ones.

1. Namespace initialization

Sets up Python namespace packages for every organisation directory found in .splent_cache/features/. This makes features importable under paths like splent_io.splent_feature_auth before any feature is loaded.

Must run first — nothing can import features until this completes.


2. Configuration loading

ConfigManager locates the active product via SPLENT_APP, imports <product>.config, selects the correct class (DevelopmentConfig, ProductionConfig, or TestingConfig), and copies all uppercase attributes into app.config.

If the product does not have a config.py, the framework falls back to its own default_config.py.

Must run before any manager that reads app.config.


3. Database initialization

MigrationManager:

  1. Initializes the db SQLAlchemy singleton with the Flask app.
  2. Initializes Flask-Migrate (Alembic).
  3. Creates the splent_migrations tracking table if it does not exist.

The per-feature Alembic version tables (alembic_<feature_name>) are managed separately per feature — they are not created here.

Must run before sessions (sessions may use the database).


4. Session initialization

Initializes Flask-Session. The session backend is determined by SESSION_TYPE in app.config:

  • "redis" — server-side sessions stored in Redis (requires SESSION_REDIS config key)
  • "filesystem" — sessions stored on disk (used in testing)

5. Logging

Configures log handlers, formatters, and log level. The log file path resolves to <product>/app.log via PathUtils.get_app_log_dir().


6. Error handlers

Registers Flask error handlers for 400, 401, 404, and 500. The framework first attempts to import custom handlers from <product>.errors; if that module is missing, it falls back to built-in template-based responses.

To customize error pages, define handler functions in your product’s errors.py:

# sample_splent_app/src/sample_splent_app/errors.py

def handle_500(e):
    return render_template("errors/500.html"), 500

def handle_404(e):
    return render_template("errors/404.html"), 404

7. Jinja context

JinjaManager registers a Flask context processor that merges:

  • The base context dict (e.g., SPLENT_APP)
  • Each feature’s inject_context_vars(app) return value

The result is available in every Jinja template as template globals, without any explicit render_template argument.

JinjaManager also injects get_template_hooks(name) as a Jinja global for the template hook system.


8. Feature registration

The final step. FeatureManager:

  1. Reads [tool.splent].features from the active product’s pyproject.toml.
  2. Resolves the topological load order via FeatureLoadOrderResolver (UVL constraints).
  3. For each feature in order, calls FeatureLoader.load(ref) which:
    • Locates the symlink in features/<namespace>/
    • Validates the src/<org>/<name>/ directory structure
    • Adds the src/ root to sys.path
    • Imports the feature package and its routes, models, hooks submodules
    • Calls feature.config.inject_config(app) if present
    • Calls feature.init_feature(app) if present
    • Registers all Blueprint instances found in the module

Pipeline order summary

Step Name Depends on
1 Namespaces Nothing — must be first
2 Config Namespaces (product module importable)
3 Database Config (SQLALCHEMY_DATABASE_URI)
4 Sessions Config (SESSION_TYPE, SESSION_REDIS)
5 Logging Config
6 Error handlers Nothing strict, but by convention after config
7 Jinja context Nothing strict
8 Features Everything above — features may use db, sessions, config, Jinja

The pipeline is fixed and cannot be reordered. If you need to run custom logic, do it in a feature’s init_feature(app) hook, which runs at step 8 after the entire infrastructure is ready.


strict mode

By default, create_splent_app() initializes the feature manager with strict=False. This means:

  • Missing optional submodules (routes, models, hooks) in a feature are treated as warnings, not errors.
  • The app still boots even if a feature is incomplete.
  • A warning is logged for each missing submodule so you can spot issues in development.

Set strict=True to raise FeatureError on any missing dependency — useful in CI or when debugging feature loading issues:

def create_app(config_name="development"):
    return create_splent_app(__name__, config_name, strict=True)

Testing mode

When running tests, the factory is called with "testing":

app = create_app("testing")

This selects TestingConfig which:

  • Sets TESTING = True, DEBUG = False
  • Points the database to the test database (MARIADB_DATABASE_TEST)
  • Uses SESSION_TYPE = "filesystem" (no Redis required)

The framework’s pytest fixtures call create_app("testing") automatically. See Testing the Framework.


Back to top

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