Skip to content

Commit 5008c69

Browse files
authored
Restore init kwarg names before returning final state dictionary. (#700)
1 parent 4433101 commit 5008c69

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

pydantic_settings/main.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
YamlConfigSettingsSource,
3636
get_subcommand,
3737
)
38+
from .sources.utils import _get_alias_names
3839

3940
T = TypeVar('T')
4041

@@ -444,13 +445,35 @@ def _settings_build_values(
444445

445446
# Strip any default values not explicity set before returning final state
446447
state = {key: val for key, val in state.items() if key not in defaults or defaults[key] != val}
448+
self._settings_restore_init_kwarg_names(self.__class__, init_kwargs, state)
447449

448450
return state
449451
else:
450452
# no one should mean to do this, but I think returning an empty dict is marginally preferable
451453
# to an informative error and much better than a confusing error
452454
return {}
453455

456+
@staticmethod
457+
def _settings_restore_init_kwarg_names(
458+
settings_cls: type[BaseSettings], init_kwargs: dict[str, Any], state: dict[str, Any]
459+
) -> None:
460+
"""
461+
Restore the init_kwarg key names to the final merged state dictionary.
462+
"""
463+
if init_kwargs and state:
464+
state_kwarg_names = set(state.keys())
465+
init_kwarg_names = set(init_kwargs.keys())
466+
for field_name, field_info in settings_cls.model_fields.items():
467+
alias_names, *_ = _get_alias_names(field_name, field_info)
468+
matchable_names = set(alias_names)
469+
include_name = settings_cls.model_config.get('populate_by_name', False)
470+
if include_name:
471+
matchable_names.add(field_name)
472+
init_kwarg_name = init_kwarg_names & matchable_names
473+
state_kwarg_name = state_kwarg_names & matchable_names
474+
if init_kwarg_name and state_kwarg_name:
475+
state[init_kwarg_name.pop()] = state.pop(state_kwarg_name.pop())
476+
454477
@staticmethod
455478
def _settings_warn_unused_config_keys(sources: tuple[object, ...], model_config: SettingsConfigDict) -> None:
456479
"""

tests/test_settings.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
Tag,
3030
ValidationError,
3131
field_validator,
32+
model_validator,
3233
)
3334
from pydantic import (
3435
dataclasses as pydantic_dataclasses,
@@ -723,6 +724,25 @@ class Example(BaseSettings):
723724
env.set('PREFIX_SURNAME', 'smith')
724725
assert Example(name='john', PREFIX_SURNAME='doe').model_dump() == {'name': 'john', 'last_name': 'doe'}
725726

727+
class Settings(BaseSettings):
728+
NAME: str = Field(
729+
default='',
730+
validation_alias=AliasChoices('NAME', 'OLD_NAME'),
731+
)
732+
733+
@model_validator(mode='before')
734+
def check_for_deprecated_attributes(cls, data: Any) -> Any:
735+
if isinstance(data, dict):
736+
old_keys = {k for k in data.keys() if k.startswith('OLD_')}
737+
assert not old_keys
738+
return data
739+
740+
s = Settings(NAME='foo')
741+
s.model_dump() == {'NAME': 'foo'}
742+
743+
with pytest.raises(ValidationError, match="Assertion failed, assert not {'OLD_NAME'}"):
744+
Settings(OLD_NAME='foo')
745+
726746

727747
def test_init_kwargs_alias_resolution_deterministic():
728748
class Example(BaseSettings):

0 commit comments

Comments
 (0)