Skip to content

Commit 58b236a

Browse files
hramezaniclaude
andauthored
Fix AttributeError with nested env vars for dict fields (#785) (#786)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4933f06 commit 58b236a

2 files changed

Lines changed: 52 additions & 3 deletions

File tree

pydantic_settings/sources/providers/env.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from ..utils import (
2222
_annotation_contains_types,
2323
_annotation_enum_name_to_val,
24+
_annotation_is_complex,
2425
_get_model_fields,
2526
_union_is_complex,
2627
parse_env_vars,
@@ -209,7 +210,7 @@ class Cfg(BaseSettings):
209210
return f
210211
return None
211212

212-
def explode_env_vars(self, field_name: str, field: FieldInfo, env_vars: Mapping[str, str | None]) -> dict[str, Any]:
213+
def explode_env_vars(self, field_name: str, field: FieldInfo, env_vars: Mapping[str, str | None]) -> dict[str, Any]: # noqa: C901
213214
"""
214215
Process env_vars and extract the values of keys containing env_nested_delimiter into nested dictionaries.
215216
@@ -253,17 +254,22 @@ def explode_env_vars(self, field_name: str, field: FieldInfo, env_vars: Mapping[
253254

254255
# check if env_val maps to a complex field and if so, parse the env_val
255256
if (target_field or is_dict) and env_val:
256-
if target_field:
257+
if isinstance(target_field, FieldInfo):
257258
is_complex, allow_json_failure = self._field_is_complex(target_field)
258259
if self.env_parse_enums:
259260
enum_val = _annotation_enum_name_to_val(target_field.annotation, env_val)
260261
env_val = env_val if enum_val is None else enum_val
262+
elif target_field:
263+
# target_field is a raw type (e.g. from dict value type annotation)
264+
is_complex = _annotation_is_complex(target_field, [])
265+
allow_json_failure = True
261266
else:
262267
# nested field type is dict
263268
is_complex, allow_json_failure = True, True
264269
if is_complex:
265270
try:
266-
env_val = self.decode_complex_value(last_key, target_field, env_val) # type: ignore
271+
field_info = target_field if isinstance(target_field, FieldInfo) else None
272+
env_val = self.decode_complex_value(last_key, field_info, env_val) # type: ignore
267273
except ValueError as e:
268274
if not allow_json_failure:
269275
raise e

tests/test_settings.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2855,6 +2855,49 @@ class Settings(BaseSettings):
28552855
assert s.model_dump() == {'nested': {'foo': {'a': {'b': 'bar'}}}}
28562856

28572857

2858+
def test_env_nested_dict_simple_value_types(env):
2859+
"""Test that nested env vars work for dict fields with simple value types (e.g. dict[str, str]).
2860+
2861+
Regression test for https://github.com/pydantic/pydantic-settings/issues/785
2862+
"""
2863+
2864+
class Settings(BaseSettings):
2865+
nested_field: dict[str, str]
2866+
2867+
model_config = SettingsConfigDict(env_nested_delimiter='__')
2868+
2869+
env.set('nested_field__key', 'some value')
2870+
s = Settings()
2871+
assert s.model_dump() == {'nested_field': {'key': 'some value'}}
2872+
2873+
2874+
def test_env_nested_dict_multiple_simple_keys(env):
2875+
"""Test multiple nested env vars for dict[str, str]."""
2876+
2877+
class Settings(BaseSettings):
2878+
nested_field: dict[str, str]
2879+
2880+
model_config = SettingsConfigDict(env_nested_delimiter='__')
2881+
2882+
env.set('nested_field__key1', 'value1')
2883+
env.set('nested_field__key2', 'value2')
2884+
s = Settings()
2885+
assert s.model_dump() == {'nested_field': {'key1': 'value1', 'key2': 'value2'}}
2886+
2887+
2888+
def test_env_nested_dict_complex_value_type(env):
2889+
"""Test nested env vars for dict with complex value types like list[str]."""
2890+
2891+
class Settings(BaseSettings):
2892+
nested_field: dict[str, list[str]]
2893+
2894+
model_config = SettingsConfigDict(env_nested_delimiter='__')
2895+
2896+
env.set('nested_field__key', '["a", "b"]')
2897+
s = Settings()
2898+
assert s.model_dump() == {'nested_field': {'key': ['a', 'b']}}
2899+
2900+
28582901
def test_nested_models_leaf_vs_deeper_env_dict_assumed(env):
28592902
class NestedSettings(BaseModel):
28602903
foo: str

0 commit comments

Comments
 (0)