Global settings runtime extension for FastAPIEx
Project description
fastapiex-settings
Process-global settings declaration and resolution runtime based on Pydantic v2.
Installation
uv add fastapiex-settings
Public API (Only These Are Exported)
from fastapiex.settings import (
BaseSettings,
Settings,
SettingsMap,
GetSettings,
GetSettingsMap,
SettingsRef,
init_settings,
reload_settings,
exceptions,
)
No other symbol is part of the package root public API.
Quick Start
from pydantic import Field
from fastapiex.settings import (
BaseSettings,
Settings,
SettingsMap,
GetSettings,
GetSettingsMap,
init_settings,
reload_settings,
)
@Settings("app")
class AppSettings(BaseSettings):
title: str = "demo"
debug: bool = False
@SettingsMap("services")
class ServiceSettings(BaseSettings):
host: str
port: int = Field(ge=1, le=65535)
init_settings(settings_path="settings.yaml")
title = GetSettings("app", field="title")
services = GetSettingsMap(ServiceSettings) # dict[str, ServiceSettings]
api_host = GetSettings("services.api", field="host")
reload_settings(reason="manual-refresh")
Public API Reference
BaseSettings
class BaseSettings(pydantic.BaseModel): ...
- Base class for declaration models.
- Inheriting this class does not register anything by itself.
Settings
@overload
def Settings(model: type[BaseSettings], /) -> type[BaseSettings]: ...
@overload
def Settings(path: str | None = None, /) -> Callable[[type[BaseSettings]], type[BaseSettings]]: ...
Role:
- Declare an object section.
Section name resolution order:
- explicit decorator path
- model-local
__section__if non-empty (__dict__only, inherited__section__is ignored) - snake_case class name
Accepted forms:
@Settings@Settings("father.son")Settings(MyModel)(functional form)
Notes:
- Decorators do not write implicit metadata back to model classes (for example, they do not set
__section__).
SettingsMap
@overload
def SettingsMap(model: type[BaseSettings], /) -> type[BaseSettings]: ...
@overload
def SettingsMap(path: str | None = None, /) -> Callable[[type[BaseSettings]], type[BaseSettings]]: ...
Role:
- Declare a map section (shape like
dict[str, Model]).
Accepted forms and section naming:
- Same behavior as
Settings.
GetSettings
def GetSettings(
target: str | type[object] | None = None,
*,
field: str | None = None,
default: object = _NO_DEFAULT,
) -> Any
Role:
- Resolve settings values from the current typed snapshot.
Parameters:
target:str: dotted path, for example"app"/"father.son"/"services.api".type: type-category injection; must match exactly one declared section.None: only useful withdefault(otherwise unresolved).
field:- optional dotted sub-path applied after
targetis resolved. - blank string is invalid.
- optional dotted sub-path applied after
default:- returned as the whole query fallback value.
- not projected by
field.
Important semantics:
- Read chain:
registered lookup -> rediscover+lookup -> default -> error. - Incomplete path returns the current node as-is (model/dict/scalar/list element).
- Mapping type target (
dict,Mapping) resolves only when exactly one@SettingsMapsection exists. - Returned objects are live runtime references (mutable); changes affect subsequent reads in the same process.
GetSettingsMap
def GetSettingsMap(
target: str | type[object] | None = None,
*,
default: object = _NO_DEFAULT,
) -> Mapping[str, Any]
Role:
- Resolve settings as mapping-only API.
Parameters:
target:- same semantics as
GetSettings.
- same semantics as
default:- whole-query fallback value.
- must be mapping when fallback path is used.
Behavior:
- Final value must be mapping; otherwise
SettingsResolveError. - Returned mapping is a live runtime reference (mutable), not a defensive copy.
SettingsRef
@dataclass(frozen=True)
class SettingsRef:
target: str | type[object] | None
field: str | None = None
default: object = _NO_DEFAULT
def get(self) -> Any: ...
@property
def value(self) -> Any: ...
def __call__(self) -> Any: ...
Role:
- Lazy settings query descriptor.
Behavior:
get(),.value, and()perform the same runtime resolve.- Resolution semantics are identical to
GetSettings.
init_settings
def init_settings(
*,
settings_path: str | Path | None = None,
) -> BaseModel
Role:
- Initialize process-global source and snapshot.
Parameter behavior:
settings_path:- when provided,
init_settingswritesFASTAPIEX__SETTINGS__PATHinto processos.environbefore bootstrap. - this is a process-global side effect (same process, later refreshes/re-reads see this value).
- runtime still follows normal control resolution; path can still be changed later by controls.
- when provided,
Bootstrap resolution order:
FASTAPIEX__SETTINGS__PATHFASTAPIEX__BASE_DIR+/settings.yaml./settings.yaml
When settings_path arg is passed, it first overwrites FASTAPIEX__SETTINGS__PATH, then uses the same order above.
Runtime source switch:
- after each yaml read, manager checks merged live snapshot key
FastAPIEx.settings.path(fastapiex.settings.path) and switches yaml source to that path when changed. - this key is treated as an ordinary snapshot key (LWW + priority), not a dedicated control state branch.
Re-init behavior:
- Re-initializing with a different resolved runtime context raises
RuntimeError. - Runtime context includes: settings path/anchor/mode, env prefix, case mode, reload mode.
reload_settings
def reload_settings(*, reason: str = "manual") -> BaseModel
Role:
- Trigger a manual runtime refresh of current settings source.
Behavior:
- Calls manager-level reload flow.
reasonis logging context string.
exceptions
from fastapiex.settings import exceptions
Module members:
exceptions.SettingsErrorexceptions.SettingsRegistrationErrorexceptions.SettingsValidationErrorexceptions.SettingsResolveError
Runtime Semantics
Registration
- Only
@Settings/@SettingsMapregister sections. - Undecorated
BaseSettingssubclasses are ignored by registry discovery. - Section paths under
FASTAPIEX.*are reserved and rejected (case-insensitive). - Registration stores section paths as declared (case-preserving), independent from runtime
CASE_SENSITIVE. - If multiple declared paths differ only by case, case-insensitive string lookups are ambiguous and fail; type-target lookups remain exact.
Source Loading
Sources:
yaml(settings.yaml, or explicit.yaml/.ymlfiles).envfile- process
os.environ
Path target semantics:
settings.pathis the only explicit settings path control, and it may point to either a file or a directory.FASTAPIEX__BASE_DIRis a fallback anchor used only whensettings.pathis not set.- directory anchors resolve to
${anchor_dir}/settings.yaml. - existing files are explicit file targets.
- missing paths with a suffix (for example
settings.toml) are explicit file targets. - missing paths without a suffix are directory anchors.
- the builtin
yamlsource supports directory anchors and explicit.yaml/.ymlfiles; other explicit extensions require a registered config source that supports them.
Merge strategy:
- LWW (last-write-wins by source revision/update order)
- if revision ties:
env > .env > yaml
Runtime reload:
- yaml may refresh by mode.
.envand process env are loaded once at init time and not re-ingested by runtime yaml reload.- when yaml source switches by
FastAPIEx.settings.path, only yaml source is switched/reloaded;.envstays from the bootstrap directory. - this is intentional by convention:
.envis treated as an environment-variable generator, and runtime business env should be stable unless explicitly opted in.
Opt-in .env runtime sync (advanced):
- This is not part of the package-root public API. Use manager helper explicitly:
from dataclasses import replace
from fastapiex.settings import reload_settings
from fastapiex.settings.manager import get_source, register_source
dotenv = get_source("dotenv")
assert dotenv is not None
register_source(
replace(
dotenv,
policy=replace(
dotenv.policy,
auto_refresh=True,
manual_refresh=True,
follow_context=True,
),
)
)
# optional: force one immediate re-ingest after enabling sync
reload_settings(reason="enable-dotenv-sync")
Parameter meaning:
get_source("dotenv"): read the current builtin.envsource spec.register_source(...): replace the.envsource spec with an updated one.auto_refresh=True: re-read.envduring auto refresh passes (RELOAD=on_change|always).manual_refresh=True: include.envin manualreload_settings().follow_context=True: when runtime settings file path switches, also rebind.envto the new anchor directory.
Monkeypatch / Black Magic
GetSettings / GetSettingsMap expose mutable live objects by design.
Recommended safety flow when manually patching settings at runtime:
- before monkeypatching, set
FastAPIEx.settings.reload(fastapiex.settings.reload) tooff. - perform the manual mutation.
- dynamic imports may still register new settings declarations; when no source reload occurs, those schema-only updates preserve existing live values and only hydrate newly declared or redefined paths from the current raw snapshots.
- when done and you want to restore canonical values from configured sources, call
reload_settings(reason="...").
Runtime Controls (FASTAPIEX__*)
Plain-key behavior:
FASTAPIEX__*keys are ingested into live snapshot as plain keys underfastapiex.*.- they are readable via
GetSettings("fastapiex...."). - declaration is still forbidden: users cannot declare
@Settings("fastapiex...")/@SettingsMap("fastapiex..."). - mixed-case keys from file/env (for example
FastAPIEx.Settings.Reload) are normalized into canonicalfastapiex.*.
Supported controls:
FASTAPIEX__SETTINGS__PATHFASTAPIEX__BASE_DIR(fallback anchor used whenFASTAPIEX__SETTINGS__PATHis not set)FASTAPIEX__SETTINGS__ENV_PREFIXFASTAPIEX__SETTINGS__CASE_SENSITIVEFASTAPIEX__SETTINGS__RELOAD
FASTAPIEX__SETTINGS__ENV_PREFIX rules:
- cannot start with
FASTAPIEX__. - empty prefix means plain keys are considered business keys.
- non-empty prefix is stripped as raw string.
- prefix matching follows runtime case mode: case-insensitive when
CASE_SENSITIVE=false, exact whenCASE_SENSITIVE=true.
Equivalent examples (all readable by GetSettings("one")):
FASTAPIEX__SETTINGS__ENV_PREFIX=SOME_CUSTOM_PREFIX__+SOME_CUSTOM_PREFIX__ONE=1FASTAPIEX__SETTINGS__ENV_PREFIX=SOME_CUSTOM_PREFIX_+SOME_CUSTOM_PREFIX_ONE=1FASTAPIEX__SETTINGS__ENV_PREFIX=SOME_CUSTOM_PREFIX+SOME_CUSTOM_PREFIXONE=1- empty prefix +
ONE=1
Env split semantics:
- delimiter is
__ FOO___BAR->["FOO", "_BAR"]- keys that produce empty segments are ignored (for example
FOO____BAR,__X,X__)
Case Sensitivity
Control:
FASTAPIEX__SETTINGS__CASE_SENSITIVE=true|false(defaultfalse)
Behavior:
false: case-insensitive lookup for read/env business keys.true: case-sensitive.- Windows (
os.name == "nt"):trueis ignored and behaves asfalse. FastAPIEx/FastAPIEx.*lookup is always case-insensitive (independent fromCASE_SENSITIVE).- When
false, if sibling keys collapse to the same folded name (for exampleAPPandapp), string-path lookup is treated as ambiguous and raises resolve error.
Reload Mode
Control:
FASTAPIEX__SETTINGS__RELOAD=off|on_change|always(defaultoff)
Behavior:
off: no auto yaml sync.on_change: sync yaml when file state changes.always: sync yaml on each read.- module delta changes still trigger declaration rediscovery and schema rebuild.
- schema-only rebuilds preserve existing live values when no source snapshot changed.
- runtime controls are resolved from current live snapshot (not direct env re-read).
Intentionally Not Public at Package Root
These capabilities still exist internally, but are not exported from fastapiex.settings:
- manager/registry direct access helpers
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastapiex_settings-0.0.6.tar.gz.
File metadata
- Download URL: fastapiex_settings-0.0.6.tar.gz
- Upload date:
- Size: 103.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d91188e1ecaf7b20736be78dc636216d73a0a5a4a02e3d893ffead3b05eac6c
|
|
| MD5 |
b708444e1b23eaa3afdc15556efe4185
|
|
| BLAKE2b-256 |
ceea50b32c39fe68ef17ab5d66ce96ca6440b03f8e158485b79956ec6ed0e7a1
|
File details
Details for the file fastapiex_settings-0.0.6-py3-none-any.whl.
File metadata
- Download URL: fastapiex_settings-0.0.6-py3-none-any.whl
- Upload date:
- Size: 39.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70c950adc8195775138d4cb89943e3fc1489cf20b5ac2803bda27191588ed954
|
|
| MD5 |
53d6556a15c6db3e1521b539e9ad8e46
|
|
| BLAKE2b-256 |
2726ac4f94628bf006d3389b62019a63bb7df911a92536ac76b46a60b8c0d89c
|