Skip to content

Commit 5933ea6

Browse files
authored
Check union args to don't consider Optional fields as complex Union (#138)
1 parent e6417b4 commit 5933ea6

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

pydantic_settings/sources.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from pydantic._internal._typing_extra import origin_is_union
1414
from pydantic._internal._utils import deep_update, lenient_issubclass
1515
from pydantic.fields import FieldInfo
16-
from typing_extensions import get_origin
16+
from typing_extensions import get_args, get_origin
1717

1818
from pydantic_settings.utils import path_type_label
1919

@@ -441,13 +441,16 @@ def prepare_field_value(self, field_name: str, field: FieldInfo, value: Any, val
441441
# simplest case, field is not complex, we only need to add the value if it was found
442442
return value
443443

444+
def _union_is_complex(self, annotation: type[Any] | None, metadata: list[Any]) -> bool:
445+
return any(_annotation_is_complex(arg, metadata) for arg in get_args(annotation))
446+
444447
def _field_is_complex(self, field: FieldInfo) -> tuple[bool, bool]:
445448
"""
446449
Find out if a field is complex, and if so whether JSON errors should be ignored
447450
"""
448451
if self.field_is_complex(field):
449452
allow_parse_failure = False
450-
elif origin_is_union(get_origin(field.annotation)):
453+
elif origin_is_union(get_origin(field.annotation)) and self._union_is_complex(field.annotation, field.metadata):
451454
allow_parse_failure = True
452455
else:
453456
return False, False

tests/test_settings.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,3 +1791,27 @@ class Settings(BaseSettings):
17911791
env.set('z', '[{"x": 1, "y": {"foo": 1}}, {"x": 2, "y": {"foo": 2}}]')
17921792
s = Settings()
17931793
assert s.model_dump() == {'z': [{'x': 1, 'y': {'foo': 1}}, {'x': 2, 'y': {'foo': 2}}]}
1794+
1795+
1796+
def test_optional_field_from_env(env):
1797+
class Settings(BaseSettings):
1798+
x: Optional[str] = None
1799+
1800+
env.set('x', '123')
1801+
1802+
s = Settings()
1803+
assert s.x == '123'
1804+
1805+
1806+
@pytest.mark.skipif(not dotenv, reason='python-dotenv not installed')
1807+
def test_dotenv_optional_json_field(tmp_path):
1808+
p = tmp_path / '.env'
1809+
p.write_text("""DATA='{"foo":"bar"}'""")
1810+
1811+
class Settings(BaseSettings):
1812+
model_config = SettingsConfigDict(env_file=p)
1813+
1814+
data: Optional[Json[Dict[str, str]]] = Field(default=None)
1815+
1816+
s = Settings()
1817+
assert s.data == {'foo': 'bar'}

0 commit comments

Comments
 (0)