Skip to main content

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:

  1. explicit decorator path
  2. model-local __section__ if non-empty (__dict__ only, inherited __section__ is ignored)
  3. 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 with default (otherwise unresolved).
  • field:
    • optional dotted sub-path applied after target is resolved.
    • blank string is invalid.
  • 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 @SettingsMap section 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.
  • 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_settings writes FASTAPIEX__SETTINGS__PATH into process os.environ before 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.

Bootstrap resolution order:

  • FASTAPIEX__SETTINGS__PATH
  • FASTAPIEX__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.
  • reason is logging context string.

exceptions

from fastapiex.settings import exceptions

Module members:

  • exceptions.SettingsError
  • exceptions.SettingsRegistrationError
  • exceptions.SettingsValidationError
  • exceptions.SettingsResolveError

Runtime Semantics

Registration

  • Only @Settings / @SettingsMap register sections.
  • Undecorated BaseSettings subclasses 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 / .yml files)
  • .env file
  • process os.environ

Path target semantics:

  • settings.path is the only explicit settings path control, and it may point to either a file or a directory.
  • FASTAPIEX__BASE_DIR is a fallback anchor used only when settings.path is 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 yaml source supports directory anchors and explicit .yaml / .yml files; 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.
  • .env and 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; .env stays from the bootstrap directory.
  • this is intentional by convention: .env is 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 .env source spec.
  • register_source(...): replace the .env source spec with an updated one.
  • auto_refresh=True: re-read .env during auto refresh passes (RELOAD=on_change|always).
  • manual_refresh=True: include .env in manual reload_settings().
  • follow_context=True: when runtime settings file path switches, also rebind .env to 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) to off.
  • 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 under fastapiex.*.
  • 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 canonical fastapiex.*.

Supported controls:

  • FASTAPIEX__SETTINGS__PATH
  • FASTAPIEX__BASE_DIR (fallback anchor used when FASTAPIEX__SETTINGS__PATH is not set)
  • FASTAPIEX__SETTINGS__ENV_PREFIX
  • FASTAPIEX__SETTINGS__CASE_SENSITIVE
  • FASTAPIEX__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 when CASE_SENSITIVE=true.

Equivalent examples (all readable by GetSettings("one")):

  • FASTAPIEX__SETTINGS__ENV_PREFIX=SOME_CUSTOM_PREFIX__ + SOME_CUSTOM_PREFIX__ONE=1
  • FASTAPIEX__SETTINGS__ENV_PREFIX=SOME_CUSTOM_PREFIX_ + SOME_CUSTOM_PREFIX_ONE=1
  • FASTAPIEX__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 (default false)

Behavior:

  • false: case-insensitive lookup for read/env business keys.
  • true: case-sensitive.
  • Windows (os.name == "nt"): true is ignored and behaves as false.
  • FastAPIEx / FastAPIEx.* lookup is always case-insensitive (independent from CASE_SENSITIVE).
  • When false, if sibling keys collapse to the same folded name (for example APP and app), string-path lookup is treated as ambiguous and raises resolve error.

Reload Mode

Control:

  • FASTAPIEX__SETTINGS__RELOAD=off|on_change|always (default off)

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

fastapiex_settings-0.0.7.tar.gz (110.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapiex_settings-0.0.7-py3-none-any.whl (39.7 kB view details)

Uploaded Python 3

File details

Details for the file fastapiex_settings-0.0.7.tar.gz.

File metadata

  • Download URL: fastapiex_settings-0.0.7.tar.gz
  • Upload date:
  • Size: 110.1 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

Hashes for fastapiex_settings-0.0.7.tar.gz
Algorithm Hash digest
SHA256 009fa2588fa954bd3f69b94b2439f8c81a8d5170f3d876678415afc47d613967
MD5 c0723ef6c66ac579cd31bd527d795df5
BLAKE2b-256 010eea86a28e89cad03fc6c1ac108351a488c70753c894fe8647ad6a64a59166

See more details on using hashes here.

File details

Details for the file fastapiex_settings-0.0.7-py3-none-any.whl.

File metadata

  • Download URL: fastapiex_settings-0.0.7-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

Hashes for fastapiex_settings-0.0.7-py3-none-any.whl
Algorithm Hash digest
SHA256 25dddd814359fc86288ec3e7a4d804b194d8825034817fd67352b4408ee3f6ea
MD5 d43089a88b5ed76b93a403dfabdf01ab
BLAKE2b-256 26fdcb6b9a2a28c0a10d7cca3f771c9d879352d534e1cab58b0ffd246ee82c4f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page