]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
✨ Enable Pydantic's serialization mode for responses, add support for Pydantic's...
authorSebastián Ramírez <tiangolo@gmail.com>
Fri, 4 Aug 2023 20:47:07 +0000 (22:47 +0200)
committerGitHub <noreply@github.com>
Fri, 4 Aug 2023 20:47:07 +0000 (22:47 +0200)
* ✨ Enable Pydantic's serialization mode for responses

* ✅ Update tests with new Pydantic v2 serialization mode

* ✅ Add a test for Pydantic v2's computed_field

31 files changed:
fastapi/routing.py
tests/test_computed_fields.py [new file with mode: 0644]
tests/test_filter_pydantic_sub_model_pv2.py
tests/test_tutorial/test_body_updates/test_tutorial001.py
tests/test_tutorial/test_body_updates/test_tutorial001_py310.py
tests/test_tutorial/test_body_updates/test_tutorial001_py39.py
tests/test_tutorial/test_dataclasses/test_tutorial002.py
tests/test_tutorial/test_dataclasses/test_tutorial003.py
tests/test_tutorial/test_extra_models/test_tutorial003.py
tests/test_tutorial/test_extra_models/test_tutorial003_py310.py
tests/test_tutorial/test_path_operation_advanced_configurations/test_tutorial004.py
tests/test_tutorial/test_path_operation_configurations/test_tutorial005.py
tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py310.py
tests/test_tutorial/test_path_operation_configurations/test_tutorial005_py39.py
tests/test_tutorial/test_response_model/test_tutorial003.py
tests/test_tutorial/test_response_model/test_tutorial003_01.py
tests/test_tutorial/test_response_model/test_tutorial003_01_py310.py
tests/test_tutorial/test_response_model/test_tutorial003_py310.py
tests/test_tutorial/test_response_model/test_tutorial004.py
tests/test_tutorial/test_response_model/test_tutorial004_py310.py
tests/test_tutorial/test_response_model/test_tutorial004_py39.py
tests/test_tutorial/test_response_model/test_tutorial005.py
tests/test_tutorial/test_response_model/test_tutorial005_py310.py
tests/test_tutorial/test_response_model/test_tutorial006.py
tests/test_tutorial/test_response_model/test_tutorial006_py310.py
tests/test_tutorial/test_security/test_tutorial005.py
tests/test_tutorial/test_security/test_tutorial005_an.py
tests/test_tutorial/test_security/test_tutorial005_an_py310.py
tests/test_tutorial/test_security/test_tutorial005_an_py39.py
tests/test_tutorial/test_security/test_tutorial005_py310.py
tests/test_tutorial/test_security/test_tutorial005_py39.py

index d8ff0579cb81382e25bd322744a2aedcea0e2d16..6efd40ff3bc9fefa964be83d44e10bc60b193fb0 100644 (file)
@@ -448,9 +448,7 @@ class APIRoute(routing.Route):
             self.response_field = create_response_field(
                 name=response_name,
                 type_=self.response_model,
-                # TODO: This should actually set mode='serialization', just, that changes the schemas
-                # mode="serialization",
-                mode="validation",
+                mode="serialization",
             )
             # Create a clone of the field, so that a Pydantic submodel is not returned
             # as is just because it's an instance of a subclass of a more limited class
diff --git a/tests/test_computed_fields.py b/tests/test_computed_fields.py
new file mode 100644 (file)
index 0000000..5286507
--- /dev/null
@@ -0,0 +1,77 @@
+import pytest
+from fastapi import FastAPI
+from fastapi.testclient import TestClient
+
+from .utils import needs_pydanticv2
+
+
+@pytest.fixture(name="client")
+def get_client():
+    app = FastAPI()
+
+    from pydantic import BaseModel, computed_field
+
+    class Rectangle(BaseModel):
+        width: int
+        length: int
+
+        @computed_field
+        @property
+        def area(self) -> int:
+            return self.width * self.length
+
+    @app.get("/")
+    def read_root() -> Rectangle:
+        return Rectangle(width=3, length=4)
+
+    client = TestClient(app)
+    return client
+
+
+@needs_pydanticv2
+def test_get(client: TestClient):
+    response = client.get("/")
+    assert response.status_code == 200, response.text
+    assert response.json() == {"width": 3, "length": 4, "area": 12}
+
+
+@needs_pydanticv2
+def test_openapi_schema(client: TestClient):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/": {
+                "get": {
+                    "summary": "Read Root",
+                    "operationId": "read_root__get",
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {"$ref": "#/components/schemas/Rectangle"}
+                                }
+                            },
+                        }
+                    },
+                }
+            }
+        },
+        "components": {
+            "schemas": {
+                "Rectangle": {
+                    "properties": {
+                        "width": {"type": "integer", "title": "Width"},
+                        "length": {"type": "integer", "title": "Length"},
+                        "area": {"type": "integer", "title": "Area", "readOnly": True},
+                    },
+                    "type": "object",
+                    "required": ["width", "length", "area"],
+                    "title": "Rectangle",
+                }
+            }
+        },
+    }
index ae12179bdf3149a137bdde2910cedba24547bcb0..9f5e6b08fb6670ea65ddd1ef74ab3f95f1eb4dc8 100644 (file)
@@ -1,7 +1,7 @@
 from typing import Optional
 
 import pytest
-from dirty_equals import HasRepr, IsDict
+from dirty_equals import HasRepr, IsDict, IsOneOf
 from fastapi import Depends, FastAPI
 from fastapi.exceptions import ResponseValidationError
 from fastapi.testclient import TestClient
@@ -139,7 +139,11 @@ def test_openapi_schema(client: TestClient):
                 },
                 "ModelA": {
                     "title": "ModelA",
-                    "required": ["name", "foo"],
+                    "required": IsOneOf(
+                        ["name", "description", "foo"],
+                        # TODO remove when deprecating Pydantic v1
+                        ["name", "foo"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
index b02f7c81c3f86b83edc162a50625864a0656914b..f1a46210aadeab9f3bb3d2864d3859ebe3d2ff71 100644 (file)
@@ -1,7 +1,8 @@
 import pytest
-from dirty_equals import IsDict
 from fastapi.testclient import TestClient
 
+from ...utils import needs_pydanticv1, needs_pydanticv2
+
 
 @pytest.fixture(name="client")
 def get_client():
@@ -36,7 +37,181 @@ def test_put(client: TestClient):
     }
 
 
+@needs_pydanticv2
 def test_openapi_schema(client: TestClient):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/items/{item_id}": {
+                "get": {
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Read Item",
+                    "operationId": "read_item_items__item_id__get",
+                    "parameters": [
+                        {
+                            "required": True,
+                            "schema": {"title": "Item Id", "type": "string"},
+                            "name": "item_id",
+                            "in": "path",
+                        }
+                    ],
+                },
+                "put": {
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Update Item",
+                    "operationId": "update_item_items__item_id__put",
+                    "parameters": [
+                        {
+                            "required": True,
+                            "schema": {"title": "Item Id", "type": "string"},
+                            "name": "item_id",
+                            "in": "path",
+                        }
+                    ],
+                    "requestBody": {
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/ItemInput"}
+                            }
+                        },
+                        "required": True,
+                    },
+                },
+            }
+        },
+        "components": {
+            "schemas": {
+                "ItemInput": {
+                    "title": "Item",
+                    "type": "object",
+                    "properties": {
+                        "name": {
+                            "title": "Name",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "price": {
+                            "title": "Price",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tax": {"title": "Tax", "type": "number", "default": 10.5},
+                        "tags": {
+                            "title": "Tags",
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ItemOutput": {
+                    "title": "Item",
+                    "type": "object",
+                    "required": ["name", "description", "price", "tax", "tags"],
+                    "properties": {
+                        "name": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Name",
+                        },
+                        "description": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Description",
+                        },
+                        "price": {
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                            "title": "Price",
+                        },
+                        "tax": {"title": "Tax", "type": "number", "default": 10.5},
+                        "tags": {
+                            "title": "Tags",
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ValidationError": {
+                    "title": "ValidationError",
+                    "required": ["loc", "msg", "type"],
+                    "type": "object",
+                    "properties": {
+                        "loc": {
+                            "title": "Location",
+                            "type": "array",
+                            "items": {
+                                "anyOf": [{"type": "string"}, {"type": "integer"}]
+                            },
+                        },
+                        "msg": {"title": "Message", "type": "string"},
+                        "type": {"title": "Error Type", "type": "string"},
+                    },
+                },
+                "HTTPValidationError": {
+                    "title": "HTTPValidationError",
+                    "type": "object",
+                    "properties": {
+                        "detail": {
+                            "title": "Detail",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ValidationError"},
+                        }
+                    },
+                },
+            }
+        },
+    }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_openapi_schema_pv1(client: TestClient):
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
     assert response.json() == {
@@ -124,36 +299,9 @@ def test_openapi_schema(client: TestClient):
                     "title": "Item",
                     "type": "object",
                     "properties": {
-                        "name": IsDict(
-                            {
-                                "title": "Name",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Name", "type": "string"}
-                        ),
-                        "description": IsDict(
-                            {
-                                "title": "Description",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Description", "type": "string"}
-                        ),
-                        "price": IsDict(
-                            {
-                                "title": "Price",
-                                "anyOf": [{"type": "number"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Price", "type": "number"}
-                        ),
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {"title": "Description", "type": "string"},
+                        "price": {"title": "Price", "type": "number"},
                         "tax": {"title": "Tax", "type": "number", "default": 10.5},
                         "tags": {
                             "title": "Tags",
index 4af2652a78e360cdcf21ddb19e85bf1514e9a2ba..ab696e4c8dfec5704b3d062402c3ccc067c0674a 100644 (file)
@@ -1,8 +1,7 @@
 import pytest
-from dirty_equals import IsDict
 from fastapi.testclient import TestClient
 
-from ...utils import needs_py310
+from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
 
 
 @pytest.fixture(name="client")
@@ -41,7 +40,182 @@ def test_put(client: TestClient):
 
 
 @needs_py310
+@needs_pydanticv2
 def test_openapi_schema(client: TestClient):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/items/{item_id}": {
+                "get": {
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Read Item",
+                    "operationId": "read_item_items__item_id__get",
+                    "parameters": [
+                        {
+                            "required": True,
+                            "schema": {"title": "Item Id", "type": "string"},
+                            "name": "item_id",
+                            "in": "path",
+                        }
+                    ],
+                },
+                "put": {
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Update Item",
+                    "operationId": "update_item_items__item_id__put",
+                    "parameters": [
+                        {
+                            "required": True,
+                            "schema": {"title": "Item Id", "type": "string"},
+                            "name": "item_id",
+                            "in": "path",
+                        }
+                    ],
+                    "requestBody": {
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/ItemInput"}
+                            }
+                        },
+                        "required": True,
+                    },
+                },
+            }
+        },
+        "components": {
+            "schemas": {
+                "ItemInput": {
+                    "title": "Item",
+                    "type": "object",
+                    "properties": {
+                        "name": {
+                            "title": "Name",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "price": {
+                            "title": "Price",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tax": {"title": "Tax", "type": "number", "default": 10.5},
+                        "tags": {
+                            "title": "Tags",
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ItemOutput": {
+                    "title": "Item",
+                    "type": "object",
+                    "required": ["name", "description", "price", "tax", "tags"],
+                    "properties": {
+                        "name": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Name",
+                        },
+                        "description": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Description",
+                        },
+                        "price": {
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                            "title": "Price",
+                        },
+                        "tax": {"title": "Tax", "type": "number", "default": 10.5},
+                        "tags": {
+                            "title": "Tags",
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ValidationError": {
+                    "title": "ValidationError",
+                    "required": ["loc", "msg", "type"],
+                    "type": "object",
+                    "properties": {
+                        "loc": {
+                            "title": "Location",
+                            "type": "array",
+                            "items": {
+                                "anyOf": [{"type": "string"}, {"type": "integer"}]
+                            },
+                        },
+                        "msg": {"title": "Message", "type": "string"},
+                        "type": {"title": "Error Type", "type": "string"},
+                    },
+                },
+                "HTTPValidationError": {
+                    "title": "HTTPValidationError",
+                    "type": "object",
+                    "properties": {
+                        "detail": {
+                            "title": "Detail",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ValidationError"},
+                        }
+                    },
+                },
+            }
+        },
+    }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_py310
+@needs_pydanticv1
+def test_openapi_schema_pv1(client: TestClient):
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
     assert response.json() == {
@@ -129,36 +303,9 @@ def test_openapi_schema(client: TestClient):
                     "title": "Item",
                     "type": "object",
                     "properties": {
-                        "name": IsDict(
-                            {
-                                "title": "Name",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Name", "type": "string"}
-                        ),
-                        "description": IsDict(
-                            {
-                                "title": "Description",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Description", "type": "string"}
-                        ),
-                        "price": IsDict(
-                            {
-                                "title": "Price",
-                                "anyOf": [{"type": "number"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Price", "type": "number"}
-                        ),
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {"title": "Description", "type": "string"},
+                        "price": {"title": "Price", "type": "number"},
                         "tax": {"title": "Tax", "type": "number", "default": 10.5},
                         "tags": {
                             "title": "Tags",
index 832f45388443901c98fcde0a361f025345f1954b..2ee6a5cb4c695639ee8c564f684d49bdfc116900 100644 (file)
@@ -1,8 +1,7 @@
 import pytest
-from dirty_equals import IsDict
 from fastapi.testclient import TestClient
 
-from ...utils import needs_py39
+from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2
 
 
 @pytest.fixture(name="client")
@@ -41,7 +40,182 @@ def test_put(client: TestClient):
 
 
 @needs_py39
+@needs_pydanticv2
 def test_openapi_schema(client: TestClient):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/items/{item_id}": {
+                "get": {
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Read Item",
+                    "operationId": "read_item_items__item_id__get",
+                    "parameters": [
+                        {
+                            "required": True,
+                            "schema": {"title": "Item Id", "type": "string"},
+                            "name": "item_id",
+                            "in": "path",
+                        }
+                    ],
+                },
+                "put": {
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Update Item",
+                    "operationId": "update_item_items__item_id__put",
+                    "parameters": [
+                        {
+                            "required": True,
+                            "schema": {"title": "Item Id", "type": "string"},
+                            "name": "item_id",
+                            "in": "path",
+                        }
+                    ],
+                    "requestBody": {
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/ItemInput"}
+                            }
+                        },
+                        "required": True,
+                    },
+                },
+            }
+        },
+        "components": {
+            "schemas": {
+                "ItemInput": {
+                    "title": "Item",
+                    "type": "object",
+                    "properties": {
+                        "name": {
+                            "title": "Name",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "price": {
+                            "title": "Price",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tax": {"title": "Tax", "type": "number", "default": 10.5},
+                        "tags": {
+                            "title": "Tags",
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ItemOutput": {
+                    "title": "Item",
+                    "type": "object",
+                    "required": ["name", "description", "price", "tax", "tags"],
+                    "properties": {
+                        "name": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Name",
+                        },
+                        "description": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Description",
+                        },
+                        "price": {
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                            "title": "Price",
+                        },
+                        "tax": {"title": "Tax", "type": "number", "default": 10.5},
+                        "tags": {
+                            "title": "Tags",
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ValidationError": {
+                    "title": "ValidationError",
+                    "required": ["loc", "msg", "type"],
+                    "type": "object",
+                    "properties": {
+                        "loc": {
+                            "title": "Location",
+                            "type": "array",
+                            "items": {
+                                "anyOf": [{"type": "string"}, {"type": "integer"}]
+                            },
+                        },
+                        "msg": {"title": "Message", "type": "string"},
+                        "type": {"title": "Error Type", "type": "string"},
+                    },
+                },
+                "HTTPValidationError": {
+                    "title": "HTTPValidationError",
+                    "type": "object",
+                    "properties": {
+                        "detail": {
+                            "title": "Detail",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ValidationError"},
+                        }
+                    },
+                },
+            }
+        },
+    }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_py39
+@needs_pydanticv1
+def test_openapi_schema_pv1(client: TestClient):
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
     assert response.json() == {
@@ -129,36 +303,9 @@ def test_openapi_schema(client: TestClient):
                     "title": "Item",
                     "type": "object",
                     "properties": {
-                        "name": IsDict(
-                            {
-                                "title": "Name",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Name", "type": "string"}
-                        ),
-                        "description": IsDict(
-                            {
-                                "title": "Description",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Description", "type": "string"}
-                        ),
-                        "price": IsDict(
-                            {
-                                "title": "Price",
-                                "anyOf": [{"type": "number"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Price", "type": "number"}
-                        ),
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {"title": "Description", "type": "string"},
+                        "price": {"title": "Price", "type": "number"},
                         "tax": {"title": "Tax", "type": "number", "default": 10.5},
                         "tags": {
                             "title": "Tags",
index 7d88e286168c45c846fc71945a9a3e3024c60593..4146f4cd65dfe9ec3b7222efc4e7077653e36e88 100644 (file)
@@ -1,4 +1,4 @@
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.dataclasses.tutorial002 import app
@@ -21,8 +21,7 @@ def test_get_item():
 def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
-    data = response.json()
-    assert data == {
+    assert response.json() == {
         "openapi": "3.1.0",
         "info": {"title": "FastAPI", "version": "0.1.0"},
         "paths": {
@@ -47,7 +46,11 @@ def test_openapi_schema():
             "schemas": {
                 "Item": {
                     "title": "Item",
-                    "required": ["name", "price"],
+                    "required": IsOneOf(
+                        ["name", "price", "tags", "description", "tax"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["name", "price"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
@@ -57,7 +60,6 @@ def test_openapi_schema():
                                 "title": "Tags",
                                 "type": "array",
                                 "items": {"type": "string"},
-                                "default": [],
                             }
                         )
                         | IsDict(
index 597757e0931c9d86c6f8181bb9aa6316ab1f8739..2e5809914f8500a00fca0a703ace1b36731f165d 100644 (file)
@@ -1,8 +1,9 @@
-from dirty_equals import IsDict
 from fastapi.testclient import TestClient
 
 from docs_src.dataclasses.tutorial003 import app
 
+from ...utils import needs_pydanticv1, needs_pydanticv2
+
 client = TestClient(app)
 
 
@@ -52,6 +53,7 @@ def test_get_authors():
     ]
 
 
+@needs_pydanticv2
 def test_openapi_schema():
     response = client.get("/openapi.json")
     assert response.status_code == 200
@@ -77,7 +79,7 @@ def test_openapi_schema():
                                 "schema": {
                                     "title": "Items",
                                     "type": "array",
-                                    "items": {"$ref": "#/components/schemas/Item"},
+                                    "items": {"$ref": "#/components/schemas/ItemInput"},
                                 }
                             }
                         },
@@ -132,26 +134,164 @@ def test_openapi_schema():
             "schemas": {
                 "Author": {
                     "title": "Author",
+                    "required": ["name", "items"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "items": {
+                            "title": "Items",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ItemOutput"},
+                        },
+                    },
+                },
+                "HTTPValidationError": {
+                    "title": "HTTPValidationError",
+                    "type": "object",
+                    "properties": {
+                        "detail": {
+                            "title": "Detail",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ValidationError"},
+                        }
+                    },
+                },
+                "ItemInput": {
+                    "title": "Item",
                     "required": ["name"],
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
-                        "items": IsDict(
-                            {
-                                "title": "Items",
-                                "type": "array",
-                                "items": {"$ref": "#/components/schemas/Item"},
-                                "default": [],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {
-                                "title": "Items",
-                                "type": "array",
-                                "items": {"$ref": "#/components/schemas/Item"},
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                    },
+                },
+                "ItemOutput": {
+                    "title": "Item",
+                    "required": ["name", "description"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                    },
+                },
+                "ValidationError": {
+                    "title": "ValidationError",
+                    "required": ["loc", "msg", "type"],
+                    "type": "object",
+                    "properties": {
+                        "loc": {
+                            "title": "Location",
+                            "type": "array",
+                            "items": {
+                                "anyOf": [{"type": "string"}, {"type": "integer"}]
+                            },
+                        },
+                        "msg": {"title": "Message", "type": "string"},
+                        "type": {"title": "Error Type", "type": "string"},
+                    },
+                },
+            }
+        },
+    }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_openapi_schema_pv1():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/authors/{author_id}/items/": {
+                "post": {
+                    "summary": "Create Author Items",
+                    "operationId": "create_author_items_authors__author_id__items__post",
+                    "parameters": [
+                        {
+                            "required": True,
+                            "schema": {"title": "Author Id", "type": "string"},
+                            "name": "author_id",
+                            "in": "path",
+                        }
+                    ],
+                    "requestBody": {
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "title": "Items",
+                                    "type": "array",
+                                    "items": {"$ref": "#/components/schemas/Item"},
+                                }
                             }
-                        ),
+                        },
+                        "required": True,
+                    },
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {"$ref": "#/components/schemas/Author"}
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                }
+            },
+            "/authors/": {
+                "get": {
+                    "summary": "Get Authors",
+                    "operationId": "get_authors_authors__get",
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "title": "Response Get Authors Authors  Get",
+                                        "type": "array",
+                                        "items": {
+                                            "$ref": "#/components/schemas/Author"
+                                        },
+                                    }
+                                }
+                            },
+                        }
+                    },
+                }
+            },
+        },
+        "components": {
+            "schemas": {
+                "Author": {
+                    "title": "Author",
+                    "required": ["name"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "items": {
+                            "title": "Items",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/Item"},
+                        },
                     },
                 },
                 "HTTPValidationError": {
@@ -171,16 +311,7 @@ def test_openapi_schema():
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
-                        "description": IsDict(
-                            {
-                                "title": "Description",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Description", "type": "string"}
-                        ),
+                        "description": {"title": "Description", "type": "string"},
                     },
                 },
                 "ValidationError": {
index 21192b7db752e964e0a4216593a6a50201f20a97..0ccb99948f38170b2091396a406e7ff1585ec204 100644 (file)
@@ -1,3 +1,4 @@
+from dirty_equals import IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.extra_models.tutorial003 import app
@@ -76,7 +77,11 @@ def test_openapi_schema():
             "schemas": {
                 "PlaneItem": {
                     "title": "PlaneItem",
-                    "required": ["description", "size"],
+                    "required": IsOneOf(
+                        ["description", "type", "size"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["description", "size"],
+                    ),
                     "type": "object",
                     "properties": {
                         "description": {"title": "Description", "type": "string"},
@@ -86,7 +91,11 @@ def test_openapi_schema():
                 },
                 "CarItem": {
                     "title": "CarItem",
-                    "required": ["description"],
+                    "required": IsOneOf(
+                        ["description", "type"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["description"],
+                    ),
                     "type": "object",
                     "properties": {
                         "description": {"title": "Description", "type": "string"},
index c17ddbbe1d5507b0344ce8f95f4f0cb8eb043697..b2fe65fd9ffb6d10aa69e209b174c74bc76b9e5e 100644 (file)
@@ -1,4 +1,5 @@
 import pytest
+from dirty_equals import IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py310
@@ -86,7 +87,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "PlaneItem": {
                     "title": "PlaneItem",
-                    "required": ["description", "size"],
+                    "required": IsOneOf(
+                        ["description", "type", "size"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["description", "size"],
+                    ),
                     "type": "object",
                     "properties": {
                         "description": {"title": "Description", "type": "string"},
@@ -96,7 +101,11 @@ def test_openapi_schema(client: TestClient):
                 },
                 "CarItem": {
                     "title": "CarItem",
-                    "required": ["description"],
+                    "required": IsOneOf(
+                        ["description", "type"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["description"],
+                    ),
                     "type": "object",
                     "properties": {
                         "description": {"title": "Description", "type": "string"},
index dd123f48d6e8cff9f0bab1c9beee36f55a25ba4b..3ffc0bca7ce628fe4d885ddc8a7a4aee0d44ed1a 100644 (file)
@@ -1,8 +1,9 @@
-from dirty_equals import IsDict
 from fastapi.testclient import TestClient
 
 from docs_src.path_operation_advanced_configuration.tutorial004 import app
 
+from ...utils import needs_pydanticv1, needs_pydanticv2
+
 client = TestClient(app)
 
 
@@ -18,7 +19,137 @@ def test_query_params_str_validations():
     }
 
 
+@needs_pydanticv2
 def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/items/": {
+                "post": {
+                    "responses": {
+                        "200": {
+                            "description": "Successful Response",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Create an item",
+                    "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
+                    "operationId": "create_item_items__post",
+                    "requestBody": {
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/ItemInput"}
+                            }
+                        },
+                        "required": True,
+                    },
+                }
+            }
+        },
+        "components": {
+            "schemas": {
+                "ItemInput": {
+                    "title": "Item",
+                    "required": ["name", "price"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "price": {"title": "Price", "type": "number"},
+                        "tax": {
+                            "title": "Tax",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tags": {
+                            "title": "Tags",
+                            "uniqueItems": True,
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ItemOutput": {
+                    "title": "Item",
+                    "required": ["name", "description", "price", "tax", "tags"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "price": {"title": "Price", "type": "number"},
+                        "tax": {
+                            "title": "Tax",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tags": {
+                            "title": "Tags",
+                            "uniqueItems": True,
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ValidationError": {
+                    "title": "ValidationError",
+                    "required": ["loc", "msg", "type"],
+                    "type": "object",
+                    "properties": {
+                        "loc": {
+                            "title": "Location",
+                            "type": "array",
+                            "items": {
+                                "anyOf": [{"type": "string"}, {"type": "integer"}]
+                            },
+                        },
+                        "msg": {"title": "Message", "type": "string"},
+                        "type": {"title": "Error Type", "type": "string"},
+                    },
+                },
+                "HTTPValidationError": {
+                    "title": "HTTPValidationError",
+                    "type": "object",
+                    "properties": {
+                        "detail": {
+                            "title": "Detail",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ValidationError"},
+                        }
+                    },
+                },
+            }
+        },
+    }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_openapi_schema_pv1():
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
     assert response.json() == {
@@ -69,27 +200,9 @@ def test_openapi_schema():
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
-                        "description": IsDict(
-                            {
-                                "title": "Description",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Description", "type": "string"}
-                        ),
+                        "description": {"title": "Description", "type": "string"},
                         "price": {"title": "Price", "type": "number"},
-                        "tax": IsDict(
-                            {
-                                "title": "Tax",
-                                "anyOf": [{"type": "number"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Tax", "type": "number"}
-                        ),
+                        "tax": {"title": "Tax", "type": "number"},
                         "tags": {
                             "title": "Tags",
                             "uniqueItems": True,
index e7e9a982e1f33d8ada66b245103f27367fc362b2..ff98295a60a44b6763627ffe7fd8db26e0222807 100644 (file)
@@ -1,8 +1,9 @@
-from dirty_equals import IsDict
 from fastapi.testclient import TestClient
 
 from docs_src.path_operation_configuration.tutorial005 import app
 
+from ...utils import needs_pydanticv1, needs_pydanticv2
+
 client = TestClient(app)
 
 
@@ -18,7 +19,137 @@ def test_query_params_str_validations():
     }
 
 
+@needs_pydanticv2
 def test_openapi_schema():
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/items/": {
+                "post": {
+                    "responses": {
+                        "200": {
+                            "description": "The created item",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Create an item",
+                    "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
+                    "operationId": "create_item_items__post",
+                    "requestBody": {
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/ItemInput"}
+                            }
+                        },
+                        "required": True,
+                    },
+                }
+            }
+        },
+        "components": {
+            "schemas": {
+                "ItemInput": {
+                    "title": "Item",
+                    "required": ["name", "price"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "price": {"title": "Price", "type": "number"},
+                        "tax": {
+                            "title": "Tax",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tags": {
+                            "title": "Tags",
+                            "uniqueItems": True,
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ItemOutput": {
+                    "title": "Item",
+                    "required": ["name", "description", "price", "tax", "tags"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Description",
+                        },
+                        "price": {"title": "Price", "type": "number"},
+                        "tax": {
+                            "title": "Tax",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tags": {
+                            "title": "Tags",
+                            "uniqueItems": True,
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ValidationError": {
+                    "title": "ValidationError",
+                    "required": ["loc", "msg", "type"],
+                    "type": "object",
+                    "properties": {
+                        "loc": {
+                            "title": "Location",
+                            "type": "array",
+                            "items": {
+                                "anyOf": [{"type": "string"}, {"type": "integer"}]
+                            },
+                        },
+                        "msg": {"title": "Message", "type": "string"},
+                        "type": {"title": "Error Type", "type": "string"},
+                    },
+                },
+                "HTTPValidationError": {
+                    "title": "HTTPValidationError",
+                    "type": "object",
+                    "properties": {
+                        "detail": {
+                            "title": "Detail",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ValidationError"},
+                        }
+                    },
+                },
+            }
+        },
+    }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_pydanticv1
+def test_openapi_schema_pv1():
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
     assert response.json() == {
@@ -69,27 +200,9 @@ def test_openapi_schema():
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
-                        "description": IsDict(
-                            {
-                                "title": "Description",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Description", "type": "string"}
-                        ),
+                        "description": {"title": "Description", "type": "string"},
                         "price": {"title": "Price", "type": "number"},
-                        "tax": IsDict(
-                            {
-                                "title": "Tax",
-                                "anyOf": [{"type": "number"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Tax", "type": "number"}
-                        ),
+                        "tax": {"title": "Tax", "type": "number"},
                         "tags": {
                             "title": "Tags",
                             "uniqueItems": True,
index ebfeb809cc3a13cb86c42d0d36056e6f6021bec7..ad1c09eaec5087df85aa7b32163103a1359bead1 100644 (file)
@@ -1,8 +1,7 @@
 import pytest
-from dirty_equals import IsDict
 from fastapi.testclient import TestClient
 
-from ...utils import needs_py310
+from ...utils import needs_py310, needs_pydanticv1, needs_pydanticv2
 
 
 @pytest.fixture(name="client")
@@ -27,7 +26,138 @@ def test_query_params_str_validations(client: TestClient):
 
 
 @needs_py310
+@needs_pydanticv2
 def test_openapi_schema(client: TestClient):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/items/": {
+                "post": {
+                    "responses": {
+                        "200": {
+                            "description": "The created item",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Create an item",
+                    "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
+                    "operationId": "create_item_items__post",
+                    "requestBody": {
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/ItemInput"}
+                            }
+                        },
+                        "required": True,
+                    },
+                }
+            }
+        },
+        "components": {
+            "schemas": {
+                "ItemInput": {
+                    "title": "Item",
+                    "required": ["name", "price"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "price": {"title": "Price", "type": "number"},
+                        "tax": {
+                            "title": "Tax",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tags": {
+                            "title": "Tags",
+                            "uniqueItems": True,
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ItemOutput": {
+                    "title": "Item",
+                    "required": ["name", "description", "price", "tax", "tags"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Description",
+                        },
+                        "price": {"title": "Price", "type": "number"},
+                        "tax": {
+                            "title": "Tax",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tags": {
+                            "title": "Tags",
+                            "uniqueItems": True,
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ValidationError": {
+                    "title": "ValidationError",
+                    "required": ["loc", "msg", "type"],
+                    "type": "object",
+                    "properties": {
+                        "loc": {
+                            "title": "Location",
+                            "type": "array",
+                            "items": {
+                                "anyOf": [{"type": "string"}, {"type": "integer"}]
+                            },
+                        },
+                        "msg": {"title": "Message", "type": "string"},
+                        "type": {"title": "Error Type", "type": "string"},
+                    },
+                },
+                "HTTPValidationError": {
+                    "title": "HTTPValidationError",
+                    "type": "object",
+                    "properties": {
+                        "detail": {
+                            "title": "Detail",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ValidationError"},
+                        }
+                    },
+                },
+            }
+        },
+    }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_py310
+@needs_pydanticv1
+def test_openapi_schema_pv1(client: TestClient):
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
     assert response.json() == {
@@ -78,27 +208,9 @@ def test_openapi_schema(client: TestClient):
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
-                        "description": IsDict(
-                            {
-                                "title": "Description",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Description", "type": "string"}
-                        ),
+                        "description": {"title": "Description", "type": "string"},
                         "price": {"title": "Price", "type": "number"},
-                        "tax": IsDict(
-                            {
-                                "title": "Tax",
-                                "anyOf": [{"type": "number"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Tax", "type": "number"}
-                        ),
+                        "tax": {"title": "Tax", "type": "number"},
                         "tags": {
                             "title": "Tags",
                             "uniqueItems": True,
index 8e79afe968843e9aef4eb31ae2da671d327fc87c..045d1d402c949facab6e7973ce5baf1b61c74cdc 100644 (file)
@@ -1,8 +1,7 @@
 import pytest
-from dirty_equals import IsDict
 from fastapi.testclient import TestClient
 
-from ...utils import needs_py39
+from ...utils import needs_py39, needs_pydanticv1, needs_pydanticv2
 
 
 @pytest.fixture(name="client")
@@ -27,7 +26,138 @@ def test_query_params_str_validations(client: TestClient):
 
 
 @needs_py39
+@needs_pydanticv2
 def test_openapi_schema(client: TestClient):
+    response = client.get("/openapi.json")
+    assert response.status_code == 200, response.text
+    assert response.json() == {
+        "openapi": "3.1.0",
+        "info": {"title": "FastAPI", "version": "0.1.0"},
+        "paths": {
+            "/items/": {
+                "post": {
+                    "responses": {
+                        "200": {
+                            "description": "The created item",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/ItemOutput"
+                                    }
+                                }
+                            },
+                        },
+                        "422": {
+                            "description": "Validation Error",
+                            "content": {
+                                "application/json": {
+                                    "schema": {
+                                        "$ref": "#/components/schemas/HTTPValidationError"
+                                    }
+                                }
+                            },
+                        },
+                    },
+                    "summary": "Create an item",
+                    "description": "Create an item with all the information:\n\n- **name**: each item must have a name\n- **description**: a long description\n- **price**: required\n- **tax**: if the item doesn't have tax, you can omit this\n- **tags**: a set of unique tag strings for this item",
+                    "operationId": "create_item_items__post",
+                    "requestBody": {
+                        "content": {
+                            "application/json": {
+                                "schema": {"$ref": "#/components/schemas/ItemInput"}
+                            }
+                        },
+                        "required": True,
+                    },
+                }
+            }
+        },
+        "components": {
+            "schemas": {
+                "ItemInput": {
+                    "title": "Item",
+                    "required": ["name", "price"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "title": "Description",
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                        },
+                        "price": {"title": "Price", "type": "number"},
+                        "tax": {
+                            "title": "Tax",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tags": {
+                            "title": "Tags",
+                            "uniqueItems": True,
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ItemOutput": {
+                    "title": "Item",
+                    "required": ["name", "description", "price", "tax", "tags"],
+                    "type": "object",
+                    "properties": {
+                        "name": {"title": "Name", "type": "string"},
+                        "description": {
+                            "anyOf": [{"type": "string"}, {"type": "null"}],
+                            "title": "Description",
+                        },
+                        "price": {"title": "Price", "type": "number"},
+                        "tax": {
+                            "title": "Tax",
+                            "anyOf": [{"type": "number"}, {"type": "null"}],
+                        },
+                        "tags": {
+                            "title": "Tags",
+                            "uniqueItems": True,
+                            "type": "array",
+                            "items": {"type": "string"},
+                            "default": [],
+                        },
+                    },
+                },
+                "ValidationError": {
+                    "title": "ValidationError",
+                    "required": ["loc", "msg", "type"],
+                    "type": "object",
+                    "properties": {
+                        "loc": {
+                            "title": "Location",
+                            "type": "array",
+                            "items": {
+                                "anyOf": [{"type": "string"}, {"type": "integer"}]
+                            },
+                        },
+                        "msg": {"title": "Message", "type": "string"},
+                        "type": {"title": "Error Type", "type": "string"},
+                    },
+                },
+                "HTTPValidationError": {
+                    "title": "HTTPValidationError",
+                    "type": "object",
+                    "properties": {
+                        "detail": {
+                            "title": "Detail",
+                            "type": "array",
+                            "items": {"$ref": "#/components/schemas/ValidationError"},
+                        }
+                    },
+                },
+            }
+        },
+    }
+
+
+# TODO: remove when deprecating Pydantic v1
+@needs_py39
+@needs_pydanticv1
+def test_openapi_schema_pv1(client: TestClient):
     response = client.get("/openapi.json")
     assert response.status_code == 200, response.text
     assert response.json() == {
@@ -78,27 +208,9 @@ def test_openapi_schema(client: TestClient):
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
-                        "description": IsDict(
-                            {
-                                "title": "Description",
-                                "anyOf": [{"type": "string"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Description", "type": "string"}
-                        ),
+                        "description": {"title": "Description", "type": "string"},
                         "price": {"title": "Price", "type": "number"},
-                        "tax": IsDict(
-                            {
-                                "title": "Tax",
-                                "anyOf": [{"type": "number"}, {"type": "null"}],
-                            }
-                        )
-                        | IsDict(
-                            # TODO: remove when deprecating Pydantic v1
-                            {"title": "Tax", "type": "number"}
-                        ),
+                        "tax": {"title": "Tax", "type": "number"},
                         "tags": {
                             "title": "Tags",
                             "uniqueItems": True,
index 20221399b14fd825b289a3d82d670a43627ba14b..384c8e0f146d0ca04ef66f18dc0cfda8b25ebed1 100644 (file)
@@ -1,4 +1,4 @@
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.response_model.tutorial003 import app
@@ -70,7 +70,11 @@ def test_openapi_schema():
             "schemas": {
                 "UserOut": {
                     "title": "UserOut",
-                    "required": ["username", "email"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username", "email"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index e8f0658f4c38b68df40caa2f5999e84998976df1..3a6a0b20d9d20cf0b1d729f0abf9c7eba6816330 100644 (file)
@@ -1,4 +1,4 @@
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.response_model.tutorial003_01 import app
@@ -70,7 +70,11 @@ def test_openapi_schema():
             "schemas": {
                 "BaseUser": {
                     "title": "BaseUser",
-                    "required": ["username", "email"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username", "email"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index a69f8cc8debed3763f4ba8092775e2a5dd727642..6985b9de6af74a9cb9876a4f35726410098c7c98 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py310
@@ -79,7 +79,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "BaseUser": {
                     "title": "BaseUser",
-                    "required": ["username", "email"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username", "email"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index 64dcd6cbdf39cd06a2b69f2ae47c0ed7d5be9774..3a3aee38aaffa229cfa01bf853fc5a15b5b1188c 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py310
@@ -79,7 +79,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "UserOut": {
                     "title": "UserOut",
-                    "required": ["username", "email"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username", "email"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index 8beb847d1df7d36f172a2610e2334e6a5b1a31d1..e9bde18dd58c3cb95ec7a225d0d97cf81a636862 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.response_model.tutorial004 import app
@@ -79,7 +79,11 @@ def test_openapi_schema():
             "schemas": {
                 "Item": {
                     "title": "Item",
-                    "required": ["name", "price"],
+                    "required": IsOneOf(
+                        ["name", "description", "price", "tax", "tags"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["name", "price"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
index 28eb88c3478fd49bd3b8ca0d505cca5ed52bd3e1..6f8a3cbea8f09aa3877cdd876d8322af7d622bd2 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py310
@@ -87,7 +87,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "Item": {
                     "title": "Item",
-                    "required": ["name", "price"],
+                    "required": IsOneOf(
+                        ["name", "description", "price", "tax", "tags"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["name", "price"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
index 9e1a21f8db6ae0a981cd50c168f256b33ff9b038..cfaa1eba2152f2ea0975ebb1f69db20d735faead 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py39
@@ -87,7 +87,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "Item": {
                     "title": "Item",
-                    "required": ["name", "price"],
+                    "required": IsOneOf(
+                        ["name", "description", "price", "tax", "tags"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["name", "price"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
index 06e5d0fd153a3bf83c32c8584caa2f21d024d54d..b20864c07ab448cab3761f5cf09748f418d23f88 100644 (file)
@@ -1,4 +1,4 @@
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.response_model.tutorial005 import app
@@ -102,7 +102,11 @@ def test_openapi_schema():
             "schemas": {
                 "Item": {
                     "title": "Item",
-                    "required": ["name", "price"],
+                    "required": IsOneOf(
+                        ["name", "description", "price", "tax"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["name", "price"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
index 0f15662439b018719d55eb0fcb70685aadb23cb7..de552c8f22de5b87cc1175ef8f7c6d9bf77cfd33 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py310
@@ -112,7 +112,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "Item": {
                     "title": "Item",
-                    "required": ["name", "price"],
+                    "required": IsOneOf(
+                        ["name", "description", "price", "tax"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["name", "price"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
index 6e6152b9f16cbdd3e0f01939d5672a4dea55578c..1e47e2eadb05ee1709f4bbc93dc50243ca350249 100644 (file)
@@ -1,4 +1,4 @@
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.response_model.tutorial006 import app
@@ -102,7 +102,11 @@ def test_openapi_schema():
             "schemas": {
                 "Item": {
                     "title": "Item",
-                    "required": ["name", "price"],
+                    "required": IsOneOf(
+                        ["name", "description", "price", "tax"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["name", "price"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
index 9a980ab5b0ae5b154f11b6962e2d02930760274a..40058b12dea454864f3d734ac33dd695182d4f72 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py310
@@ -112,7 +112,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "Item": {
                     "title": "Item",
-                    "required": ["name", "price"],
+                    "required": IsOneOf(
+                        ["name", "description", "price", "tax"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["name", "price"],
+                    ),
                     "type": "object",
                     "properties": {
                         "name": {"title": "Name", "type": "string"},
index 22ae76f428f4aced3d3233b329fa8760a2474a3a..c669c306ddb014226c695dd06cb7b7e02c2e9930 100644 (file)
@@ -1,4 +1,4 @@
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.security.tutorial005 import (
@@ -267,7 +267,11 @@ def test_openapi_schema():
             "schemas": {
                 "User": {
                     "title": "User",
-                    "required": ["username"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name", "disabled"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index 07239cc8900515a4607afcd333fbbd0e15a80a2d..aaab04f78fb4efe3842e4ecfa8d3dff6bb1bfa5b 100644 (file)
@@ -1,4 +1,4 @@
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from docs_src.security.tutorial005_an import (
@@ -267,7 +267,11 @@ def test_openapi_schema():
             "schemas": {
                 "User": {
                     "title": "User",
-                    "required": ["username"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name", "disabled"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index 1ab836639e6f9ef07d4f81051fe99d18d4f76e81..243d0773c266ce0724527d3ba7b32787b045e115 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py310
@@ -295,7 +295,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "User": {
                     "title": "User",
-                    "required": ["username"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name", "disabled"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index 6aabbe04acc796a05f11fa44735fd6dde5dbe7f8..17a3f9aa2a54820d8b1b4e896cb47954298b136e 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py39
@@ -295,7 +295,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "User": {
                     "title": "User",
-                    "required": ["username"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name", "disabled"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index c21884df83a9839f7ba713ef08c2a6a3fb312e32..06455cd632f0953d68f1759a4daec6b44cda62d8 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py310
@@ -295,7 +295,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "User": {
                     "title": "User",
-                    "required": ["username"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name", "disabled"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},
index 170c5d60b867da654952b60d06c1f65654781678..9455bfb4ef6b93b70dc81de0a53bc8a4b659f282 100644 (file)
@@ -1,5 +1,5 @@
 import pytest
-from dirty_equals import IsDict
+from dirty_equals import IsDict, IsOneOf
 from fastapi.testclient import TestClient
 
 from ...utils import needs_py39
@@ -295,7 +295,11 @@ def test_openapi_schema(client: TestClient):
             "schemas": {
                 "User": {
                     "title": "User",
-                    "required": ["username"],
+                    "required": IsOneOf(
+                        ["username", "email", "full_name", "disabled"],
+                        # TODO: remove when deprecating Pydantic v1
+                        ["username"],
+                    ),
                     "type": "object",
                     "properties": {
                         "username": {"title": "Username", "type": "string"},