pythonasyncioapiasyncfastapiframeworkjsonjson-schemaopenapiopenapi3pydanticpython-typespython3redocreststarletteswaggerswagger-uiuvicornweb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
66 lines
1.9 KiB
66 lines
1.9 KiB
"""
|
|
Regression tests for:
|
|
PydanticUserError when a generic BaseModel with a Json[T] field is
|
|
specialized with SerializeAsAny[SomeModel].
|
|
|
|
fastapi/_compat/v2.py: get_model_fields() must suppress config= for
|
|
Annotated-wrapped types whose inner type is a BaseModel / dataclass,
|
|
not only for bare BaseModel types.
|
|
"""
|
|
|
|
from typing import Generic, TypeVar
|
|
|
|
from fastapi import FastAPI
|
|
from pydantic import BaseModel, Json, SerializeAsAny
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
class Inner(BaseModel):
|
|
value: str
|
|
|
|
|
|
class Source(BaseModel, Generic[T]):
|
|
payload: Json[T]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_openapi_schema_generation_serialize_as_any_does_not_raise():
|
|
"""app.openapi() must not raise PydanticUserError for Source[SerializeAsAny[Inner]].
|
|
|
|
Previously raised:
|
|
pydantic.errors.PydanticUserError: Cannot use `config` when the type
|
|
is a BaseModel, dataclass or TypedDict.
|
|
|
|
Root cause: get_model_fields() in fastapi/_compat/v2.py did not unwrap
|
|
Annotated before checking if config= should be suppressed, so
|
|
Json[Annotated[Inner, SerializeAsAny]] slipped through and TypeAdapter
|
|
received config= illegally.
|
|
"""
|
|
app = FastAPI()
|
|
|
|
@app.get("/search")
|
|
def search() -> Source[SerializeAsAny[Inner]]: # pragma: no cover
|
|
...
|
|
|
|
schema = app.openapi()
|
|
assert schema is not None
|
|
# The route must appear in the generated schema.
|
|
assert "/search" in schema["paths"]
|
|
|
|
|
|
def test_openapi_schema_generation_plain_inner_unaffected():
|
|
"""Baseline: Source[Inner] (no SerializeAsAny) must continue to work."""
|
|
app = FastAPI()
|
|
|
|
@app.get("/search")
|
|
def search() -> Source[Inner]: # pragma: no cover
|
|
...
|
|
|
|
schema = app.openapi()
|
|
assert schema is not None
|
|
assert "/search" in schema["paths"]
|
|
|