import re
import warnings
from copy import copy, deepcopy
-from dataclasses import dataclass
+from dataclasses import dataclass, is_dataclass
from enum import Enum
from typing import (
Any,
from fastapi._compat import may_v1, shared
from fastapi.openapi.constants import REF_TEMPLATE
from fastapi.types import IncEx, ModelNameMap, UnionType
-from pydantic import BaseModel, TypeAdapter, create_model
+from pydantic import BaseModel, ConfigDict, TypeAdapter, create_model
from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
from pydantic import PydanticUndefinedAnnotation as PydanticUndefinedAnnotation
from pydantic import ValidationError as ValidationError
field_info: FieldInfo
name: str
mode: Literal["validation", "serialization"] = "validation"
+ config: Union[ConfigDict, None] = None
@property
def alias(self) -> str:
warnings.simplefilter(
"ignore", category=UnsupportedFieldAttributeWarning
)
+ annotated_args = (
+ self.field_info.annotation,
+ *self.field_info.metadata,
+ self.field_info,
+ )
self._type_adapter: TypeAdapter[Any] = TypeAdapter(
- Annotated[self.field_info.annotation, self.field_info]
+ Annotated[annotated_args],
+ config=self.config,
)
def get_default(self) -> Any:
def get_model_fields(model: Type[BaseModel]) -> List[ModelField]:
- return [
- ModelField(field_info=field_info, name=name)
- for name, field_info in model.model_fields.items()
- ]
+ 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_):
+ model_config = None
+ else:
+ model_config = model.model_config
+ model_fields.append(
+ ModelField(
+ field_info=field_info,
+ name=name,
+ config=model_config,
+ )
+ )
+ return model_fields
# Duplicate of several schema functions from Pydantic v1 to make them compatible with
--- /dev/null
+from typing import List
+
+import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+from inline_snapshot import snapshot
+from typing_extensions import Annotated
+
+from .utils import needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client():
+ from pydantic import (
+ BaseModel,
+ ConfigDict,
+ PlainSerializer,
+ TypeAdapter,
+ WithJsonSchema,
+ )
+
+ class FakeNumpyArray:
+ def __init__(self):
+ self.data = [1.0, 2.0, 3.0]
+
+ FakeNumpyArrayPydantic = Annotated[
+ FakeNumpyArray,
+ WithJsonSchema(TypeAdapter(List[float]).json_schema()),
+ PlainSerializer(lambda v: v.data),
+ ]
+
+ class MyModel(BaseModel):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+ custom_field: FakeNumpyArrayPydantic
+
+ app = FastAPI()
+
+ @app.get("/")
+ def test() -> MyModel:
+ return MyModel(custom_field=FakeNumpyArray())
+
+ client = TestClient(app)
+ return client
+
+
+@needs_pydanticv2
+def test_get(client: TestClient):
+ response = client.get("/")
+ assert response.json() == {"custom_field": [1.0, 2.0, 3.0]}
+
+
+@needs_pydanticv2
+def test_typeadapter():
+ # This test is only to confirm that Pydantic alone is working as expected
+ from pydantic import (
+ BaseModel,
+ ConfigDict,
+ PlainSerializer,
+ TypeAdapter,
+ WithJsonSchema,
+ )
+
+ class FakeNumpyArray:
+ def __init__(self):
+ self.data = [1.0, 2.0, 3.0]
+
+ FakeNumpyArrayPydantic = Annotated[
+ FakeNumpyArray,
+ WithJsonSchema(TypeAdapter(List[float]).json_schema()),
+ PlainSerializer(lambda v: v.data),
+ ]
+
+ class MyModel(BaseModel):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+ custom_field: FakeNumpyArrayPydantic
+
+ ta = TypeAdapter(MyModel)
+ assert ta.dump_python(MyModel(custom_field=FakeNumpyArray())) == {
+ "custom_field": [1.0, 2.0, 3.0]
+ }
+ assert ta.json_schema() == snapshot(
+ {
+ "properties": {
+ "custom_field": {
+ "items": {"type": "number"},
+ "title": "Custom Field",
+ "type": "array",
+ }
+ },
+ "required": ["custom_field"],
+ "title": "MyModel",
+ "type": "object",
+ }
+ )
+
+
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient):
+ response = client.get("openapi.json")
+ assert response.json() == snapshot(
+ {
+ "openapi": "3.1.0",
+ "info": {"title": "FastAPI", "version": "0.1.0"},
+ "paths": {
+ "/": {
+ "get": {
+ "summary": "Test",
+ "operationId": "test__get",
+ "responses": {
+ "200": {
+ "description": "Successful Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/MyModel"
+ }
+ }
+ },
+ }
+ },
+ }
+ }
+ },
+ "components": {
+ "schemas": {
+ "MyModel": {
+ "properties": {
+ "custom_field": {
+ "items": {"type": "number"},
+ "type": "array",
+ "title": "Custom Field",
+ }
+ },
+ "type": "object",
+ "required": ["custom_field"],
+ "title": "MyModel",
+ }
+ }
+ },
+ }
+ )