diff --git a/pydantic_settings/sources.py b/pydantic_settings/sources.py index 28178f0..7af1f48 100644 --- a/pydantic_settings/sources.py +++ b/pydantic_settings/sources.py @@ -7,10 +7,10 @@ from collections import deque from dataclasses import is_dataclass from pathlib import Path -from typing import TYPE_CHECKING, Any, List, Mapping, Sequence, Tuple, Union, cast +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Sequence, Tuple, Union, cast from dotenv import dotenv_values -from pydantic import AliasChoices, AliasPath, BaseModel, Json +from pydantic import AliasChoices, AliasPath, BaseModel, Json, TypeAdapter from pydantic._internal._typing_extra import origin_is_union from pydantic._internal._utils import deep_update, lenient_issubclass from pydantic.fields import FieldInfo @@ -121,7 +121,7 @@ def get_field_value(self, field: FieldInfo, field_name: str) -> tuple[Any, str, return None, '', False def __call__(self) -> dict[str, Any]: - return self.init_kwargs + return TypeAdapter(Dict[str, Any]).dump_python(self.init_kwargs) def __repr__(self) -> str: return f'InitSettingsSource(init_kwargs={self.init_kwargs!r})' diff --git a/tests/test_settings.py b/tests/test_settings.py index 9a7fe69..36dcc7e 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -619,6 +619,43 @@ def settings_customise_sources( assert s.bar == 'env setting' +def test_env_deep_override(env): + class DeepSubModel(BaseModel): + v4: str + + class SubModel(BaseModel): + v1: str + v2: bytes + v3: int + deep: DeepSubModel + + class Settings(BaseSettings, env_nested_delimiter='__'): + v0: str + sub_model: SubModel + + @classmethod + def settings_customise_sources( + cls, settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings + ): + return env_settings, dotenv_settings, init_settings, file_secret_settings + + env.set('SUB_MODEL__DEEP__V4', 'override-v4') + + s_final = {'v0': '0', 'sub_model': {'v1': 'init-v1', 'v2': b'init-v2', 'v3': 3, 'deep': {'v4': 'override-v4'}}} + + s = Settings(v0='0', sub_model={'v1': 'init-v1', 'v2': b'init-v2', 'v3': 3, 'deep': {'v4': 'init-v4'}}) + assert s.model_dump() == s_final + + s = Settings(v0='0', sub_model=SubModel(v1='init-v1', v2=b'init-v2', v3=3, deep=DeepSubModel(v4='init-v4'))) + assert s.model_dump() == s_final + + s = Settings(v0='0', sub_model=SubModel(v1='init-v1', v2=b'init-v2', v3=3, deep={'v4': 'init-v4'})) + assert s.model_dump() == s_final + + s = Settings(v0='0', sub_model={'v1': 'init-v1', 'v2': b'init-v2', 'v3': 3, 'deep': DeepSubModel(v4='init-v4')}) + assert s.model_dump() == s_final + + def test_config_file_settings_nornir(env): """ See https://github.com/pydantic/pydantic/pull/341#issuecomment-450378771