Browse Source

Merge f83a382ee9 into 04b279fe77

pull/15115/merge
vishnu00734 4 months ago
committed by GitHub
parent
commit
7992a5ca56
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 9
      fastapi/_compat/v2.py
  2. 54
      tests/test_compat.py

9
fastapi/_compat/v2.py

@ -383,7 +383,14 @@ def get_model_fields(model: type[BaseModel]) -> list[ModelField]:
model_fields: list[ModelField] = []
for name, field_info in model.model_fields.items():
type_ = field_info.annotation
if lenient_issubclass(type_, (BaseModel, dict)) or is_dataclass(type_):
# Unwrap Annotated[T, ...] to T before the issubclass check.
# For RootModel fields and similar, pydantic may store the annotation
# as Annotated[SomeBaseModel, FieldInfo(...)]. lenient_issubclass does
# not recognise the Annotated wrapper as a BaseModel subclass, so
# model_config would be passed to TypeAdapter, which Pydantic rejects
# when the inner type is a BaseModel/dataclass/TypedDict.
check_type = get_args(type_)[0] if get_origin(type_) is Annotated else type_
if lenient_issubclass(check_type, (BaseModel, dict)) or is_dataclass(check_type):
model_config = None
else:
model_config = model.model_config

54
tests/test_compat.py

@ -1,3 +1,5 @@
from typing import Annotated
from fastapi import FastAPI, UploadFile
from fastapi._compat import (
Undefined,
@ -5,7 +7,7 @@ from fastapi._compat import (
)
from fastapi._compat.shared import is_bytes_sequence_annotation
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, ConfigDict, Field, RootModel
from pydantic.fields import FieldInfo
@ -130,3 +132,53 @@ def test_serialize_sequence_value_with_none_first_in_union():
result = v2.serialize_sequence_value(field=field, value=["x", "y"])
assert result == ["x", "y"]
assert isinstance(result, list)
def test_get_model_fields_annotated_basemodel():
"""
get_model_fields must not raise when a field annotation is Annotated[SomeBaseModel, ...].
RootModel stores its root field annotation verbatim, so for
RootModel[Annotated[Item, Field(...)]] the annotation is
Annotated[Item, FieldInfo(...)]. Before the fix, lenient_issubclass
returned False for the Annotated wrapper, causing model_config to be
forwarded to TypeAdapter, which Pydantic rejects for BaseModel types.
"""
from fastapi._compat import v2
class Item(BaseModel):
name: str
class Wrapper(RootModel[Annotated[Item, Field(description="an item")]]):
pass
# Must not raise PydanticUserError
fields = v2.get_model_fields(Wrapper)
assert len(fields) == 1
assert fields[0].name == "root"
def test_get_model_fields_annotated_basemodel_openapi():
"""
OpenAPI schema generation must not crash when a response model is a
RootModel whose root annotation is Annotated[SomeBaseModel, Field(...)].
"""
from fastapi.openapi.utils import get_openapi
class Item(BaseModel):
name: str
class Wrapper(RootModel[Annotated[Item, Field(description="an item")]]):
pass
app = FastAPI()
@app.get("/test", response_model=Wrapper)
def endpoint():
return Wrapper(root=Item(name="foo"))
client = TestClient(app)
response = client.get("/openapi.json")
assert response.status_code == 200, response.text
schema = response.json()
assert "/test" in schema["paths"]

Loading…
Cancel
Save