Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions pydantic_settings/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
YamlConfigSettingsSource,
get_subcommand,
)
from .sources.utils import _get_alias_names

T = TypeVar('T')

Expand Down Expand Up @@ -444,13 +445,35 @@ def _settings_build_values(

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

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

@staticmethod
def _settings_restore_init_kwarg_names(
settings_cls: type[BaseSettings], init_kwargs: dict[str, Any], state: dict[str, Any]
) -> None:
"""
Restore the init_kwarg key names to the final merged state dictionary.
"""
if init_kwargs and state:
state_kwarg_names = set(state.keys())
init_kwarg_names = set(init_kwargs.keys())
for field_name, field_info in settings_cls.model_fields.items():
alias_names, *_ = _get_alias_names(field_name, field_info)
matchable_names = set(alias_names)
include_name = settings_cls.model_config.get('populate_by_name', False)
if include_name:
matchable_names.add(field_name)
init_kwarg_name = init_kwarg_names & matchable_names
state_kwarg_name = state_kwarg_names & matchable_names
if init_kwarg_name and state_kwarg_name:
state[init_kwarg_name.pop()] = state.pop(state_kwarg_name.pop())

@staticmethod
def _settings_warn_unused_config_keys(sources: tuple[object, ...], model_config: SettingsConfigDict) -> None:
"""
Expand Down
20 changes: 20 additions & 0 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
Tag,
ValidationError,
field_validator,
model_validator,
)
from pydantic import (
dataclasses as pydantic_dataclasses,
Expand Down Expand Up @@ -723,6 +724,25 @@ class Example(BaseSettings):
env.set('PREFIX_SURNAME', 'smith')
assert Example(name='john', PREFIX_SURNAME='doe').model_dump() == {'name': 'john', 'last_name': 'doe'}

class Settings(BaseSettings):
NAME: str = Field(
default='',
validation_alias=AliasChoices('NAME', 'OLD_NAME'),
)

@model_validator(mode='before')
def check_for_deprecated_attributes(cls, data: Any) -> Any:
if isinstance(data, dict):
old_keys = {k for k in data.keys() if k.startswith('OLD_')}
assert not old_keys
return data

s = Settings(NAME='foo')
s.model_dump() == {'NAME': 'foo'}

with pytest.raises(ValidationError, match="Assertion failed, assert not {'OLD_NAME'}"):
Settings(OLD_NAME='foo')


def test_init_kwargs_alias_resolution_deterministic():
class Example(BaseSettings):
Expand Down