Skip to content

Commit

Permalink
Fix bug with schema generation with Field(...) in a forward ref (#8494)
Browse files Browse the repository at this point in the history
  • Loading branch information
dmontagu committed Jan 5, 2024
1 parent 069d020 commit ace2748
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 3 deletions.
11 changes: 8 additions & 3 deletions pydantic/_internal/_generate_schema.py
Expand Up @@ -991,16 +991,21 @@ def _common_field_schema( # C901

evaluated = _typing_extra.eval_type_lenient(field_info.annotation, types_namespace, None)
if evaluated is not field_info.annotation and not has_instance_in_type(evaluated, PydanticRecursiveRef):
field_info.annotation = evaluated
new_field_info = FieldInfo.from_annotation(evaluated)
field_info.annotation = new_field_info.annotation

# 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:
# We skip over "attributes" that are present in the metadata_lookup dict because these won't
# actually end up as attributes of the `FieldInfo` instance.
if k not in field_info._attributes_set and k not in field_info.metadata_lookup:
setattr(field_info, k, v)

# Finally, ensure the field info also reflects all the `_attributes_set` that are actually metadata.
field_info.metadata = [*new_field_info.metadata, *field_info.metadata]

source_type, annotations = field_info.annotation, field_info.metadata

def set_discriminator(schema: CoreSchema) -> CoreSchema:
Expand Down
46 changes: 46 additions & 0 deletions tests/test_annotated.py
Expand Up @@ -424,3 +424,49 @@ class AnnotatedPrivateFieldModel(BaseModel):

with pytest.raises(AttributeError):
assert model.bar


def test_min_length_field_info_not_lost():
class AnnotatedFieldModel(BaseModel):
foo: 'Annotated[String, Field(min_length=3)]' = Field(description='hello')

String = str

AnnotatedFieldModel.model_rebuild()

assert AnnotatedFieldModel(foo='000').foo == '000'

with pytest.raises(ValidationError) as exc_info:
AnnotatedFieldModel(foo='00')

assert exc_info.value.errors(include_url=False) == [
{
'loc': ('foo',),
'input': '00',
'ctx': {'min_length': 3},
'msg': 'String should have at least 3 characters',
'type': 'string_too_short',
}
]

# Ensure that the inner annotation does not override the outer, even for metadata:
class AnnotatedFieldModel2(BaseModel):
foo: 'Annotated[String, Field(min_length=3)]' = Field(description='hello', min_length=2)

AnnotatedFieldModel2(foo='00')

class AnnotatedFieldModel4(BaseModel):
foo: 'Annotated[String, Field(min_length=3)]' = Field(description='hello', min_length=4)

with pytest.raises(ValidationError) as exc_info:
AnnotatedFieldModel4(foo='00')

assert exc_info.value.errors(include_url=False) == [
{
'loc': ('foo',),
'input': '00',
'ctx': {'min_length': 4},
'msg': 'String should have at least 4 characters',
'type': 'string_too_short',
}
]

0 comments on commit ace2748

Please sign in to comment.