Highlights
Breaking changes and behavior changes
- Python 3.10 is no longer supported (#1003). pydantic-settings now requires Python 3.11+, since 3.10 reaches end of life this month. The
tomlifallback is gone in favor of stdlibtomllib; thetomlextra is kept as an empty extra sopip install pydantic-settings[toml]keeps working. python-dotenv>=1.2.2is now required (#971) to pick up the fix for CVE-2026-28684.- Aliases are ignored in every source when
validate_by_alias=False(#1033). Env vars, secrets, CLI flags and defaults now use only field names, matching pydantic. An env var named after the alias is now ignored instead of raisingextra_forbidden, and the CLI exposes--applerather than--pomo. validation_aliastakes precedence overalias(#1038). When the two differ, values from JSON/YAML/TOML files and the CLI were silently ignored; they now load. CLI help lists thevalidation_aliasflag first, andCliApp.serializeemits it.- CLI case-insensitivity only lowercases registered options (#1036). With
case_sensitive=False, args after--and those captured byCliUnknownArgskeep their case (-Xmx4Gno longer becomes-xmx4g). Unrecognized options are now reported as typed (--NOPEinstead of--nope). - JSON and YAML config files are read as UTF-8 by default (#955) instead of the locale encoding, completing the secrets-file fix from 2.15.0.
json_file_encoding/yaml_file_encodingstill override it. - Discriminated union defaults are partially updated again (#997). This reverts the 2.15.0 regression with
nested_model_default_partial_update=True: the default is only dropped when the incoming value selects a different union member.
New features
- Python 3.15 and pydantic 2.14 support (#1026, #1029, #1022). pydantic 2.14 broke
CliApp.runandvalidate_by_alias=Falsehandling; both are fixed. - Cached settings instances via
Settings.settings_cached()andSettings.settings_clear_cache()(#952). These give you a process-local singleton per settings class without hand-rollinglru_cachewrappers. Calling the class directly still builds a fresh instance. - AWS Systems Manager Parameter Store source —
AWSSystemsManagerSettingsSource(#941). It recursively reads every parameter under anssm_path, with/mapping onto nested models. Install it with the newaws-systems-managerextra. env_file_depth(#940) lets a relativeenv_filebe found in parent directories, mirroringpyproject_toml_depth. This is useful when scripts run from a subdirectory of a monorepo. It defaults to0, which keeps the current behavior.CliVariadicArgfor named variadic options (#949, #958). It accepts--param a b cin addition to repeated--param a --param b. Required fields need at least one value.
Bug fixes
CLI
CliApp.serializeoutput now round-trips throughCliApp.runfor enums, tuples, dates andDecimals (#988), explicitNonevalues (#981), empty containers (#1008),CliVariadicArgvalues (#976) and populatedCliUnknownArgsfields, which previously raisedIndexError(#992).- Empty JSON arrays (
--items []) now give an empty list instead of[''](#994). - Tuples,
frozenset,MutableSequenceand other collection subtypes are now treated as repeatable/variadic options (#972), and so are named collection type aliases such asTypeAliasType('Addrs', list[IPvAnyAddress])(#1032). cli_exit_on_error=Falseis now honored inCliSubCommandparsers, which raiseSettingsErrorinstead of exiting (#977).CliSubCommandaliases keep their case withcase_sensitive=False(#989).- Each
CliUnknownArgsfield now receives only its own subcommand's leftover args (#1004). CliApp.format_helpno longer crashes whencli_prefixis set inmodel_config(#945).
Type aliases and parsing
- Named union type aliases (
TypeAliasType('V', list[int] | str)) now decode like their inline equivalents in env and dotenv sources, including underOptional,Annotatedand custom validation aliases (#1006, #1030, #1041). - Resolving a named
Annotatedtype alias no longer mutates sharedFieldInfometadata, which made validators run twice on subclasses or aftermodel_rebuild()(#979). - A bare single-segment
AliasPath('SOME_KEY')no longer tries to JSON-decode plain string values (#980). collections.abc.Set/MutableSet/typing.AbstractSetfields now JSON-decode env values (#973).- A bare
typing.Dictfield withenv_nested_delimiterno longer raisesIndexError, which was a regression in 2.13.0 (#1009). - Case-insensitive matching now works for nested pydantic dataclasses (#1010).
Dotenv, secrets and config files
- With
extra='allow', a non-lowercaseenv_prefixis now stripped from dotenv extras (#959, #968). secrets_case_sensitive=Trueis now honored on Windows (#1001).- An empty JSON config file is treated as an empty mapping instead of crashing (#947), matching YAML and TOML.
- TOML table headers:
~paths are now expanded when checking that the file exists (#1034), and an explicittoml_table_header=()now overrides the model config to read the file root (#1027).
Cloud sources
- Azure Key Vault and GCP Secret Manager now honor
env_parse_none_str(#1019, #1040). - Azure Key Vault:
dash_to_underscore=Truenow works for nested model fields (#1025). NestedSecretsSettingsSourcenow validates its specific constructor arguments (#975).DotenvFilteringis exported frompydantic_settings.sources.types(#944).
Documentation
- Document that runtime validation arguments (
strict,extra,context) passed tomodel_validate()are not applied toBaseSettings, and give a workaround (#1017). - Explain why
extra='forbid'doesn't reject misspelled top-level env vars (#986). - Clarify alias plus nested model env lookup with
populate_by_name(#933), and add a nested model example forenv_prefix_target(#953). - Fix assorted typos (#942, #964).
All changes (including dependency bumps and internal maintenance)
What's Changed
- Bump boto3 from 1.43.60 to 1.43.66 in the python-packages group by @dependabot[bot] in #936
- docs: clarify alias + nested model env lookup with populate_by_name by @pranjalm37 in #933
- lint: enable flake8-bugbear and fix the issues it found by @hramezani in #931
- Bump the python-packages group with 6 updates by @dependabot[bot] in #943
- docs: improve ENV_FILE_SENTINEL comment phrasing in sources/types.py by @aoright in #942
- fix(types): include DotenvFiltering in all in sources.types by @aoright in #944
- Add env_file_depth for parent directory env file lookup by @dmi03 in #940
- Add an AWS Systems Manager Parameter Store settings source by @ChrisJr404 in #941
- fix: use self.cli_prefix instead of cli_prefix in CliSettingsSource constructor by @RomainL972 in #945
- Bump the python-packages group with 6 updates by @dependabot[bot] in #948
- fix: treat empty JSON config file as empty mapping instead of crashing by @sneha4175 in #947
- Expand ruff rule selection by @hramezani in #950
- Enable ruff's flake8-return (RET) rules by @hramezani in #951
- docs: add nested model alias example for env_prefix_target by @MohammedAlkindi in #953
- Add cached settings instances by @tiangolo in #952
- Bump the github-actions group with 3 updates by @dependabot[bot] in #954
- Bump the python-packages group with 4 updates by @dependabot[bot] in #957
- Add CliVariadicArg for named variadic CLI options by @gyanu2507 in #949
- Fix CliVariadicArg empty-value handling for named options by @hramezani in #958
- fix: read JSON and YAML config as UTF-8, not the locale encoding by @MohammedAlkindi in #955
- Reach 100% test coverage by @hramezani in #963
- docs: fix three small wording/typo errors by @simpleqt in #964
- fix: strip a case-normalized env_prefix from dotenv extras by @MohammedAlkindi in #959
- fix: slice the case-normalized prefix in dotenv match_prefix by @hramezani in #968
- Bump the python-packages group with 3 updates by @dependabot[bot] in #966
- fix: address CVE-2026-28684 in python-dotenv dependency by @jmartens in #971
- build(deps): bump the python-packages group with 3 updates by @dependabot[bot] in #978
- Verify
NestedSecretsSettingsSourcespecific arguments by @makukha in #975 - Fix abstract Set annotations not being treated as complex by @mmdverse in #973
- Fix CLI append-action detection for collection subtypes by @mmdverse in #972
- Honor cli_exit_on_error=False in CliSubCommand parsers by @glaziermag in #977
- Preserve CliVariadicArg values in CliApp.serialize argparse/env styles by @glaziermag in #976
- Avoid mutating field metadata when resolving type aliases by @jakezwang in #979
- fix: bare AliasPath complexity keyed off name length, not path length by @ishan-1010 in #980
- docs: clarify extra validation for environment variables by @hulkbig in #986
- build(deps): bump the python-packages group with 2 updates by @dependabot[bot] in #990
- Preserve explicit None values in CLI serialization by @mtcd001-tech in #981
- Make
CliApp.serializeoutput parseable for enum and non-JSON values by @SulimanAbdulrazzaq in #988 - Preserve CliSubCommand alias case with case_sensitive=False by @glaziermag in #989
- Fix Path.is_mount type-checking error on win32 platform by @truecallerabreham in #982
- build(deps): bump astral-sh/setup-uv from 10.0.1 to 10.2.0 in the github-actions group by @dependabot[bot] in #998
- build(deps): bump the python-packages group with 3 updates by @dependabot[bot] in #999
- Fix
CliApp.serializefor models withCliUnknownArgsfields by @truecallerabreham in #992 - Honor
secrets_case_sensitiveon Windows by overriding_load_env_varsby @hramezani in #1001 - Fix empty JSON arrays in CLI list arguments by @hansu650 in #994
- Bump
samuelcolvin/check-python-versionaction to v5.1 by @Viicos in #1014 - build(deps): bump the python-packages group with 7 updates by @dependabot[bot] in #1015
- Only skip partial updates for discriminated unions selecting another type by @SammyTourani in #997
- fix: handle bare typing.Dict in EnvSettingsSource.next_field (#1007) by @DawnofGenX in #1009
- Apply case-insensitive field matching to nested pydantic dataclasses by @sungbin1015 in #1010
- Document that runtime validation arguments are not applied to BaseSettings by @hramezani in #1017
- Fix named union alias parsing in environment sources by @hulkbig in #1006
- Drop support for Python 3.10 by @hramezani in #1003
- Add support for Python 3.15 by @hramezani in #1026
- Give each CliUnknownArgs field only its own parser leftovers by @fhgffy in #1004
- Preserve empty containers in CLI serialization by @fallintoplace in #1008
- Honor env_parse_none_str for Azure Key Vault secrets by @fhgffy in #1019
- Fix nested Azure Key Vault dash translation by @goudan789 in #1025
- Support pydantic 2.14 by @hramezani in #1029
- Read field names when
validate_by_alias=Falseon pydantic 2.14 by @maharanay22 in #1022 - Ignore aliases in every source when
validate_by_alias=Falseby @hramezani in #1033 - Honor explicit empty TOML table header overrides by @xiehuanyi in #1027
- Fix CLI parsing for named collection type aliases by @JackyHsu-X in #1032
- Fix JSON decoding for optional named union aliases by @hulkbig in #1030
- Decode optional Annotated named union aliases by @hramezani in #1041
- Expand home paths when checking TOML table headers by @JunaidAhamed-7777 in #1034
- Fix config file and CLI values being ignored when alias differs from validation_alias by @hzmsh in #1038
- Honor env_parse_none_str for GCP Secret Manager by @Ganesh1729-ui in #1040
- Simplify alias resolution branch and test alias with AliasChoices/AliasPath by @hramezani in #1042
- Keep case of non-option CLI args when case_sensitive=False by @hzmsh in #1036
- Prepare release 2.16.0 by @hramezani in #1043
New Contributors
- @pranjalm37 made their first contribution in #933
- @aoright made their first contribution in #942
- @dmi03 made their first contribution in #940
- @ChrisJr404 made their first contribution in #941
- @RomainL972 made their first contribution in #945
- @sneha4175 made their first contribution in #947
- @MohammedAlkindi made their first contribution in #953
- @tiangolo made their first contribution in #952
- @gyanu2507 made their first contribution in #949
- @simpleqt made their first contribution in #964
- @jmartens made their first contribution in #971
- @mmdverse made their first contribution in #973
- @glaziermag made their first contribution in #977
- @jakezwang made their first contribution in #979
- @ishan-1010 made their first contribution in #980
- @hulkbig made their first contribution in #986
- @mtcd001-tech made their first contribution in #981
- @SulimanAbdulrazzaq made their first contribution in #988
- @truecallerabreham made their first contribution in #982
- @hansu650 made their first contribution in #994
- @SammyTourani made their first contribution in #997
- @DawnofGenX made their first contribution in #1009
- @sungbin1015 made their first contribution in #1010
- @fhgffy made their first contribution in #1004
- @fallintoplace made their first contribution in #1008
- @goudan789 made their first contribution in #1025
- @maharanay22 made their first contribution in #1022
- @xiehuanyi made their first contribution in #1027
- @JackyHsu-X made their first contribution in #1032
- @JunaidAhamed-7777 made their first contribution in #1034
- @hzmsh made their first contribution in #1038
- @Ganesh1729-ui made their first contribution in #1040
Full Changelog: v2.15.0...v2.16.0