{"id":2821,"library":"typed-settings","title":"Typed Settings","description":"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.","status":"active","version":"25.3.0","language":"en","source_language":"en","source_url":"https://github.com/sscherfke/typed-settings","tags":["settings","configuration","types","attrs","dataclasses","pydantic","cli","env-vars","config-files"],"install":[{"cmd":"pip install typed-settings","lang":"bash","label":"Base installation"},{"cmd":"pip install typed-settings[attrs,cattrs,click,dotenv,jinja,orjson,pydantic,yaml]","lang":"bash","label":"Full installation with all optional features"}],"dependencies":[{"reason":"Recommended for defining settings classes, via `typed-settings[attrs]`.","package":"attrs","optional":true},{"reason":"Alternative for defining settings classes, via `typed-settings[pydantic]`.","package":"pydantic","optional":true},{"reason":"Powerful and fast converter; used by default if available, via `typed-settings[cattrs]`.","package":"cattrs","optional":true},{"reason":"For generating Click-based command-line interfaces, via `typed-settings[click]`.","package":"click","optional":true},{"reason":"For reading .env files, via `typed-settings[dotenv]`.","package":"python-dotenv","optional":true},{"reason":"For value interpolation with Jinja templates, via `typed-settings[jinja]`.","package":"jinja2","optional":true},{"reason":"Faster JSON loading, via `typed-settings[orjson]`.","package":"orjson","optional":true},{"reason":"For loading YAML files, via `typed-settings[yaml]`.","package":"PyYAML","optional":true},{"reason":"Required for TOML support on Python <= 3.10; used by default on 3.11+ via `tomllib`.","package":"tomli","optional":false}],"imports":[{"symbol":"settings","correct":"from typed_settings import settings"},{"symbol":"load","correct":"from typed_settings import load"},{"symbol":"SecretStr","correct":"from typed_settings import SecretStr"},{"symbol":"find","correct":"from typed_settings import find"},{"symbol":"default_loaders","correct":"from typed_settings import default_loaders"}],"quickstart":{"code":"import attrs\nfrom pathlib import Path\nimport typed_settings as ts\nimport os\n\n@attrs.frozen\nclass DatabaseSettings:\n    host: str = \"localhost\"\n    port: int = 5432\n    user: str = \"admin\"\n    password: ts.SecretStr = ts.SecretStr(\"\")\n\n@ts.settings\nclass AppSettings:\n    debug: bool = False\n    log_level: str = \"INFO\"\n    database: DatabaseSettings = attrs.field(factory=DatabaseSettings)\n\n# Simulate a config file\nconfig_content = '''\n[myapp]\ndebug = true\nlog-level = \"DEBUG\"\n\n[myapp.database]\nhost = \"my.db.server\"\nport = 6000\n'''\nconfig_file_path = Path(\"settings.toml\")\nconfig_file_path.write_text(config_content)\n\n# Simulate environment variables\nos.environ['MYAPP_DATABASE_USER'] = os.environ.get('MYAPP_DATABASE_USER', 'env_user')\nos.environ['MYAPP_DATABASE_PASSWORD'] = os.environ.get('MYAPP_DATABASE_PASSWORD', 'env_secret')\n\ntry:\n    settings = ts.load(\n        cls=AppSettings,\n        appname=\"myapp\",\n        config_files=[config_file_path],\n        config_file_section=\"myapp\",\n    )\n    print(f\"Debug: {settings.debug}\")\n    print(f\"Log Level: {settings.log_level}\")\n    print(f\"DB Host: {settings.database.host}\")\n    print(f\"DB Port: {settings.database.port}\")\n    print(f\"DB User: {settings.database.user}\")\n    print(f\"DB Password (hidden): {settings.database.password}\")\nexcept Exception as e:\n    print(f\"Error loading settings: {e}\")\nfinally:\n    config_file_path.unlink(missing_ok=True)\n    del os.environ['MYAPP_DATABASE_USER']\n    del os.environ['MYAPP_DATABASE_PASSWORD']\n","lang":"python","description":"Define settings using `attrs` or `dataclasses` (or Pydantic with optional dependency) with type hints and default values. Use `ts.load` to automatically load and merge settings from config files and environment variables, with later sources overriding earlier ones. Environment variables follow an `APPNAME_OPTION_NAME` pattern by default. `SecretStr` can be used to prevent secrets from being printed in plaintext."},"warnings":[{"fix":"Review configuration files and environment variables to ensure dictionary settings are explicitly complete in the highest-precedence source, as partial definitions will be replaced entirely.","message":"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.","severity":"breaking","affected_versions":">=25.0.0"},{"fix":"Ensure all options in your config files correspond to attributes in your settings class, or remove extraneous options from the config.","message":"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.","severity":"breaking","affected_versions":"V2.0.0 onwards (specifically noted in 23.0.0 changelog as a breaking change)."},{"fix":"Explicitly provide default values for optional CLI options in your settings class, or be aware that their absence will require them on the command line.","message":"For CLI integration, Click options without a default value (or a loaded value from other sources) are now automatically marked as `required=True`.","severity":"breaking","affected_versions":"V2.0.0 onwards (specifically noted in 23.0.0 changelog as a breaking change)."},{"fix":"Adjust paths in your configuration files or environment variables if they were previously implicitly resolved differently. To disable this, manually create a converter with `resolve_paths=False` and pass it to `load_settings()` if fine-grained control is needed.","message":"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.","severity":"gotcha","affected_versions":">=23.1.0"},{"fix":"Instead of `MYAPP_API_TOKEN=\"secret\"`, store secrets in a file and pass the file path via an environment variable (e.g., `MYAPP_API_TOKEN_FILE=/private/token`), or use a dedicated secret vault loader. The library provides `SecretStr` to mask values in string representations.","message":"Passing secrets directly via environment variables is strongly discouraged due to security risks (e.g., accidental leaks in logs or CI/CD).","severity":"gotcha","affected_versions":"All versions"},{"fix":"Upgrade to Python 3.8 or newer.","message":"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.","severity":"deprecated","affected_versions":"<23.1.0 (dropped in 23.1.0)"},{"fix":"Review and update custom converter implementations and loaders according to the `typed-settings` documentation for version 23.1.0 and later.","message":"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.","severity":"breaking","affected_versions":">=23.1.0"}],"env_vars":null,"last_verified":"2026-04-10T00:00:00.000Z","next_check":"2026-07-09T00:00:00.000Z"}