diff --git a/pydantic/_internal/_fields.py b/pydantic/_internal/_fields.py index 6e5e933061..fd9ed4bf9e 100644 --- a/pydantic/_internal/_fields.py +++ b/pydantic/_internal/_fields.py @@ -226,6 +226,7 @@ def collect_model_fields( # noqa: C901 # Nothing stops us from just creating a new FieldInfo for this type hint, so we do this. field_info = FieldInfo.from_annotation(ann_type) else: + _warn_on_nested_alias_in_annotation(ann_type, ann_name) field_info = FieldInfo.from_annotated_attribute(ann_type, default) # attributes which are fields are removed from the class namespace: # 1. To match the behaviour of annotation-only fields @@ -251,6 +252,21 @@ def collect_model_fields( # noqa: C901 return fields, class_vars +def _warn_on_nested_alias_in_annotation(ann_type: type[Any], ann_name: str): + from ..fields import FieldInfo + + if hasattr(ann_type, '__args__'): + for anno_arg in ann_type.__args__: + if _typing_extra.is_annotated(anno_arg): + for anno_type_arg in _typing_extra.get_args(anno_arg): + if isinstance(anno_type_arg, FieldInfo) and anno_type_arg.alias is not None: + warnings.warn( + f'`alias` specification on field "{ann_name}" must be set on outermost annotation to take effect.', + UserWarning, + ) + break + + def _is_finalvar_with_default_val(type_: type[Any], val: Any) -> bool: from ..fields import FieldInfo diff --git a/tests/test_annotated.py b/tests/test_annotated.py index 6264454458..8d82372d34 100644 --- a/tests/test_annotated.py +++ b/tests/test_annotated.py @@ -1,5 +1,5 @@ import sys -from typing import Any, Generic, Iterator, List, Set, TypeVar +from typing import Any, Generic, Iterator, List, Optional, Set, TypeVar import pytest from annotated_types import BaseMetadata, GroupedMetadata, Gt, Lt, Predicate @@ -232,6 +232,18 @@ class _(BaseModel): calls.clear() +def test_annotated_alias_at_low_level() -> None: + with pytest.warns( + UserWarning, + match=r'`alias` specification on field "low_level_alias_field" must be set on outermost annotation to take effect.', + ): + + class Model(BaseModel): + low_level_alias_field: Optional[Annotated[int, Field(alias='field_alias')]] = None + + assert Model(field_alias=1).low_level_alias_field is None + + def test_get_pydantic_core_schema_source_type() -> None: types: Set[Any] = set()