Skip to main content

SPLENT Framework

Core library for building modular, feature-driven Flask applications using Software Product Line principles.

Quick start

from splent_framework import create_splent_app

app = create_splent_app(__name__, config_name="development")

Configuration order

A key can be set by four layers, and the stronger one wins whatever order the code runs in: the framework's defaults, then a feature's inject_config, then the keys the product's config.py sets itself, then the environment (a variable named like the key, and the operator settings below). Before, a feature's default replaced what the product's config.py chose, because the features load after it. Every key is traced in app.extensions["splent_config_trace"] with its layer, and a value that held against a weaker one carries "action": "kept" and the value it overruled. The environment winning over the product is accepted but marked as drift: the entry carries "drift": true and, in overruled_source and overruled_value, what the product had chosen. A key the framework's config reads from a variable of the same name (TIMEZONE, the BABEL_ locales, the TASKS_ settings and the operator settings below) is traced as the environment when that variable chose it, and otherwise keeps the value the product's config.py gives it. product:config shows the trace.

In development product:env --merge writes every feature's declared default into the product's .env, so there the environment wins over config.py for any key a feature declares. A product decides a feature's key in its own [tool.splent.config], which that merge puts above the feature's; config.py is for keys no feature reads from the environment.

Production defaults

A product built with create_splent_app gets these without any code. Each one is a variable in the deployment's .env. The header and asset settings can also be fixed in the product's config.py, which then wins over the .env. For SPLENT_COOKIE_SECURE, the cookie variables and MAX_CONTENT_LENGTH the .env wins over the product and over any feature's inject_config, because only the deployment knows whether it runs over HTTPS and how large its uploads are. The factory applies them again after the features load.

Variable Default What it does
SPLENT_TRUSTED_PROXIES 0 Number of reverse proxies in front of the app. Set it to 1 behind a TLS proxy (nginx, Caddy, Traefik) so the app sees the client address and the https the proxy was reached on, builds https:// links and sends HSTS. Leave it at 0 when the app port is reachable without the proxy.
SPLENT_COOKIE_SECURE auto Session and remember-me cookies are always HttpOnly. Left unset, every cookie is Secure when the request arrived over HTTPS, directly or through a trusted proxy, so behind TLS set SPLENT_TRUSTED_PROXIES. true forces Secure, false never sets it.
SESSION_COOKIE_SAMESITE Lax Lax, Strict or None, in any case. None is for a client on another site that signs in with the cookie, and it needs Secure: the app warns at start when the cookie may go out without it. None also lets any site post with the user's session, so every view exempt from CSRF that takes the session needs a defence of its own first; the app names each one in the log, at start and when a feature exempts it later. Anything else stops the app at start. REMEMBER_COOKIE_SAMESITE follows it unless set.
SESSION_COOKIE_SECURE unset true marks the session cookie Secure on every request, even under auto. REMEMBER_COOKIE_SECURE follows it unless set. SPLENT_COOKIE_SECURE=false still wins for the session cookie.
MAX_CONTENT_LENGTH 16M Largest request body. Bigger ones get 413, as JSON for a client that sent or asked for JSON. Accepts bytes or K, M, G, and 0 for no limit.
SPLENT_SECURITY_HEADERS true nosniff, X-Frame-Options, Referrer-Policy, Permissions-Policy and Cache-Control: no-store for signed-in users. false leaves them all to the proxy.
SPLENT_FRAME_OPTIONS SAMEORIGIN DENY, or off to send nothing.
SPLENT_REFERRER_POLICY strict-origin-when-cross-origin Any Referrer-Policy value.
SPLENT_PERMISSIONS_POLICY camera, microphone, geolocation, payment and USB off The whole Permissions-Policy value. Embedded maps lose their location button under the default; allow geolocation here if a product needs it.
SPLENT_NO_STORE_SIGNED_IN true Cache-Control: no-store on pages for a signed-in user.
SPLENT_HSTS_MAX_AGE 31536000 Sent only over HTTPS. 0 turns HSTS off.
SPLENT_HSTS_INCLUDE_SUBDOMAINS, SPLENT_HSTS_PRELOAD false The optional HSTS directives.
SPLENT_CONTENT_SECURITY_POLICY none A Content-Security-Policy to send. Opt in, since existing templates use inline scripts.
SPLENT_CSP_REPORT_ONLY false Send that policy as Content-Security-Policy-Report-Only to try it first.
SPLENT_ASSET_MAX_AGE 31536000 How long a versioned asset (?v=<mtime>) may be cached. 0 turns it off.
SPLENT_ASSET_VERSIONING true Add ?v=<mtime> to the asset URLs url_for builds.

Database connections are pinged when they leave the pool and recycled after an hour (SQLALCHEMY_ENGINE_OPTIONS), so a database restart or MariaDB's eight hour wait_timeout never reaches a visitor as a 500. A product changes them in its config.py.

Health probes

GET /healthz answers {"status": "ok"} while the process is alive, and GET /readyz answers the same once every readiness check passes, or 503 with {"status": "unavailable"}. Both are answered by a middleware around the whole app, so a probe opens no session, sets no cookie, runs no feature's hook and is never subject to CSRF. Which check failed, and why, goes to the log and not into the response.

The framework checks the database (SELECT 1), the session store when it answers a ping (the Redis of splent_feature_session_redis: with it down every page fails), and the task executor checks its Redis in rq mode. A feature adds its own service from init_feature:

from splent_framework.health import register_readiness_check

register_readiness_check("search", lambda: client.ping(), app)
Variable Default What it does
SPLENT_HEALTH_PATH /healthz Where the liveness probe answers. off turns it off.
SPLENT_READY_PATH /readyz Where the readiness probe answers. off turns it off.
SPLENT_READY_TIMEOUT 3 Seconds the checks may take before the probe answers 503. While a slow check is still running, the next probes answer 503 at once.

Background tasks

splent_framework.tasks runs work outside the request: a feature marks a function at the top level of a module with @task and enqueues it with the ids it needs.

from splent_framework.tasks import enqueue, get_run, task

@task(timeout=60, retries=2)
def transform_uvl(hubfile_id):
    ...

run = enqueue(transform_uvl, {"hubfile_id": hubfile.id}, key=hubfile.id)
get_run(run.id).status   # waiting, queued, running, succeeded, failed, timed_out, dropped

A task enqueued after a write that is not committed yet waits for the commit, and is dropped if the transaction, or the savepoint it was enqueued in, is rolled back; with nothing pending it starts at once (after_commit=True or False decides it instead). Each run gets an app context and a database session of its own. A key merges the run into a run of the same task and key that has not started yet. A failed attempt is retried up to the task's retries (at most 10), waiting backoff seconds (10) and twice as long each time after. Arguments must be JSON values in every mode, and a task must do no harm when it runs twice. The mode is read once, when the app is built: a command or a seeder that needs the work done before it exits calls the task, which is still a plain function, or wait() in thread mode.

Variable Default What it does
TASKS_MODE thread sync runs the task in the caller before enqueue returns, which the testing configuration always uses. thread runs it in a bounded pool of the web process: lost if the process stops, and a thread cannot be stopped at its timeout, so a slow run is only logged and marked overdue. It does not protect against a task that hangs: that task holds its thread for good and what comes after waits behind it, in memory, with a warning in the log at the timeout and every minute after. A product whose tasks read untrusted input runs rq in production. rq queues it in Redis for python -m splent_framework.tasks.worker, which stops an attempt at its timeout and survives a restart of the web; it needs splent_framework[rq]. Anything else stops the app at start.
TASKS_THREAD_WORKERS 2 Tasks that run at once in thread mode.
TASKS_DEFAULT_TIMEOUT 180 Seconds an attempt may take when its task names none.
TASKS_REDIS_URL REDIS_TASKS_URL, then REDIS_URL The Redis of the rq queue. Empty takes the tasks database splent_feature_redis sets apart (REDIS_TASKS_URL), so the queue is not flushed with a cache or the sessions. /readyz depends on it in that mode.
TASKS_QUEUE the product's name The queue, so two products on one Redis keep their work apart.
TASKS_RUN_TTL 86400 Seconds Redis keeps the record of an ended run.

The worker builds the product's app from SPLENT_APP in the profile SPLENT_ENV names. python -m splent_framework.tasks.worker --check is for the container's healthcheck: it builds no app and opens no connection, and exits 0 while the worker's last heartbeat holds. The worker writes when the next one is due to TASKS_WORKER_HEARTBEAT_FILE (by default splent-tasks-worker.heartbeat in the temporary directory) and removes it when it stops. --burst stops when the queue is empty.

Requirements

  • Python 3.13+
  • Flask 3.1+
  • SQLAlchemy 2.0+

Documentation

Full docs, tutorials, and architecture reference at docs.splent.io

License

splent_framework is free software under the GNU Lesser General Public License, version 3 or any later version (LGPL-3.0-or-later). See COPYING.LESSER, and COPYING for the GNU General Public License it builds on. A product built on the framework can carry any license of its own choice.

Metadata

Release files for splent-framework 1.21.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for splent-framework 1.21.0
File Size Uploaded Provenance
splent_framework-1.21.0.tar.gz 368.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for splent-framework 1.21.0
File Interpreter ABI Platform
splent_framework-1.21.0-py3-none-any.whl Python 3 none any Details

Total release size: 596.9 kB

Release files / splent_framework-1.21.0.tar.gz

Download URL splent_framework-1.21.0.tar.gz
Size 368.2 kB
Tags Source
SHA-256 checksum
How to use checksums
507b6361a3f9248b1d9f90b0af68a845b86651ec99e65b89224fd4200a9c354b
BLAKE2b-256 checksum
How to use checksums
b2ef9b4b85f78c7c4fdb215fa1d3b6c7655d918c7091a8bd805d23562d51aa29
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.16

Release files / splent_framework-1.21.0-py3-none-any.whl

Download URL splent_framework-1.21.0-py3-none-any.whl
Size 228.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0c34bece69e6763a88bfcb9c286c51738d9fb1cb46e95cb8de5f7dd6f9b92c5a
BLAKE2b-256 checksum
How to use checksums
97f08e6c669ea5e20b5514f30dc611766c4723e6acf083a5deee4cabcb365db9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.13.16

Security for splent-framework 1.21.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.21.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page