pydantic-settings
repository·main·Indexed 23 days ago
https://github.com/pydantic/pydantic-settingsA library for settings management using Pydantic that allows application configuration to be defined as type-safe Pydantic models. It provides tools for loading and validating settings from environment variables, .env files, and command-line arguments via BaseSettings and SettingsConfigDict.
What's inside pydantic-settings
- pydantic-settings is a library for settings management using Pydantic. It allows you to define your application configuration as type-safe Pydantic models, enabling easy validation and loading of settings from various sources like environment variables or configuration files.
Customize settings source priority and composition
mainYou can change the order of priority or add/remove settings sources by overriding the
settings_customise_sourcesclass method in yourSettingsclass.settings_customise_sourcesreceives four arguments representing the built-in sources:init_settings,env_settings,dotenv_settings, andfile_secret_settings. It must return atupleof callables. The order of the returned tuple determines priority: the first item has the highest priority.Changing Priority
To make environment variables override
__init__arguments, returnenv_settingsbeforeinit_settingsin the tuple.Adding Sources
To add a custom source, implement a class inheriting from
PydanticBaseSettingsSourceand include it in the returned tuple fromsettings_customise_sources.from pydantic import PostgresDsn from pydantic_settings import BaseSettings, PydanticBaseSettingsSource class Settings(BaseSettings): database_dsn: PostgresDsn @classmethod def settings_customise_sources( cls, settings_cls: type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource, ) -> tuple[PydanticBaseSettingsSource, ...]: # env_settings now has higher priority than init_settings return env_settings, init_settings, file_secret_settingsUnderstand field value priority
mainWhen a setting is defined in multiple places,
pydantic-settingsresolves the value using the following priority (from highest to lowest):- CLI Arguments: If
cli_parse_argsis enabled. - Class Initializer: Arguments passed directly to
Settings(...). - Environment Variables: e.g.,
MY_PREFIX_FIELD. - Dotenv Files: Variables loaded from a
.envfile. - Secrets Directory: Variables loaded from the configured secrets directory.
- Default Values: The default values defined in the
BaseSettingsmodel fields.
- CLI Arguments: If
Use Literals and Enums as CLI choices
mainWhen a field is typed as an
Enumor aLiteral, the CLI parser automatically converts these into valid CLI choices, restricting the user to the defined values.import sys from enum import IntEnum from typing import Literal from pydantic_settings import BaseSettings class Fruit(IntEnum): pear = 0 kiwi = 1 lime = 2 class Settings(BaseSettings, cli_parse_args=True): fruit: Fruit pet: Literal['dog', 'cat', 'bird'] sys.argv = ['example.py', '--fruit', 'lime', '--pet', 'cat'] print(Settings().model_dump()) # {'fruit': <Fruit.lime: 2>, 'pet': 'cat'}Configure Dotenv filtering and extra field behavior
mainBy default, if
extra='forbid'is set inmodel_config, any entry in your.envfile that does not correspond to a defined field in yourBaseSettingsmodel will raise aValidationError.You can control this behavior using the
dotenv_filteringsetting inSettingsConfigDict:'match_prefix': Only variables matching theenv_prefixwill be passed to the model. This is useful for scoping a single.envfile to a specific model.'only_existing': Only variables that have a corresponding field in the model will be passed. This makes the dotenv source behave like standard environment variables.
Note on
env_prefixasymmetry: If you have anenv_prefixset (e.g.,app_), an unprefixed entry in the.envfile that matches a field name (e.g.,debug=truefor a fielddebug) is silently ignored rather than being treated as an extra field. However, a completely unrelated unprefixed entry (e.g.,foo=bar) will still raise aValidationErrorifextra='forbid'is active.from pydantic_settings import BaseSettings, SettingsConfigDict # Example of using dotenv_filtering to avoid ValidationErrors from extra keys class Settings(BaseSettings): model_config = SettingsConfigDict( env_file='.env', dotenv_filtering='only_existing' )Limit nesting depth with env_nested_max_split
mainWhen using
env_nested_delimiter,pydantic-settingssplits variables into arbitrarily deep fields by default. To prevent accidental deep nesting (e.g., when a delimiter like_is part of a field name), useenv_nested_max_splitto limit the depth.For example, if you have a field
api_keyand use_as a delimiter,API_KEYmight be interpreted asapi.key. Settingenv_nested_max_split=1ensures it is treated as a single field.class GenerationConfig(BaseSettings): model_config = SettingsConfigDict( env_nested_delimiter='_', env_nested_max_split=1, env_prefix='GENERATION_' ) llm: LLMConfig # With export GENERATION_LLM_API_KEY='key', # max_split=1 ensures it maps to llm.api_key instead of llm.api.keyCreate mutually exclusive CLI argument groups
mainTo ensure that only one of a set of arguments is provided, inherit from
CliMutuallyExclusiveGroup.Constraints:
- A
CliMutuallyExclusiveGroupcannot be used within aUniontype. - A
CliMutuallyExclusiveGroupcannot contain nested models.
from typing import Optional from pydantic import BaseModel from pydantic_settings import CliApp, CliMutuallyExclusiveGroup, SettingsError class Circle(CliMutuallyExclusiveGroup): radius: Optional[float] = None diameter: Optional[float] = None perimeter: Optional[float] = None class Settings(BaseModel): circle: Circle try: CliApp.run( Settings, cli_args=['--circle.radius=1', '--circle.diameter=2'], cli_exit_on_error=False, ) except SettingsError as e: print(e) # error parsing CLI: argument --circle.diameter: not allowed with argument --circle.radius- A
How BaseSettings works
mainBy inheriting fromBaseSettings, a model's initializer will automatically attempt to populate fields from environment variables if they are not provided as keyword arguments. If a matching environment variable is not found, the field's default value is used. This allows for type-hinted configuration classes that can be easily overridden via the environment or manually during instantiation (e.g., for testing).Disable JSON parsing for environment variables
mainIf you want to avoid the requirement for valid JSON in environment variables, you have three options:
- Per-field: Annotate a field with
NoDecode. This disables JSON decoding for that specific field, allowing you to use afield_validator(withmode='before') to parse the raw string (e.g., comma-separated values). - Global: Set
enable_decoding=FalseinSettingsConfigDictto disable JSON parsing for all fields. - Force JSON: If global decoding is disabled, you can force JSON parsing for a specific field using the
ForceDecodeannotation.
from typing import Annotated from pydantic import field_validator from pydantic_settings import BaseSettings, NoDecode class Settings(BaseSettings): # Disables JSON parsing; use validator to handle '1,2,3' numbers: Annotated[list[int], NoDecode] @field_validator('numbers', mode='before') @classmethod def decode_numbers(cls, v: str) -> list[int]: return [int(x) for x in v.split(',')]- Per-field: Annotate a field with
Use file-based settings sources (JSON, TOML, YAML)
mainYou can load configuration from files using specialized settings sources. The available sources are:
JsonConfigSettingsSource: Requiresjson_fileandjson_file_encoding.PyprojectTomlConfigSettingsSource: Requirespyproject_toml_depth(optional) andpyproject_toml_table_header(optional).TomlConfigSettingsSource: Requirestoml_fileandtoml_table_header(optional).YamlConfigSettingsSource: Requiresyaml_fileandyaml_file_encoding.
To use these, override the
settings_customise_sourcesclass method in yourBaseSettingssubclass and return the desired source(s).from pydantic_settings import ( BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, TomlConfigSettingsSource, ) class Settings(BaseSettings): foobar: str model_config = SettingsConfigDict(toml_file='config.toml') @classmethod def settings_customise_sources( cls, settings_cls: type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource ) -> tuple[PydanticBaseSettingsSource, ...]: return (TomlConfigSettingsSource(settings_cls),)Resolve GCP Project ID from other settings sources
mainYou can avoid hardcoding the
project_idby allowing it to be resolved from a previously loaded settings source (like an environment variable).Use the
project_id_fieldargument inGoogleSecretManagerSettingsSourceto specify which field (or alias) contains the project ID. The value must match the key used by the earlier source in itscurrent_state(typically the field's preferred alias).Parse lists and dictionaries via CLI
mainPydantic settings supports multiple styles for parsing complex types from the CLI:
Lists support:
- JSON style:
--field='[1,2]' - Argparse style:
--field 1 --field 2 - Lazy style:
--field=1,2
Dictionaries support:
- JSON style:
--field='{"k1": 1, "k2": 2}' - Environment variable style:
--field k1=1 --field k2=2
You can mix these styles (e.g., using lazy list style and JSON dictionary style in the same command).
import sys from pydantic_settings import BaseSettings class Settings(BaseSettings, cli_parse_args=True): my_list: list[int] my_dict: dict[str, int] # List examples sys.argv = ['example.py', '--my_list', '[1,2]'] sys.argv = ['example.py', '--my_list', '1', '--my_list', '2'] sys.argv = ['example.py', '--my_list', '1,2'] # Dictionary examples sys.argv = ['example.py', '--my_dict', '{"k1":1,"k2":2}'] sys.argv = ['example.py', '--my_dict', 'k1=1', '--my_dict', 'k2=2']- JSON style: