From 90685513e482a433a94b5cf7256d28956726dc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 11 Oct 2025 01:28:03 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20examples=20for?= =?UTF-8?q?=20migrating=20from=20Pydantic=20v1=20to=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test_pydantic_v1_in_v2/__init__.py | 0 .../test_tutorial001.py | 36 ++++ .../test_tutorial002.py | 140 ++++++++++++++++ .../test_tutorial003.py | 154 ++++++++++++++++++ .../test_tutorial004.py | 153 +++++++++++++++++ 5 files changed, 483 insertions(+) create mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py create mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py create mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py create mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py create mode 100644 tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py b/tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py new file mode 100644 index 000000000..b4bf991e7 --- /dev/null +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py @@ -0,0 +1,36 @@ +import sys + +import pytest +from fastapi._compat import PYDANTIC_V2 + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + + +if not PYDANTIC_V2: + pytest.skip("This test is only for Pydantic v2", allow_module_level=True) + +import importlib + +import pytest + +from ...utils import needs_py310 + + +@pytest.fixture( + name="mod", + params=[ + "tutorial001_an", + pytest.param("tutorial001_an_py310", marks=needs_py310), + ], +) +def get_mod(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + return mod + + +def test_model(mod: Any): + item = mod.Item(name="Foo", size=3.4) + assert item.dict() == {"name": "Foo", "description": None, "size": 3.4} diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py new file mode 100644 index 000000000..a402c663d --- /dev/null +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py @@ -0,0 +1,140 @@ +import sys + +import pytest +from fastapi._compat import PYDANTIC_V2 +from inline_snapshot import snapshot + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + + +if not PYDANTIC_V2: + pytest.skip("This test is only for Pydantic v2", allow_module_level=True) + +import importlib + +import pytest +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial002_an", + pytest.param("tutorial002_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + + c = TestClient(mod.app) + return c + + +def test_call(client: TestClient): + response = client.post("/items/", json={"name": "Foo", "size": 3.4}) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Foo", + "description": None, + "size": 3.4, + } + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": {"type": "string", "title": "Description"}, + "size": {"type": "number", "title": "Size"}, + }, + "type": "object", + "required": ["name", "size"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py new file mode 100644 index 000000000..03155c924 --- /dev/null +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py @@ -0,0 +1,154 @@ +import sys + +import pytest +from fastapi._compat import PYDANTIC_V2 +from inline_snapshot import snapshot + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +if not PYDANTIC_V2: + pytest.skip("This test is only for Pydantic v2", allow_module_level=True) + + +import importlib + +from fastapi.testclient import TestClient + +from ...utils import needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial003_an", + pytest.param("tutorial003_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + + c = TestClient(mod.app) + return c + + +def test_call(client: TestClient): + response = client.post("/items/", json={"name": "Foo", "size": 3.4}) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Foo", + "description": None, + "size": 3.4, + } + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + {"$ref": "#/components/schemas/Item"} + ], + "title": "Item", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ItemV2" + } + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": {"type": "string", "title": "Description"}, + "size": {"type": "number", "title": "Size"}, + }, + "type": "object", + "required": ["name", "size"], + "title": "Item", + }, + "ItemV2": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": { + "anyOf": [{"type": "string"}, {"type": "null"}], + "title": "Description", + }, + "size": {"type": "number", "title": "Size"}, + }, + "type": "object", + "required": ["name", "size"], + "title": "ItemV2", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + ) diff --git a/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py new file mode 100644 index 000000000..d2e204dda --- /dev/null +++ b/tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py @@ -0,0 +1,153 @@ +import sys + +import pytest +from fastapi._compat import PYDANTIC_V2 +from inline_snapshot import snapshot + +from tests.utils import skip_module_if_py_gte_314 + +if sys.version_info >= (3, 14): + skip_module_if_py_gte_314() + +if not PYDANTIC_V2: + pytest.skip("This test is only for Pydantic v2", allow_module_level=True) + + +import importlib + +from fastapi.testclient import TestClient + +from ...utils import needs_py39, needs_py310 + + +@pytest.fixture( + name="client", + params=[ + "tutorial004_an", + pytest.param("tutorial004_an_py39", marks=needs_py39), + pytest.param("tutorial004_an_py310", marks=needs_py310), + ], +) +def get_client(request: pytest.FixtureRequest): + mod = importlib.import_module(f"docs_src.pydantic_v1_in_v2.{request.param}") + + c = TestClient(mod.app) + return c + + +def test_call(client: TestClient): + response = client.post("/items/", json={"item": {"name": "Foo", "size": 3.4}}) + assert response.status_code == 200, response.text + assert response.json() == { + "name": "Foo", + "description": None, + "size": 3.4, + } + + +def test_openapi_schema(client: TestClient): + response = client.get("/openapi.json") + assert response.status_code == 200, response.text + assert response.json() == snapshot( + { + "openapi": "3.1.0", + "info": {"title": "FastAPI", "version": "0.1.0"}, + "paths": { + "/items/": { + "post": { + "summary": "Create Item", + "operationId": "create_item_items__post", + "requestBody": { + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Body_create_item_items__post" + } + ], + "title": "Body", + } + } + }, + "required": True, + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {"$ref": "#/components/schemas/Item"} + } + }, + }, + "422": { + "description": "Validation Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HTTPValidationError" + } + } + }, + }, + }, + } + } + }, + "components": { + "schemas": { + "Body_create_item_items__post": { + "properties": { + "item": { + "allOf": [{"$ref": "#/components/schemas/Item"}], + "title": "Item", + } + }, + "type": "object", + "required": ["item"], + "title": "Body_create_item_items__post", + }, + "HTTPValidationError": { + "properties": { + "detail": { + "items": { + "$ref": "#/components/schemas/ValidationError" + }, + "type": "array", + "title": "Detail", + } + }, + "type": "object", + "title": "HTTPValidationError", + }, + "Item": { + "properties": { + "name": {"type": "string", "title": "Name"}, + "description": {"type": "string", "title": "Description"}, + "size": {"type": "number", "title": "Size"}, + }, + "type": "object", + "required": ["name", "size"], + "title": "Item", + }, + "ValidationError": { + "properties": { + "loc": { + "items": { + "anyOf": [{"type": "string"}, {"type": "integer"}] + }, + "type": "array", + "title": "Location", + }, + "msg": {"type": "string", "title": "Message"}, + "type": {"type": "string", "title": "Error Type"}, + }, + "type": "object", + "required": ["loc", "msg", "type"], + "title": "ValidationError", + }, + } + }, + } + )