Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Properly rebuild the FieldInfo when a forward ref gets evaluated #7698

Merged
merged 2 commits into from Sep 29, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion pydantic/_internal/_generate_schema.py
Expand Up @@ -897,7 +897,9 @@ def _generate_dc_field_schema(
metadata=common_field['metadata'],
)

def _common_field_schema(self, name: str, field_info: FieldInfo, decorators: DecoratorInfos) -> _CommonField:
def _common_field_schema( # noqa C901
self, name: str, field_info: FieldInfo, decorators: DecoratorInfos
) -> _CommonField:
# Update FieldInfo annotation if appropriate:
if has_instance_in_type(field_info.annotation, (ForwardRef, str)):
types_namespace = self._types_namespace
Expand All @@ -910,6 +912,14 @@ def _common_field_schema(self, name: str, field_info: FieldInfo, decorators: Dec
if evaluated is not field_info.annotation and not has_instance_in_type(evaluated, PydanticRecursiveRef):
field_info.annotation = evaluated

# Handle any field info attributes that may have been obtained from now-resolved annotations
new_field_info = FieldInfo.from_annotation(evaluated)
for k, v in new_field_info._attributes_set.items():
# If an attribute is already set, it means it was set by assigning to a call to Field (or just a
# default value), and that should take the highest priority. So don't overwrite existing attributes.
if k not in field_info._attributes_set:
setattr(field_info, k, v)

source_type, annotations = field_info.annotation, field_info.metadata

def set_discriminator(schema: CoreSchema) -> CoreSchema:
Expand Down
27 changes: 27 additions & 0 deletions tests/test_annotated.py
Expand Up @@ -373,3 +373,30 @@ def test_predicate_error_python() -> None:
'input': -1,
}
]


def test_annotated_field_info_not_lost_from_forwardref():
from pydantic import BaseModel

class ForwardRefAnnotatedFieldModel(BaseModel):
foo: 'Annotated[Integer, Field(alias="bar", default=1)]' = 2
foo2: 'Annotated[Integer, Field(alias="bar2", default=1)]' = Field(default=2, alias='baz')

Integer = int

ForwardRefAnnotatedFieldModel.model_rebuild()

assert ForwardRefAnnotatedFieldModel(bar=3).foo == 3
assert ForwardRefAnnotatedFieldModel(baz=3).foo2 == 3

with pytest.raises(ValidationError) as exc_info:
ForwardRefAnnotatedFieldModel(bar='bar')
assert exc_info.value.errors() == [
{
'input': 'bar',
'loc': ('bar',),
'msg': 'Input should be a valid integer, unable to parse string as an integer',
'type': 'int_parsing',
'url': 'https://errors.pydantic.dev/2.4/v/int_parsing',
}
]