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
- 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.
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:
- 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:
- 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:
- 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:- 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:
- 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.