Configuration¶
Every Veloce application carries a configuration dictionary at app.config.
It is an ordinary mapping seeded with documented defaults, plus loader
methods that pull settings from Python objects, files, and the environment.
from veloce import Veloce
app = Veloce()
app.config["DEBUG"] = True
app.config["SECRET_KEY"] = "change-me"
print(app.config["DEBUG"]) # True
print(app.config["PREFERRED_URL_SCHEME"]) # "http" (a built-in default)
app.config is an instance of Config (importable as
from veloce.config import Config), a dict subclass. Every dict idiom
keeps working — config["KEY"] = value, config.get("KEY"),
config.update(...), "KEY" in config. The loader methods below add
convenient ways to populate it from external sources.
Only uppercase keys are configuration¶
The loaders store a key only when its name is made of ASCII uppercase
letters, digits, and underscores, and does not start with a digit
(DEBUG, SECRET_KEY, MAX_CONTENT_LENGTH). Lowercase names in a source
module or mapping are treated as private and skipped. This lets a settings
module keep helper variables and imports alongside its config without
leaking them into app.config.
Direct assignment (app.config["debug"] = True) bypasses this filter — the
uppercase rule applies only to the from_* loaders.
Built-in defaults¶
A fresh app.config is pre-populated so reads never raise KeyError.
The defaults are:
| Key | Default | Purpose |
|---|---|---|
DEBUG |
False |
Enable debug behaviour (tracebacks, propagation). |
TESTING |
False |
Mark the app as under test. |
SECRET_KEY |
None |
Key for signing sessions and tokens. |
SERVER_NAME |
None |
Host (and optional port) used for URL building. |
APPLICATION_ROOT |
"/" |
Mount path of the application; also the default Path of the session cookie. |
PREFERRED_URL_SCHEME |
"http" |
Scheme used when generating external URLs. |
MAX_CONTENT_LENGTH |
104857600 (100 MiB) |
Maximum request body size in bytes. The body is buffered in memory, so the default bounds a large-request OOM; raise it for large-upload endpoints, or set None for unlimited. |
MAX_FORM_PARTS |
1000 |
Maximum number of multipart form parts. |
MAX_FORM_PART_SIZE |
10485760 |
Maximum size of a single form part in bytes (applies to both file and text parts unless overridden below). |
MAX_FORM_FILES |
None |
Maximum number of file parts (None = only bounded by MAX_FORM_PARTS). |
MAX_FORM_FIELDS |
None |
Maximum number of text-field parts (None = only bounded by MAX_FORM_PARTS). |
MAX_FORM_FILE_SIZE |
None |
Per-file size limit in bytes; overrides MAX_FORM_PART_SIZE for file parts. |
MAX_FORM_FIELD_SIZE |
None |
Per-text-field size limit in bytes; overrides MAX_FORM_PART_SIZE for text parts. |
MAX_FORM_FIELD_MEMORY |
None |
Cumulative resident-memory ceiling (bytes) across all text fields, including field-name bytes. |
MAX_COOKIE_SIZE |
4093 |
Size ceiling for the emitted session cookie. |
SESSION_COOKIE_NAME |
"session" |
Name of the session cookie. |
SESSION_COOKIE_HTTPONLY |
True |
Set HttpOnly on the session cookie. |
SESSION_COOKIE_SECURE |
False |
Set Secure on the session cookie. |
SESSION_COOKIE_SAMESITE |
None |
SameSite attribute for the session cookie (None keeps the middleware default, lax). |
PERMANENT_SESSION_LIFETIME |
2678400 |
Lifetime of a permanent session in seconds (31 days). |
JSON_SORT_KEYS |
True |
Sort keys when serialising JSON. |
JSONIFY_PRETTYPRINT_REGULAR |
False |
Pretty-print JSON responses. |
PROPAGATE_EXCEPTIONS |
None |
Re-raise unhandled exceptions out of dispatch. |
SEND_FILE_MAX_AGE_DEFAULT |
None |
Default Cache-Control: max-age for send_file / async_send_file when the caller passes no max_age=. |
REQUEST_HANDLER_TIMEOUT |
30 |
Per-handler timeout in seconds. |
KEEP_ALIVE_TIMEOUT |
75 |
Keep-alive timeout in seconds. |
REQUEST_TIMEOUT |
30 |
Request read timeout in seconds. |
TCP_KEEPALIVE |
True |
Enable OS-level TCP keepalive (SO_KEEPALIVE) on the built-in serving path. |
TCP_KEEPALIVE_IDLE |
None |
Idle seconds before the first keepalive probe (TCP_KEEPIDLE); Linux/macOS only. |
TCP_KEEPALIVE_INTERVAL |
None |
Seconds between keepalive probes (TCP_KEEPINTVL); Linux only. |
TCP_KEEPALIVE_COUNT |
None |
Failed probes before the connection is dropped (TCP_KEEPCNT); Linux only. |
JSON_ERRORS_VERBOSE |
False |
Include parser detail in JSON body-parse error responses. |
GRACEFUL_TASK_TIMEOUT |
10 |
Seconds to wait for background tasks to finish cancelling on shutdown. |
A few of these keys drive framework behaviour directly. MAX_CONTENT_LENGTH
bounds the request body the server will read. PROPAGATE_EXCEPTIONS
controls whether unhandled exceptions re-raise: when it is left at None,
exceptions propagate only if both DEBUG and TESTING are True (useful
so test runs surface the real traceback). Boolean keys loaded from an env
file arrive as strings and are coerced ("false", "0", "off" read as
off). JSON_SORT_KEYS affects the ordering of keys in JSON responses.
The SECRET_KEY, SESSION_COOKIE_*, PERMANENT_SESSION_LIFETIME,
MAX_COOKIE_SIZE, and APPLICATION_ROOT keys are consumed by the session
middlewares: any setting not passed explicitly to SessionMiddleware /
ServerSessionMiddleware resolves from config on the first request, so
app.secret_key = "..." plus a bare app.add_middleware(SessionMiddleware)
is a complete setup. Explicit constructor arguments always win over config.
Set a real SECRET_KEY before using sessions
SECRET_KEY defaults to None. Signed sessions, flash messages, and
the Signer require a non-empty,
unpredictable key. Generate one with python -c "import secrets; print(secrets.token_hex(32))"
and load it from the environment, never hard-code it in source.
Loading from a Python object¶
from_object imports every uppercase attribute of a module, class, or
instance. Pass the object itself, or a dotted import path as a string.
import types
from veloce import Veloce
settings = types.SimpleNamespace(
DEBUG=True,
SECRET_KEY="from-object",
internal_note="ignored — lowercase",
)
app = Veloce()
app.config.from_object(settings)
print(app.config["DEBUG"]) # True
print("internal_note" in app.config) # False
A dotted string is resolved before its attributes are read, which makes it easy to select an environment-specific class:
from veloce import Veloce
app = Veloce()
# Resolves the dotted path, then copies its UPPERCASE attributes.
app.config.from_object("myapp.settings.Production")
The common pattern is one module per environment, or one class per environment in a shared module, selected at startup from an env var.
Loading from a Python file¶
from_pyfile executes a standalone .py file and copies its uppercase
module-level names. The file does not need to be importable as a package.
from pathlib import Path
from veloce import Veloce
Path("production.cfg").write_text(
"DEBUG = False\n"
"SECRET_KEY = 'loaded-from-file'\n"
"MAX_CONTENT_LENGTH = 5 * 1024 * 1024\n"
)
app = Veloce()
app.config.from_pyfile("production.cfg")
print(app.config["MAX_CONTENT_LENGTH"]) # 5242880
Pass silent=True to treat a missing file as a no-op (returns False)
instead of raising OSError. Errors raised inside the file always
propagate — a typo in a settings file is a real bug, not something to
swallow.
from_pyfile executes arbitrary code
from_pyfile runs the file with exec. Only point it at files you
control. Never load a config file from an untrusted or user-supplied
path.
from_envvar combines the two: it reads a filename from a named
environment variable and feeds it to from_pyfile.
import os
from veloce import Veloce
os.environ["MYAPP_SETTINGS"] = "production.cfg"
app = Veloce()
app.config.from_envvar("MYAPP_SETTINGS")
Loading from the environment¶
from_prefixed_env pulls every environment variable that begins with a
prefix (default VELOCE), strips the <prefix>_ part, and stores the
remainder. Values are JSON-decoded, falling back to the raw string when
JSON parsing fails — so VELOCE_DEBUG=true becomes the boolean True,
while VELOCE_SECRET_KEY=abc stays the string "abc".
import os
from veloce import Veloce
os.environ["VELOCE_DEBUG"] = "true" # JSON -> True
os.environ["VELOCE_SECRET_KEY"] = "s3cret" # not JSON -> "s3cret"
os.environ["VELOCE_MAX_CONTENT_LENGTH"] = "1048576" # JSON -> 1048576
app = Veloce()
app.config.from_prefixed_env()
print(app.config["DEBUG"]) # True
print(app.config["SECRET_KEY"]) # "s3cret"
print(app.config["MAX_CONTENT_LENGTH"]) # 1048576
A double underscore in the variable name builds nested configuration:
VELOCE_MAIL__SERVER sets app.config["MAIL"]["SERVER"].
import os
from veloce import Veloce
os.environ["VELOCE_MAIL__SERVER"] = "smtp.example.com"
os.environ["VELOCE_MAIL__PORT"] = "587"
app = Veloce()
app.config.from_prefixed_env()
print(app.config["MAIL"]) # {'SERVER': 'smtp.example.com', 'PORT': 587}
Pass a custom prefix to read a different namespace, and a custom loads
callable to change how values are decoded.
Loading from a dotenv file¶
from_env_file reads KEY=VALUE pairs from a .env-style file. It skips
blank lines and # comment lines, accepts an optional export prefix,
and unquotes values wrapped in matching single or double quotes. Values
are always stored as strings — a .env file carries no types.
from pathlib import Path
from veloce import Veloce
Path(".env").write_text(
"# application settings\n"
"SECRET_KEY='dotenv-secret'\n"
"export SERVER_NAME=example.com\n"
)
app = Veloce()
app.config.from_env_file() # defaults to ".env"
print(app.config["SECRET_KEY"]) # "dotenv-secret"
print(app.config["SERVER_NAME"]) # "example.com"
Note
from_env_file keeps values as raw strings. When you need typed values
(booleans, integers, nested mappings) from the environment, prefer
from_prefixed_env, which JSON-decodes each value.
As with the other loaders, silent=True turns a missing file into a
False return instead of an exception.
Loading from a structured file¶
from_file handles any structured format by accepting a load callable
that takes an open file object and returns a mapping. The default loader
parses JSON. Pass text=True to open the file in text mode for loaders
that expect a string (most do); the default binary mode suits JSON and
TOML.
import tomllib
from pathlib import Path
from veloce import Veloce
Path("config.toml").write_text(
'SECRET_KEY = "from-toml"\n'
"MAX_FORM_PARTS = 50\n"
)
app = Veloce()
app.config.from_file("config.toml", load=tomllib.load)
print(app.config["MAX_FORM_PARTS"]) # 50
The returned object must be a mapping; anything else raises TypeError.
Bulk updates and namespaces¶
from_mapping applies a mapping and/or keyword arguments in one call,
respecting the uppercase rule:
from veloce import Veloce
app = Veloce()
app.config.from_mapping(
{"DEBUG": False},
SECRET_KEY="from-mapping",
MAX_COOKIE_SIZE=8192,
)
get_namespace extracts every key that shares a prefix, which is handy
for passing one subsystem's settings to an extension. By default the
prefix is trimmed and the remaining key is lower-cased:
from veloce import Veloce
app = Veloce()
app.config.from_mapping(
MAIL_SERVER="smtp.example.com",
MAIL_PORT=587,
MAIL_USE_TLS=True,
)
mail = app.config.get_namespace("MAIL_")
print(mail) # {'server': 'smtp.example.com', 'port': 587, 'use_tls': True}
Pass lowercase=False to keep the original case, or trim_namespace=False
to keep the prefix on each returned key.
A typical startup¶
The loaders compose. A common arrangement loads a base object, overlays an environment file, then lets real environment variables win:
import types
from veloce import Veloce
base = types.SimpleNamespace(DEBUG=False, JSON_SORT_KEYS=True)
app = Veloce()
app.config.from_object(base) # defaults from a base object
app.config.from_env_file(silent=True) # overlay a local .env if present
app.config.from_prefixed_env() # environment wins last
Later loaders overwrite earlier values for the same key, so order is the precedence order.
Next steps¶
- Error handling — turn configuration like
PROPAGATE_EXCEPTIONSinto the right behaviour for failed requests, and register custom error responses. - Sessions and Middleware — the
SESSION_COOKIE_*andSECRET_KEYkeys drive session signing and cookie attributes. - The API reference documents the public
velocesurface;Configand its loader methods live inveloce.config.