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, with no global state and no side effects outside the factory.
Table of contents
- The old way vs the new way
- The initialization pipeline
- Pipeline order summary
- strict mode
- 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.
create_splent_appis the package’s public export. Framework versions 1.6.2 and 1.7.0 shipped without a package__init__.py, sofrom splent_framework import create_splent_appfailed with anImportErroron those versions; 1.7.1 restores it. If you are pinned to an affected version, import from the module directly withfrom splent_framework.app_factory import create_splent_app.
The initialization pipeline
create_splent_app() runs 8 steps in a fixed order. The pipeline cannot be reordered, since 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 does three things.
- Initializes the
dbSQLAlchemy singleton with the Flask app. - Initializes Flask-Migrate (Alembic).
- Creates the
splent_migrationstracking 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 (requiresSESSION_REDISconfig 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 two sources.
- 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 proceeds as follows.
- Reads
[tool.splent].featuresfrom the active product’spyproject.toml. - Resolves the topological load order via
FeatureLoadOrderResolver(UVL constraints). - For each feature in order, calls
FeatureLoader.load(ref), which does the following.- Locates the symlink in
features/<namespace>/ - Validates the
src/<org>/<name>/directory structure - Adds the
src/root tosys.path - Imports the feature package and its
routes,models,hookssubmodules - Calls
feature.config.inject_config(app)if present - Calls
feature.init_feature(app)if present - Registers all
Blueprintinstances found in the module
- Locates the symlink in
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 the following.
- 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, which is 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 does the following.
- 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.