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
7 changes: 6 additions & 1 deletion pydantic_settings/sources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from pydantic._internal._utils import is_model_class
from pydantic.fields import FieldInfo
from typing_extensions import get_args
from typing_inspection import typing_objects
from typing_inspection.introspection import is_union_origin

from ..exceptions import SettingsError
Expand All @@ -25,6 +26,7 @@
_annotation_is_complex,
_get_alias_names,
_get_model_fields,
_strip_annotated,
_union_is_complex,
)

Expand Down Expand Up @@ -353,7 +355,10 @@ def _extract_field_info(self, field: FieldInfo, field_name: str) -> list[tuple[s
field_info.append((v_alias, self._apply_case_sensitive(v_alias), False))

if not v_alias or self.config.get('populate_by_name', False):
if is_union_origin(get_origin(field.annotation)) and _union_is_complex(field.annotation, field.metadata):
annotation = field.annotation
if typing_objects.is_typealiastype(annotation) or typing_objects.is_typealiastype(get_origin(annotation)):
annotation = _strip_annotated(annotation.__value__) # type: ignore[union-attr]
if is_union_origin(get_origin(annotation)) and _union_is_complex(annotation, field.metadata):
field_info.append((field_name, self._apply_case_sensitive(self.env_prefix + field_name), True))
else:
field_info.append((field_name, self._apply_case_sensitive(self.env_prefix + field_name), False))
Expand Down
4 changes: 3 additions & 1 deletion pydantic_settings/sources/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ def parse_env_vars(
}


def _annotation_is_complex(annotation: type[Any] | None, metadata: list[Any]) -> bool:
def _annotation_is_complex(annotation: Any, metadata: list[Any]) -> bool:
# If the model is a root model, the root annotation should be used to
# evaluate the complexity.
if typing_objects.is_typealiastype(annotation) or typing_objects.is_typealiastype(get_origin(annotation)):
annotation = annotation.__value__
if annotation is not None and _lenient_issubclass(annotation, RootModel) and annotation is not RootModel:
annotation = cast('type[RootModel[Any]]', annotation)
root_annotation = annotation.model_fields['root'].annotation
Expand Down
26 changes: 25 additions & 1 deletion tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
dataclasses as pydantic_dataclasses,
)
from pydantic.fields import FieldInfo
from typing_extensions import override
from typing_extensions import TypeAliasType, override

from pydantic_settings import (
BaseSettings,
Expand Down Expand Up @@ -474,6 +474,30 @@ class AnnotatedComplexSettings(BaseSettings):
]


def test_annotated_with_type(env):
"""https://github.com/pydantic/pydantic-settings/issues/536.

PEP 695 type aliases need to be analyzed when determining if an annotation is complex.
"""
MinLenList = TypeAliasType('MinLenList', Annotated[Union[list[str], list[int]], MinLen(2)])

class AnnotatedComplexSettings(BaseSettings):
apples: MinLenList

env.set('apples', '["russet", "granny smith"]')
s = AnnotatedComplexSettings()
assert s.apples == ['russet', 'granny smith']

T = TypeVar('T')
MinLenList = TypeAliasType('MinLenList', Annotated[Union[list[T], tuple[T]], MinLen(2)], type_params=(T,))

class AnnotatedComplexSettings(BaseSettings):
apples: MinLenList[str]

s = AnnotatedComplexSettings()
assert s.apples == ['russet', 'granny smith']


def test_set_dict_model(env):
env.set('bananas', '[1, 2, 3, 3]')
env.set('CARROTS', '{"a": null, "b": 4}')
Expand Down