fastapi-settings
Centralized, lazy application settings for Python and FastAPI projects, with a Django-inspired import experience and no required runtime dependencies.
fastapi-settings deliberately does not replace Pydantic Settings. It provides a common settings proxy, configurable module discovery, independent settings containers, and temporary test overrides. Environment parsing and value validation remain the application's responsibility.
Installation
pip install fastapi-settings
CPython 3.11 or newer is required. Supported versions are CPython 3.11, 3.12, 3.13, 3.14, and 3.15. PyPy and other Python implementations are not currently supported. FastAPI, Pydantic, and Pydantic Settings are optional.
Quick start
Create an importable Python settings module:
# config/settings.py
DEBUG = False
DATABASE_URL = "postgresql://localhost/app"
FASTAPI_LOGGING = {"level": "INFO"}
Select it with an environment variable:
export FASTAPI_SETTINGS_MODULE=config.settings
Use the settings anywhere:
from fastapi_settings import settings
if settings.DEBUG:
print("Debug mode enabled")
The settings module is imported only when a value is first accessed. A selected module is imported through Python's standard import system, including its normal module caching behavior. The proxy takes a shallow snapshot of its top-level settings values at initialization.
Explicit configuration
Instead of setting FASTAPI_SETTINGS_MODULE, explicitly configure the default proxy before accessing any settings:
from fastapi_settings import configure, settings
configure(module="config.settings")
print(settings.DEBUG)
Or pass an existing object or mapping:
configure(settings_object={"DEBUG": False, "PORT": 8000})
Pass exactly one of module or settings_object. Re-selecting the same module or the same object instance is idempotent; changing the source raises SettingsAlreadyConfiguredError. If FASTAPI_SETTINGS_MODULE is defined, explicit global configuration must select that exact module. After source selection, environment changes do not affect the proxy.
Pydantic Settings
Use Pydantic for typed fields, environment variables, .env files, or secrets, then export a SETTINGS instance from your module:
# config/settings.py
from pydantic_settings import BaseSettings, SettingsConfigDict
class ApplicationSettings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="APP_", env_file=".env")
debug: bool = False
database_url: str
SETTINGS = ApplicationSettings()
from fastapi_settings import settings
print(settings.debug)
print(settings.database_url)
The package preserves field names and delegates all validation, environment precedence, and dotenv behavior to Pydantic. Install Pydantic Settings separately if you use this option:
pip install pydantic-settings
Source selection: A module must contain either public uppercase bindings or one SETTINGS object, not both. Every public uppercase binding is a setting, whether defined locally or imported, including scalar constants, classes, functions, and intentional re-exports:
from socket import AF_INET
from pathlib import Path as PATH_CLASS
DEBUG = True
# settings.AF_INET, settings.PATH_CLASS, and settings.DEBUG are all available.
Private names, internal Python module attributes, lowercase bindings, and imported module objects are ignored in module-level mode. An imported uppercase binding alongside SETTINGS raises InvalidSettingsSourceError, just like a locally defined uppercase setting. Use lowercase or private aliases for helper imports in SETTINGS mode. No import-provenance inference or automatic case conversion occurs.
A SETTINGS object may be a mapping, a dataclass instance, or an ordinary object with public instance attributes. Mapping keys must be valid public Python attribute names. Ordinary object class attributes and properties are not included in the snapshot.
Initialization and recovery
The lifecycle is unconfigured → configured → initializing → initialized. Source selection is permanent and follows the module-path/object-identity rules above. A failed attempt returns to configured, without publishing a snapshot; a later access retries the same source. There is no automatic reload after success.
Each proxy has one initialization owner. Ordinary concurrent readers wait for that attempt to finish. Imports and source-object inspection execute outside the proxy state lock; only source selection, ownership, snapshot publication, and waiter notification use that lock. All waiting readers are notified after success or failure, including cancellation through a BaseException.
Access during an incomplete settings-module import raises SettingsInitializationError. Do not read the proxy from the settings module or its import-time dependencies. If application code catches this exception and lets the module finish importing, a subsequent access can initialize the complete snapshot.
Recursive access by the initialization owner also raises SettingsInitializationError. A caller already executing import code or initializing another proxy will not wait on a busy proxy: it raises the same exception to prevent import-lock and cross-proxy cycles. Acyclic access to an idle independent proxy remains supported. Unrelated concurrent callers continue to wait normally. The package cannot resolve deadlocks created entirely by application locks or by application code joining workers that depend on its own initialization.
Python has no public import-completion query. Import-state detection relies on the private CPython implementation attribute ModuleSpec._initializing, checked before and after import, and active import frames to decide when waiting is unsafe. It does not acquire private importlib locks. CPython changes to this private attribute may require compatibility updates; this is why support currently targets CPython. Real-import subprocess tests run in the CPython 3.11–3.15 CI matrix. Manually registered, already constructed modules without that marker remain supported.
Independent containers
Use separate settings proxies when multiple configurations are needed within one Python process:
from fastapi_settings import create_settings
a = create_settings(module="config.settings.development")
b = create_settings(settings_object={"DEBUG": False})
print(a.DEBUG, b.DEBUG)
Unlike the default global settings, independent proxies never consult FASTAPI_SETTINGS_MODULE and require an explicit source.
Isolated testing overrides
from fastapi_settings import create_settings
from fastapi_settings.testing import override_settings
config = create_settings(settings_object={"DEBUG": False})
with override_settings(config, DEBUG=True):
assert config.DEBUG is True
assert config.DEBUG is False
For the default global proxy, omit the first argument:
from fastapi_settings import settings
from fastapi_settings.testing import override_settings
with override_settings(DEBUG=True):
assert settings.DEBUG
An override must target existing top-level settings, replaces the entire value (no deep merge), and is automatically cleaned up even after an exception. Overrides do not validate types; create a Pydantic settings object yourself if you need validation.
Overrides use Python contextvars for thread and asyncio-task isolation. A newly created child asyncio task inherits its creator's context: if it outlives an override block, it can continue seeing that inherited override. Avoid spawning long-lived child tasks inside test override scopes.
FastAPI dependencies
FastAPI's native dependency injection remains available; no special integration layer is needed:
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi_settings import SettingsProxy, settings
app = FastAPI()
def get_settings() -> SettingsProxy:
return settings
@app.get("/health")
def health(config: Annotated[SettingsProxy, Depends(get_settings)]):
return {"debug": config.DEBUG}
FastAPI's app.dependency_overrides can replace get_settings in integration tests. Synchronous and asynchronous endpoints can use the same proxy. To fail before serving requests, access a required setting in a FastAPI lifespan handler; otherwise initialization remains lazy until the first access.
The proxy preserves original Pydantic field values and types, but its dynamic attributes do not provide static field checking. Return a concrete Pydantic settings class directly from a dependency when static typing matters more than proxy discovery and scoped overrides. Constructing a model before passing it to create_settings runs Pydantic initialization immediately; exporting a model from a lazily imported settings module defers construction until that module is imported. Overrides do not rerun model validation.
See the runnable examples for module-level settings, Pydantic environment configuration, native dependency overrides, and independent applications. Pydantic remains responsible for source precedence, custom sources, nested models, secrets, and validation.
Configuration contract
| Detail | Behavior |
|---|---|
| Discovery | FASTAPI_SETTINGS_MODULE or explicit configure() |
| First access | Loads once and caches the resulting top-level snapshot |
| Object sources | Mapping, dataclass, or public instance attributes |
| Missing value | Normal AttributeError |
| Assignment or deletion | Rejected (AttributeError) |
Mapping-style settings["KEY"] |
Not supported |
.env parsing |
Not performed by the core; delegate to Pydantic or your application |
| Runtime reload | Not supported |
| Deep immutability | Not provided; nested mutable values remain shared |
| Production configuration mutation | Not supported |
| Testing | Context-local, reversible top-level replacements |
The public exceptions are exported from fastapi_settings and fastapi_settings.exceptions:
| Exception | Meaning |
|---|---|
FastAPISettingsError |
Base class for package-managed configuration errors |
SettingsNotConfiguredError |
No source is selected when a value is accessed |
SettingsAlreadyConfiguredError |
A different source or conflicting environment module is selected |
SettingsModuleImportError |
The selected module failed to import; its original exception is chained |
InvalidSettingsSourceError |
Empty or unsupported source, invalid mapping keys, or mixed module styles |
SettingsInitializationError |
Recursive, incomplete-import, or unsafe cyclic initialization |
SettingsOverrideError |
Empty overrides or unknown setting names |
Invalid call arguments raise TypeError or ValueError. Missing attributes on an initialized proxy raise AttributeError. Exceptions raised while inspecting a custom source object remain application exceptions.
configure() returns None and selects the global source without loading it. create_settings() returns a lazy SettingsProxy. The exported SettingsProxy name is available for annotations and instance checks; use the factory to construct configured independent instances. Empty mappings and objects with no public settings are rejected. Ordinary public slots are included alongside public instance attributes; properties and class attributes are excluded.
The proxy's representation never includes configuration values. Module import exceptions retain their Python exception causes for debugging, which means application or dependency exceptions might still reveal information in a traceback; secure the application-level error handling and logging accordingly. Treat settings modules as trusted application code, never as untrusted user-supplied paths.
Development
We use uv to manage project environments and dependencies. We also use the uv_build backend to create wheels and source distributions. Plain uv sync installs the default dev group, including test and quality tools. To use the checked-in lockfile without updating it and run the suite:
uv sync --locked
uv run --locked pytest -q --cov=fastapi_settings
uv run --locked ruff check src tests examples .github/scripts
uv run --locked ruff format --check src tests examples .github/scripts
uv run --locked mypy src/
The quality extra pins Ruff to 0.17.3, and the Ruff configuration requires that exact version locally and in CI. Update the pin and required version together after checking the full quality suite. It also provides mypy, Twine, Commitizen, pre-commit, tox, Bandit, and TOML/tox formatters. Ruff formats Python source, tests, examples, and CI scripts; Markdown is maintained separately.
Coverage measures both statements and branches, including subprocess import and concurrency regressions, and requires 100%. Each CPython matrix job runs the full suite with coverage, builds distributions, tests the extracted source archive, compares its rebuilt wheel's contents, and installs the wheel in a fresh environment without optional dependencies. Lint, formatting, type checks, and strict package-description validation run in a separate quality job. A configured matrix is not evidence of successful execution; consult the actual workflow results for compatibility validation.
To run the same distribution validation locally after building:
uv build
uv run --locked python -m twine check --strict dist/*
uv run --locked python .github/scripts/validate_distribution.py dist --test-sdist
Build into an empty dist directory. See the contributor guide and security policy.
Use uv run --locked cz commit for Conventional Commits with the relevant issue footer. See AUTHORS.md, CONTRIBUTORS.md, and CODE_OF_CONDUCT.md for project and community information.
Metadata
Release files for fastapi-settings 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| fastapi_settings-1.0.0.tar.gz | 175.5 kB | Without provenance | Details for fastapi_settings-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| fastapi_settings-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for fastapi_settings-1.0.0-py3-none-any.whl |
Total release size: 191.6 kB
Release files / fastapi_settings-1.0.0.tar.gz
| Download URL | fastapi_settings-1.0.0.tar.gz |
|---|---|
| Size | 175.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d5aed6e3c549e71b6f64a02cb1114857dd07fdddb141c20fad61ceb763114aee
|
|
BLAKE2b-256 checksum How to use checksums |
e21a0b3fcd6812573cf50c31676f44631f9564873596e13c4aade3d50b752100
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}
|
Release files / fastapi_settings-1.0.0-py3-none-any.whl
| Download URL | fastapi_settings-1.0.0-py3-none-any.whl |
|---|---|
| Size | 16.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f9562ff03a7ec56993464a0c28ebddd616f2ec88b1c3b070f4601e81bb9050c7
|
|
BLAKE2b-256 checksum How to use checksums |
6be15b087f4f096fd679c03b275df0d3600159b206a0776fe742d1cc5de35cb4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","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}
|
Security for fastapi-settings 1.0.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.