Typed Settings
Typed Settings is an active Python library (current version 25.3.0) that simplifies loading and merging application settings from various sources like config files (TOML, JSON, YAML), environment variables, and secret vaults into structured, type-hinted classes (attrs, dataclasses, or Pydantic models). It also supports generating command-line interfaces (CLIs) based on these settings. The library follows a calendar versioning scheme and maintains a relatively frequent release cadence, often with monthly updates, reflecting continuous development and use in production environments.
Warnings
- breaking Since version 25.0.0, dictionary values are no longer merged across different settings sources; instead, they are fully overridden. This changes the behavior from previous versions where dictionaries might have been deeply merged.
- breaking As of a past breaking change, a `ValueError` is now raised if a config file contains options not defined in the settings class. Previously, these might have been silently ignored.
- breaking For CLI integration, Click options without a default value (or a loaded value from other sources) are now automatically marked as `required=True`.
- gotcha Typed Settings, since version 23.1.0, automatically resolves paths loaded from config files relative to the config file's directory, and paths from environment variables/CLI args relative to the current working directory. This might differ from previous behavior where paths were treated as absolute or un-resolved.
- gotcha Passing secrets directly via environment variables is strongly discouraged due to security risks (e.g., accidental leaks in logs or CI/CD).
- deprecated Support for Python 3.7 was dropped with the 23.1.0 release. Users on Python 3.7 will need to upgrade their Python version.
- breaking The converter API and internal settings dict underwent breaking changes in version 23.1.0. This primarily affects users who have written custom loaders or extended the default converter.
Install
-
pip install typed-settings -
pip install typed-settings[attrs,cattrs,click,dotenv,jinja,orjson,pydantic,yaml]
Imports
- settings
from typed_settings import settings
- load
from typed_settings import load
- SecretStr
from typed_settings import SecretStr
- find
from typed_settings import find
- default_loaders
from typed_settings import default_loaders
Quickstart
import attrs
from pathlib import Path
import typed_settings as ts
import os
@attrs.frozen
class DatabaseSettings:
host: str = "localhost"
port: int = 5432
user: str = "admin"
password: ts.SecretStr = ts.SecretStr("")
@ts.settings
class AppSettings:
debug: bool = False
log_level: str = "INFO"
database: DatabaseSettings = attrs.field(factory=DatabaseSettings)
# Simulate a config file
config_content = '''
[myapp]
debug = true
log-level = "DEBUG"
[myapp.database]
host = "my.db.server"
port = 6000
'''
config_file_path = Path("settings.toml")
config_file_path.write_text(config_content)
# Simulate environment variables
os.environ['MYAPP_DATABASE_USER'] = os.environ.get('MYAPP_DATABASE_USER', 'env_user')
os.environ['MYAPP_DATABASE_PASSWORD'] = os.environ.get('MYAPP_DATABASE_PASSWORD', 'env_secret')
try:
settings = ts.load(
cls=AppSettings,
appname="myapp",
config_files=[config_file_path],
config_file_section="myapp",
)
print(f"Debug: {settings.debug}")
print(f"Log Level: {settings.log_level}")
print(f"DB Host: {settings.database.host}")
print(f"DB Port: {settings.database.port}")
print(f"DB User: {settings.database.user}")
print(f"DB Password (hidden): {settings.database.password}")
except Exception as e:
print(f"Error loading settings: {e}")
finally:
config_file_path.unlink(missing_ok=True)
del os.environ['MYAPP_DATABASE_USER']
del os.environ['MYAPP_DATABASE_PASSWORD']