diff --git a/pydantic_settings/sources/base.py b/pydantic_settings/sources/base.py index b2c4d166..4ead04aa 100644 --- a/pydantic_settings/sources/base.py +++ b/pydantic_settings/sources/base.py @@ -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 @@ -25,6 +26,7 @@ _annotation_is_complex, _get_alias_names, _get_model_fields, + _strip_annotated, _union_is_complex, ) @@ -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)) diff --git a/pydantic_settings/sources/utils.py b/pydantic_settings/sources/utils.py index 270a8c17..a8501568 100644 --- a/pydantic_settings/sources/utils.py +++ b/pydantic_settings/sources/utils.py @@ -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 diff --git a/tests/test_settings.py b/tests/test_settings.py index a7bd86c3..8e845ff1 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -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, @@ -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}')