Browse Source

🔥 Remove Pydantic v1 tests

pull/14609/head
Sebastián Ramírez 7 months ago
parent
commit
7f76702908
  1. 1059
      tests/test_compat_params_v1.py
  2. 34
      tests/test_datetime_custom_encoder.py
  3. 0
      tests/test_filter_pydantic_sub_model/__init__.py
  4. 45
      tests/test_filter_pydantic_sub_model/app_pv1.py
  5. 146
      tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py
  6. 47
      tests/test_get_model_definitions_formfeed_escape.py
  7. 439
      tests/test_pydantic_v1_v2_01.py
  8. 682
      tests/test_pydantic_v1_v2_list.py
  9. 1408
      tests/test_pydantic_v1_v2_mixed.py
  10. 0
      tests/test_pydantic_v1_v2_multifile/__init__.py
  11. 137
      tests/test_pydantic_v1_v2_multifile/main.py
  12. 19
      tests/test_pydantic_v1_v2_multifile/modelsv1.py
  13. 19
      tests/test_pydantic_v1_v2_multifile/modelsv2.py
  14. 19
      tests/test_pydantic_v1_v2_multifile/modelsv2b.py
  15. 951
      tests/test_pydantic_v1_v2_multifile/test_multifile.py
  16. 692
      tests/test_pydantic_v1_v2_noneable.py
  17. 50
      tests/test_read_with_orm_mode.py
  18. 26
      tests/test_response_model_as_return_annotation.py
  19. 0
      tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py
  20. 31
      tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py
  21. 142
      tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py
  22. 157
      tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py
  23. 155
      tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py
  24. 127
      tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py
  25. 151
      tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py

1059
tests/test_compat_params_v1.py

File diff suppressed because it is too large

34
tests/test_datetime_custom_encoder.py

@ -1,12 +1,9 @@
import warnings
from datetime import datetime, timezone from datetime import datetime, timezone
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from pydantic import BaseModel from pydantic import BaseModel
from .utils import needs_pydanticv1
def test_pydanticv2(): def test_pydanticv2():
from pydantic import field_serializer from pydantic import field_serializer
@ -29,34 +26,3 @@ def test_pydanticv2():
with client: with client:
response = client.get("/model") response = client.get("/model")
assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"} assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_pydanticv1():
from pydantic import v1
class ModelWithDatetimeField(v1.BaseModel):
dt_field: datetime
class Config:
json_encoders = {
datetime: lambda dt: dt.replace(
microsecond=0, tzinfo=timezone.utc
).isoformat()
}
app = FastAPI()
model = ModelWithDatetimeField(dt_field=datetime(2019, 1, 1, 8))
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
@app.get("/model", response_model=ModelWithDatetimeField)
def get_model():
return model
client = TestClient(app)
with client:
response = client.get("/model")
assert response.json() == {"dt_field": "2019-01-01T08:00:00+00:00"}

0
tests/test_filter_pydantic_sub_model/__init__.py

45
tests/test_filter_pydantic_sub_model/app_pv1.py

@ -1,45 +0,0 @@
import warnings
from typing import Optional
from fastapi import Depends, FastAPI
from pydantic.v1 import BaseModel, validator
app = FastAPI()
class ModelB(BaseModel):
username: str
class ModelC(ModelB):
password: str
class ModelA(BaseModel):
name: str
description: Optional[str] = None
model_b: ModelB
tags: dict[str, str] = {}
@validator("name")
def lower_username(cls, name: str, values):
if not name.endswith("A"):
raise ValueError("name must end in A")
return name
async def get_model_c() -> ModelC:
return ModelC(username="test-user", password="test-password")
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
@app.get("/model/{name}", response_model=ModelA)
async def get_model_a(name: str, model_c=Depends(get_model_c)):
return {
"name": name,
"description": "model-a-desc",
"model_b": model_c,
"tags": {"key1": "value1", "key2": "value2"},
}

146
tests/test_filter_pydantic_sub_model/test_filter_pydantic_sub_model_pv1.py

@ -1,146 +0,0 @@
import pytest
from fastapi.exceptions import ResponseValidationError
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from ..utils import needs_pydanticv1
@pytest.fixture(name="client")
def get_client():
from .app_pv1 import app
client = TestClient(app)
return client
@needs_pydanticv1
def test_filter_sub_model(client: TestClient):
response = client.get("/model/modelA")
assert response.status_code == 200, response.text
assert response.json() == {
"name": "modelA",
"description": "model-a-desc",
"model_b": {"username": "test-user"},
"tags": {"key1": "value1", "key2": "value2"},
}
@needs_pydanticv1
def test_validator_is_cloned(client: TestClient):
with pytest.raises(ResponseValidationError) as err:
client.get("/model/modelX")
assert err.value.errors() == [
{
"loc": ("response", "name"),
"msg": "name must end in A",
"type": "value_error",
}
]
@needs_pydanticv1
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": {
"/model/{name}": {
"get": {
"summary": "Get Model A",
"operationId": "get_model_a_model__name__get",
"parameters": [
{
"required": True,
"schema": {"title": "Name", "type": "string"},
"name": "name",
"in": "path",
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ModelA"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
}
},
"components": {
"schemas": {
"HTTPValidationError": {
"title": "HTTPValidationError",
"type": "object",
"properties": {
"detail": {
"title": "Detail",
"type": "array",
"items": {
"$ref": "#/components/schemas/ValidationError"
},
}
},
},
"ModelA": {
"title": "ModelA",
"required": ["name", "model_b"],
"type": "object",
"properties": {
"name": {"title": "Name", "type": "string"},
"description": {"title": "Description", "type": "string"},
"model_b": {"$ref": "#/components/schemas/ModelB"},
"tags": {
"additionalProperties": {"type": "string"},
"type": "object",
"title": "Tags",
"default": {},
},
},
},
"ModelB": {
"title": "ModelB",
"required": ["username"],
"type": "object",
"properties": {
"username": {"title": "Username", "type": "string"}
},
},
"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"},
},
},
}
},
}
)

47
tests/test_get_model_definitions_formfeed_escape.py

@ -1,25 +1,12 @@
import warnings
import pytest import pytest
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from inline_snapshot import snapshot from inline_snapshot import snapshot
from .utils import needs_pydanticv1
@pytest.fixture( @pytest.fixture(name="client")
name="client", def client_fixture() -> TestClient:
params=[ from pydantic import BaseModel
pytest.param("pydantic-v1", marks=needs_pydanticv1),
"pydantic-v2",
],
)
def client_fixture(request: pytest.FixtureRequest) -> TestClient:
if request.param == "pydantic-v1":
from pydantic.v1 import BaseModel
else:
from pydantic import BaseModel
class Address(BaseModel): class Address(BaseModel):
""" """
@ -38,28 +25,12 @@ def client_fixture(request: pytest.FixtureRequest) -> TestClient:
app = FastAPI() app = FastAPI()
if request.param == "pydantic-v1": @app.get("/facilities/{facility_id}")
with warnings.catch_warnings(record=True): def get_facility(facility_id: str) -> Facility:
warnings.simplefilter("always") return Facility(
id=facility_id,
@app.get("/facilities/{facility_id}") address=Address(line_1="123 Main St", city="Anytown", state_province="CA"),
def get_facility(facility_id: str) -> Facility: )
return Facility(
id=facility_id,
address=Address(
line_1="123 Main St", city="Anytown", state_province="CA"
),
)
else:
@app.get("/facilities/{facility_id}")
def get_facility(facility_id: str) -> Facility:
return Facility(
id=facility_id,
address=Address(
line_1="123 Main St", city="Anytown", state_province="CA"
),
)
client = TestClient(app) client = TestClient(app)
return client return client

439
tests/test_pydantic_v1_v2_01.py

@ -1,439 +0,0 @@
import sys
import warnings
from typing import Any, Union
from tests.utils import skip_module_if_py_gte_314
if sys.version_info >= (3, 14):
skip_module_if_py_gte_314()
from fastapi import FastAPI
from fastapi._compat.v1 import BaseModel
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
class SubItem(BaseModel):
name: str
class Item(BaseModel):
title: str
size: int
description: Union[str, None] = None
sub: SubItem
multi: list[SubItem] = []
app = FastAPI()
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
@app.post("/simple-model")
def handle_simple_model(data: SubItem) -> SubItem:
return data
@app.post("/simple-model-filter", response_model=SubItem)
def handle_simple_model_filter(data: SubItem) -> Any:
extended_data = data.dict()
extended_data.update({"secret_price": 42})
return extended_data
@app.post("/item")
def handle_item(data: Item) -> Item:
return data
@app.post("/item-filter", response_model=Item)
def handle_item_filter(data: Item) -> Any:
extended_data = data.dict()
extended_data.update({"secret_data": "classified", "internal_id": 12345})
extended_data["sub"].update({"internal_id": 67890})
return extended_data
client = TestClient(app)
def test_old_simple_model():
response = client.post(
"/simple-model",
json={"name": "Foo"},
)
assert response.status_code == 200, response.text
assert response.json() == {"name": "Foo"}
def test_old_simple_model_validation_error():
response = client.post(
"/simple-model",
json={"wrong_name": "Foo"},
)
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"loc": ["body", "name"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
def test_old_simple_model_filter():
response = client.post(
"/simple-model-filter",
json={"name": "Foo"},
)
assert response.status_code == 200, response.text
assert response.json() == {"name": "Foo"}
def test_item_model():
response = client.post(
"/item",
json={
"title": "Test Item",
"size": 100,
"description": "This is a test item",
"sub": {"name": "SubItem1"},
"multi": [{"name": "Multi1"}, {"name": "Multi2"}],
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"title": "Test Item",
"size": 100,
"description": "This is a test item",
"sub": {"name": "SubItem1"},
"multi": [{"name": "Multi1"}, {"name": "Multi2"}],
}
def test_item_model_minimal():
response = client.post(
"/item",
json={"title": "Minimal Item", "size": 50, "sub": {"name": "SubMin"}},
)
assert response.status_code == 200, response.text
assert response.json() == {
"title": "Minimal Item",
"size": 50,
"description": None,
"sub": {"name": "SubMin"},
"multi": [],
}
def test_item_model_validation_errors():
response = client.post(
"/item",
json={"title": "Missing fields"},
)
assert response.status_code == 422, response.text
error_detail = response.json()["detail"]
assert len(error_detail) == 2
assert {
"loc": ["body", "size"],
"msg": "field required",
"type": "value_error.missing",
} in error_detail
assert {
"loc": ["body", "sub"],
"msg": "field required",
"type": "value_error.missing",
} in error_detail
def test_item_model_nested_validation_error():
response = client.post(
"/item",
json={"title": "Test Item", "size": 100, "sub": {"wrong_field": "test"}},
)
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"loc": ["body", "sub", "name"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
def test_item_model_invalid_type():
response = client.post(
"/item",
json={"title": "Test Item", "size": "not_a_number", "sub": {"name": "SubItem"}},
)
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"loc": ["body", "size"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
def test_item_filter():
response = client.post(
"/item-filter",
json={
"title": "Filtered Item",
"size": 200,
"description": "Test filtering",
"sub": {"name": "SubFiltered"},
"multi": [],
},
)
assert response.status_code == 200, response.text
result = response.json()
assert result == {
"title": "Filtered Item",
"size": 200,
"description": "Test filtering",
"sub": {"name": "SubFiltered"},
"multi": [],
}
assert "secret_data" not in result
assert "internal_id" not in result
def test_openapi_schema():
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": {
"/simple-model": {
"post": {
"summary": "Handle Simple Model",
"operationId": "handle_simple_model_simple_model_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{"$ref": "#/components/schemas/SubItem"}
],
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubItem"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/simple-model-filter": {
"post": {
"summary": "Handle Simple Model Filter",
"operationId": "handle_simple_model_filter_simple_model_filter_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{"$ref": "#/components/schemas/SubItem"}
],
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SubItem"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/item": {
"post": {
"summary": "Handle Item",
"operationId": "handle_item_item_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{"$ref": "#/components/schemas/Item"}
],
"title": "Data",
}
}
},
"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"
}
}
},
},
},
}
},
"/item-filter": {
"post": {
"summary": "Handle Item Filter",
"operationId": "handle_item_filter_item_filter_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{"$ref": "#/components/schemas/Item"}
],
"title": "Data",
}
}
},
"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": {
"title": {"type": "string", "title": "Title"},
"size": {"type": "integer", "title": "Size"},
"description": {"type": "string", "title": "Description"},
"sub": {"$ref": "#/components/schemas/SubItem"},
"multi": {
"items": {"$ref": "#/components/schemas/SubItem"},
"type": "array",
"title": "Multi",
"default": [],
},
},
"type": "object",
"required": ["title", "size", "sub"],
"title": "Item",
},
"SubItem": {
"properties": {"name": {"type": "string", "title": "Name"}},
"type": "object",
"required": ["name"],
"title": "SubItem",
},
"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",
},
}
},
}
)

682
tests/test_pydantic_v1_v2_list.py

@ -1,682 +0,0 @@
import sys
import warnings
from typing import Any, Union
from tests.utils import skip_module_if_py_gte_314
if sys.version_info >= (3, 14):
skip_module_if_py_gte_314()
from fastapi import FastAPI
from fastapi._compat.v1 import BaseModel
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
class SubItem(BaseModel):
name: str
class Item(BaseModel):
title: str
size: int
description: Union[str, None] = None
sub: SubItem
multi: list[SubItem] = []
app = FastAPI()
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
@app.post("/item")
def handle_item(data: Item) -> list[Item]:
return [data, data]
@app.post("/item-filter", response_model=list[Item])
def handle_item_filter(data: Item) -> Any:
extended_data = data.dict()
extended_data.update({"secret_data": "classified", "internal_id": 12345})
extended_data["sub"].update({"internal_id": 67890})
return [extended_data, extended_data]
@app.post("/item-list")
def handle_item_list(data: list[Item]) -> Item:
if data:
return data[0]
return Item(title="", size=0, sub=SubItem(name=""))
@app.post("/item-list-filter", response_model=Item)
def handle_item_list_filter(data: list[Item]) -> Any:
if data:
extended_data = data[0].dict()
extended_data.update({"secret_data": "classified", "internal_id": 12345})
extended_data["sub"].update({"internal_id": 67890})
return extended_data
return Item(title="", size=0, sub=SubItem(name=""))
@app.post("/item-list-to-list")
def handle_item_list_to_list(data: list[Item]) -> list[Item]:
return data
@app.post("/item-list-to-list-filter", response_model=list[Item])
def handle_item_list_to_list_filter(data: list[Item]) -> Any:
if data:
extended_data = data[0].dict()
extended_data.update({"secret_data": "classified", "internal_id": 12345})
extended_data["sub"].update({"internal_id": 67890})
return [extended_data, extended_data]
return []
client = TestClient(app)
def test_item_to_list():
response = client.post(
"/item",
json={
"title": "Test Item",
"size": 100,
"description": "This is a test item",
"sub": {"name": "SubItem1"},
"multi": [{"name": "Multi1"}, {"name": "Multi2"}],
},
)
assert response.status_code == 200, response.text
result = response.json()
assert isinstance(result, list)
assert len(result) == 2
for item in result:
assert item == {
"title": "Test Item",
"size": 100,
"description": "This is a test item",
"sub": {"name": "SubItem1"},
"multi": [{"name": "Multi1"}, {"name": "Multi2"}],
}
def test_item_to_list_filter():
response = client.post(
"/item-filter",
json={
"title": "Filtered Item",
"size": 200,
"description": "Test filtering",
"sub": {"name": "SubFiltered"},
"multi": [],
},
)
assert response.status_code == 200, response.text
result = response.json()
assert isinstance(result, list)
assert len(result) == 2
for item in result:
assert item == {
"title": "Filtered Item",
"size": 200,
"description": "Test filtering",
"sub": {"name": "SubFiltered"},
"multi": [],
}
# Verify secret fields are filtered out
assert "secret_data" not in item
assert "internal_id" not in item
assert "internal_id" not in item["sub"]
def test_list_to_item():
response = client.post(
"/item-list",
json=[
{"title": "First Item", "size": 50, "sub": {"name": "First Sub"}},
{"title": "Second Item", "size": 75, "sub": {"name": "Second Sub"}},
],
)
assert response.status_code == 200, response.text
assert response.json() == {
"title": "First Item",
"size": 50,
"description": None,
"sub": {"name": "First Sub"},
"multi": [],
}
def test_list_to_item_empty():
response = client.post(
"/item-list",
json=[],
)
assert response.status_code == 200, response.text
assert response.json() == {
"title": "",
"size": 0,
"description": None,
"sub": {"name": ""},
"multi": [],
}
def test_list_to_item_filter():
response = client.post(
"/item-list-filter",
json=[
{
"title": "First Item",
"size": 100,
"sub": {"name": "First Sub"},
"multi": [{"name": "Multi1"}],
},
{"title": "Second Item", "size": 200, "sub": {"name": "Second Sub"}},
],
)
assert response.status_code == 200, response.text
result = response.json()
assert result == {
"title": "First Item",
"size": 100,
"description": None,
"sub": {"name": "First Sub"},
"multi": [{"name": "Multi1"}],
}
# Verify secret fields are filtered out
assert "secret_data" not in result
assert "internal_id" not in result
def test_list_to_item_filter_no_data():
response = client.post("/item-list-filter", json=[])
assert response.status_code == 200, response.text
assert response.json() == {
"title": "",
"size": 0,
"description": None,
"sub": {"name": ""},
"multi": [],
}
def test_list_to_list():
input_items = [
{"title": "Item 1", "size": 10, "sub": {"name": "Sub1"}},
{
"title": "Item 2",
"size": 20,
"description": "Second item",
"sub": {"name": "Sub2"},
"multi": [{"name": "M1"}, {"name": "M2"}],
},
{"title": "Item 3", "size": 30, "sub": {"name": "Sub3"}},
]
response = client.post(
"/item-list-to-list",
json=input_items,
)
assert response.status_code == 200, response.text
result = response.json()
assert isinstance(result, list)
assert len(result) == 3
assert result[0] == {
"title": "Item 1",
"size": 10,
"description": None,
"sub": {"name": "Sub1"},
"multi": [],
}
assert result[1] == {
"title": "Item 2",
"size": 20,
"description": "Second item",
"sub": {"name": "Sub2"},
"multi": [{"name": "M1"}, {"name": "M2"}],
}
assert result[2] == {
"title": "Item 3",
"size": 30,
"description": None,
"sub": {"name": "Sub3"},
"multi": [],
}
def test_list_to_list_filter():
response = client.post(
"/item-list-to-list-filter",
json=[{"title": "Item 1", "size": 100, "sub": {"name": "Sub1"}}],
)
assert response.status_code == 200, response.text
result = response.json()
assert isinstance(result, list)
assert len(result) == 2
for item in result:
assert item == {
"title": "Item 1",
"size": 100,
"description": None,
"sub": {"name": "Sub1"},
"multi": [],
}
# Verify secret fields are filtered out
assert "secret_data" not in item
assert "internal_id" not in item
def test_list_to_list_filter_no_data():
response = client.post(
"/item-list-to-list-filter",
json=[],
)
assert response.status_code == 200, response.text
assert response.json() == []
def test_list_validation_error():
response = client.post(
"/item-list",
json=[
{"title": "Valid Item", "size": 100, "sub": {"name": "Sub1"}},
{
"title": "Invalid Item"
# Missing required fields: size and sub
},
],
)
assert response.status_code == 422, response.text
error_detail = response.json()["detail"]
assert len(error_detail) == 2
assert {
"loc": ["body", 1, "size"],
"msg": "field required",
"type": "value_error.missing",
} in error_detail
assert {
"loc": ["body", 1, "sub"],
"msg": "field required",
"type": "value_error.missing",
} in error_detail
def test_list_nested_validation_error():
response = client.post(
"/item-list",
json=[
{"title": "Item with bad sub", "size": 100, "sub": {"wrong_field": "value"}}
],
)
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"loc": ["body", 0, "sub", "name"],
"msg": "field required",
"type": "value_error.missing",
}
]
}
)
def test_list_type_validation_error():
response = client.post(
"/item-list",
json=[{"title": "Item", "size": "not_a_number", "sub": {"name": "Sub"}}],
)
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"loc": ["body", 0, "size"],
"msg": "value is not a valid integer",
"type": "type_error.integer",
}
]
}
)
def test_invalid_list_structure():
response = client.post(
"/item-list",
json={"title": "Not a list", "size": 100, "sub": {"name": "Sub"}},
)
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"loc": ["body"],
"msg": "value is not a valid list",
"type": "type_error.list",
}
]
}
)
def test_openapi_schema():
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": {
"/item": {
"post": {
"summary": "Handle Item",
"operationId": "handle_item_item_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{"$ref": "#/components/schemas/Item"}
],
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/Item"
},
"type": "array",
"title": "Response Handle Item Item Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/item-filter": {
"post": {
"summary": "Handle Item Filter",
"operationId": "handle_item_filter_item_filter_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{"$ref": "#/components/schemas/Item"}
],
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/Item"
},
"type": "array",
"title": "Response Handle Item Filter Item Filter Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/item-list": {
"post": {
"summary": "Handle Item List",
"operationId": "handle_item_list_item_list_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"items": {"$ref": "#/components/schemas/Item"},
"type": "array",
"title": "Data",
}
}
},
"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"
}
}
},
},
},
}
},
"/item-list-filter": {
"post": {
"summary": "Handle Item List Filter",
"operationId": "handle_item_list_filter_item_list_filter_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"items": {"$ref": "#/components/schemas/Item"},
"type": "array",
"title": "Data",
}
}
},
"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"
}
}
},
},
},
}
},
"/item-list-to-list": {
"post": {
"summary": "Handle Item List To List",
"operationId": "handle_item_list_to_list_item_list_to_list_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"items": {"$ref": "#/components/schemas/Item"},
"type": "array",
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/Item"
},
"type": "array",
"title": "Response Handle Item List To List Item List To List Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/item-list-to-list-filter": {
"post": {
"summary": "Handle Item List To List Filter",
"operationId": "handle_item_list_to_list_filter_item_list_to_list_filter_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"items": {"$ref": "#/components/schemas/Item"},
"type": "array",
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/Item"
},
"type": "array",
"title": "Response Handle Item List To List Filter Item List To List Filter Post",
}
}
},
},
"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": {
"title": {"type": "string", "title": "Title"},
"size": {"type": "integer", "title": "Size"},
"description": {"type": "string", "title": "Description"},
"sub": {"$ref": "#/components/schemas/SubItem"},
"multi": {
"items": {"$ref": "#/components/schemas/SubItem"},
"type": "array",
"title": "Multi",
"default": [],
},
},
"type": "object",
"required": ["title", "size", "sub"],
"title": "Item",
},
"SubItem": {
"properties": {"name": {"type": "string", "title": "Name"}},
"type": "object",
"required": ["name"],
"title": "SubItem",
},
"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",
},
}
},
}
)

1408
tests/test_pydantic_v1_v2_mixed.py

File diff suppressed because it is too large

0
tests/test_pydantic_v1_v2_multifile/__init__.py

137
tests/test_pydantic_v1_v2_multifile/main.py

@ -1,137 +0,0 @@
import warnings
from fastapi import FastAPI
from . import modelsv1, modelsv2, modelsv2b
app = FastAPI()
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
@app.post("/v1-to-v2/item")
def handle_v1_item_to_v2(data: modelsv1.Item) -> modelsv2.Item:
return modelsv2.Item(
new_title=data.title,
new_size=data.size,
new_description=data.description,
new_sub=modelsv2.SubItem(new_sub_name=data.sub.name),
new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi],
)
@app.post("/v2-to-v1/item")
def handle_v2_item_to_v1(data: modelsv2.Item) -> modelsv1.Item:
return modelsv1.Item(
title=data.new_title,
size=data.new_size,
description=data.new_description,
sub=modelsv1.SubItem(name=data.new_sub.new_sub_name),
multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi],
)
@app.post("/v1-to-v2/item-to-list")
def handle_v1_item_to_v2_list(data: modelsv1.Item) -> list[modelsv2.Item]:
converted = modelsv2.Item(
new_title=data.title,
new_size=data.size,
new_description=data.description,
new_sub=modelsv2.SubItem(new_sub_name=data.sub.name),
new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in data.multi],
)
return [converted, converted]
@app.post("/v1-to-v2/list-to-list")
def handle_v1_list_to_v2_list(data: list[modelsv1.Item]) -> list[modelsv2.Item]:
result = []
for item in data:
result.append(
modelsv2.Item(
new_title=item.title,
new_size=item.size,
new_description=item.description,
new_sub=modelsv2.SubItem(new_sub_name=item.sub.name),
new_multi=[
modelsv2.SubItem(new_sub_name=s.name) for s in item.multi
],
)
)
return result
@app.post("/v1-to-v2/list-to-item")
def handle_v1_list_to_v2_item(data: list[modelsv1.Item]) -> modelsv2.Item:
if data:
item = data[0]
return modelsv2.Item(
new_title=item.title,
new_size=item.size,
new_description=item.description,
new_sub=modelsv2.SubItem(new_sub_name=item.sub.name),
new_multi=[modelsv2.SubItem(new_sub_name=s.name) for s in item.multi],
)
return modelsv2.Item(
new_title="", new_size=0, new_sub=modelsv2.SubItem(new_sub_name="")
)
@app.post("/v2-to-v1/item-to-list")
def handle_v2_item_to_v1_list(data: modelsv2.Item) -> list[modelsv1.Item]:
converted = modelsv1.Item(
title=data.new_title,
size=data.new_size,
description=data.new_description,
sub=modelsv1.SubItem(name=data.new_sub.new_sub_name),
multi=[modelsv1.SubItem(name=s.new_sub_name) for s in data.new_multi],
)
return [converted, converted]
@app.post("/v2-to-v1/list-to-list")
def handle_v2_list_to_v1_list(data: list[modelsv2.Item]) -> list[modelsv1.Item]:
result = []
for item in data:
result.append(
modelsv1.Item(
title=item.new_title,
size=item.new_size,
description=item.new_description,
sub=modelsv1.SubItem(name=item.new_sub.new_sub_name),
multi=[
modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi
],
)
)
return result
@app.post("/v2-to-v1/list-to-item")
def handle_v2_list_to_v1_item(data: list[modelsv2.Item]) -> modelsv1.Item:
if data:
item = data[0]
return modelsv1.Item(
title=item.new_title,
size=item.new_size,
description=item.new_description,
sub=modelsv1.SubItem(name=item.new_sub.new_sub_name),
multi=[modelsv1.SubItem(name=s.new_sub_name) for s in item.new_multi],
)
return modelsv1.Item(title="", size=0, sub=modelsv1.SubItem(name=""))
@app.post("/v2-to-v1/same-name")
def handle_v2_same_name_to_v1(
item1: modelsv2.Item, item2: modelsv2b.Item
) -> modelsv1.Item:
return modelsv1.Item(
title=item1.new_title,
size=item2.dup_size,
description=item1.new_description,
sub=modelsv1.SubItem(name=item1.new_sub.new_sub_name),
multi=[modelsv1.SubItem(name=s.dup_sub_name) for s in item2.dup_multi],
)
@app.post("/v2-to-v1/list-of-items-to-list-of-items")
def handle_v2_items_in_list_to_v1_item_in_list(
data1: list[modelsv2.ItemInList], data2: list[modelsv2b.ItemInList]
) -> list[modelsv1.ItemInList]:
item1 = data1[0]
item2 = data2[0]
return [
modelsv1.ItemInList(name1=item1.name2),
modelsv1.ItemInList(name1=item2.dup_name2),
]

19
tests/test_pydantic_v1_v2_multifile/modelsv1.py

@ -1,19 +0,0 @@
from typing import Union
from fastapi._compat.v1 import BaseModel
class SubItem(BaseModel):
name: str
class Item(BaseModel):
title: str
size: int
description: Union[str, None] = None
sub: SubItem
multi: list[SubItem] = []
class ItemInList(BaseModel):
name1: str

19
tests/test_pydantic_v1_v2_multifile/modelsv2.py

@ -1,19 +0,0 @@
from typing import Union
from pydantic import BaseModel
class SubItem(BaseModel):
new_sub_name: str
class Item(BaseModel):
new_title: str
new_size: int
new_description: Union[str, None] = None
new_sub: SubItem
new_multi: list[SubItem] = []
class ItemInList(BaseModel):
name2: str

19
tests/test_pydantic_v1_v2_multifile/modelsv2b.py

@ -1,19 +0,0 @@
from typing import Union
from pydantic import BaseModel
class SubItem(BaseModel):
dup_sub_name: str
class Item(BaseModel):
dup_title: str
dup_size: int
dup_description: Union[str, None] = None
dup_sub: SubItem
dup_multi: list[SubItem] = []
class ItemInList(BaseModel):
dup_name2: str

951
tests/test_pydantic_v1_v2_multifile/test_multifile.py

@ -1,951 +0,0 @@
import sys
from tests.utils import skip_module_if_py_gte_314
if sys.version_info >= (3, 14):
skip_module_if_py_gte_314()
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from .main import app
client = TestClient(app)
def test_v1_to_v2_item():
response = client.post(
"/v1-to-v2/item",
json={"title": "Test", "size": 10, "sub": {"name": "SubTest"}},
)
assert response.status_code == 200
assert response.json() == {
"new_title": "Test",
"new_size": 10,
"new_description": None,
"new_sub": {"new_sub_name": "SubTest"},
"new_multi": [],
}
def test_v2_to_v1_item():
response = client.post(
"/v2-to-v1/item",
json={
"new_title": "NewTest",
"new_size": 20,
"new_sub": {"new_sub_name": "NewSubTest"},
},
)
assert response.status_code == 200
assert response.json() == {
"title": "NewTest",
"size": 20,
"description": None,
"sub": {"name": "NewSubTest"},
"multi": [],
}
def test_v1_to_v2_item_to_list():
response = client.post(
"/v1-to-v2/item-to-list",
json={"title": "ListTest", "size": 30, "sub": {"name": "SubListTest"}},
)
assert response.status_code == 200
assert response.json() == [
{
"new_title": "ListTest",
"new_size": 30,
"new_description": None,
"new_sub": {"new_sub_name": "SubListTest"},
"new_multi": [],
},
{
"new_title": "ListTest",
"new_size": 30,
"new_description": None,
"new_sub": {"new_sub_name": "SubListTest"},
"new_multi": [],
},
]
def test_v1_to_v2_list_to_list():
response = client.post(
"/v1-to-v2/list-to-list",
json=[
{"title": "Item1", "size": 40, "sub": {"name": "Sub1"}},
{"title": "Item2", "size": 50, "sub": {"name": "Sub2"}},
],
)
assert response.status_code == 200
assert response.json() == [
{
"new_title": "Item1",
"new_size": 40,
"new_description": None,
"new_sub": {"new_sub_name": "Sub1"},
"new_multi": [],
},
{
"new_title": "Item2",
"new_size": 50,
"new_description": None,
"new_sub": {"new_sub_name": "Sub2"},
"new_multi": [],
},
]
def test_v1_to_v2_list_to_item():
response = client.post(
"/v1-to-v2/list-to-item",
json=[
{"title": "FirstItem", "size": 60, "sub": {"name": "FirstSub"}},
{"title": "SecondItem", "size": 70, "sub": {"name": "SecondSub"}},
],
)
assert response.status_code == 200
assert response.json() == {
"new_title": "FirstItem",
"new_size": 60,
"new_description": None,
"new_sub": {"new_sub_name": "FirstSub"},
"new_multi": [],
}
def test_v2_to_v1_item_to_list():
response = client.post(
"/v2-to-v1/item-to-list",
json={
"new_title": "ListNew",
"new_size": 80,
"new_sub": {"new_sub_name": "SubListNew"},
},
)
assert response.status_code == 200
assert response.json() == [
{
"title": "ListNew",
"size": 80,
"description": None,
"sub": {"name": "SubListNew"},
"multi": [],
},
{
"title": "ListNew",
"size": 80,
"description": None,
"sub": {"name": "SubListNew"},
"multi": [],
},
]
def test_v2_to_v1_list_to_list():
response = client.post(
"/v2-to-v1/list-to-list",
json=[
{
"new_title": "New1",
"new_size": 90,
"new_sub": {"new_sub_name": "NewSub1"},
},
{
"new_title": "New2",
"new_size": 100,
"new_sub": {"new_sub_name": "NewSub2"},
},
],
)
assert response.status_code == 200
assert response.json() == [
{
"title": "New1",
"size": 90,
"description": None,
"sub": {"name": "NewSub1"},
"multi": [],
},
{
"title": "New2",
"size": 100,
"description": None,
"sub": {"name": "NewSub2"},
"multi": [],
},
]
def test_v2_to_v1_list_to_item():
response = client.post(
"/v2-to-v1/list-to-item",
json=[
{
"new_title": "FirstNew",
"new_size": 110,
"new_sub": {"new_sub_name": "FirstNewSub"},
},
{
"new_title": "SecondNew",
"new_size": 120,
"new_sub": {"new_sub_name": "SecondNewSub"},
},
],
)
assert response.status_code == 200
assert response.json() == {
"title": "FirstNew",
"size": 110,
"description": None,
"sub": {"name": "FirstNewSub"},
"multi": [],
}
def test_v1_to_v2_list_to_item_empty():
response = client.post("/v1-to-v2/list-to-item", json=[])
assert response.status_code == 200
assert response.json() == {
"new_title": "",
"new_size": 0,
"new_description": None,
"new_sub": {"new_sub_name": ""},
"new_multi": [],
}
def test_v2_to_v1_list_to_item_empty():
response = client.post("/v2-to-v1/list-to-item", json=[])
assert response.status_code == 200
assert response.json() == {
"title": "",
"size": 0,
"description": None,
"sub": {"name": ""},
"multi": [],
}
def test_v2_same_name_to_v1():
response = client.post(
"/v2-to-v1/same-name",
json={
"item1": {
"new_title": "Title1",
"new_size": 100,
"new_description": "Description1",
"new_sub": {"new_sub_name": "Sub1"},
"new_multi": [{"new_sub_name": "Multi1"}],
},
"item2": {
"dup_title": "Title2",
"dup_size": 200,
"dup_description": "Description2",
"dup_sub": {"dup_sub_name": "Sub2"},
"dup_multi": [
{"dup_sub_name": "Multi2a"},
{"dup_sub_name": "Multi2b"},
],
},
},
)
assert response.status_code == 200
assert response.json() == {
"title": "Title1",
"size": 200,
"description": "Description1",
"sub": {"name": "Sub1"},
"multi": [{"name": "Multi2a"}, {"name": "Multi2b"}],
}
def test_v2_items_in_list_to_v1_item_in_list():
response = client.post(
"/v2-to-v1/list-of-items-to-list-of-items",
json={
"data1": [{"name2": "Item1"}, {"name2": "Item2"}],
"data2": [{"dup_name2": "Item3"}, {"dup_name2": "Item4"}],
},
)
assert response.status_code == 200, response.text
assert response.json() == [
{"name1": "Item1"},
{"name1": "Item3"},
]
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == snapshot(
{
"openapi": "3.1.0",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/v1-to-v2/item": {
"post": {
"summary": "Handle V1 Item To V2",
"operationId": "handle_v1_item_to_v2_v1_to_v2_item_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
}
],
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v2-to-v1/item": {
"post": {
"summary": "Handle V2 Item To V1",
"operationId": "handle_v2_item_to_v1_v2_to_v1_item_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
},
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v1-to-v2/item-to-list": {
"post": {
"summary": "Handle V1 Item To V2 List",
"operationId": "handle_v1_item_to_v2_list_v1_to_v2_item_to_list_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
}
],
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
},
"type": "array",
"title": "Response Handle V1 Item To V2 List V1 To V2 Item To List Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v1-to-v2/list-to-list": {
"post": {
"summary": "Handle V1 List To V2 List",
"operationId": "handle_v1_list_to_v2_list_v1_to_v2_list_to_list_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
},
"type": "array",
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
},
"type": "array",
"title": "Response Handle V1 List To V2 List V1 To V2 List To List Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v1-to-v2/list-to-item": {
"post": {
"summary": "Handle V1 List To V2 Item",
"operationId": "handle_v1_list_to_v2_item_v1_to_v2_list_to_item_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
},
"type": "array",
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v2-to-v1/item-to-list": {
"post": {
"summary": "Handle V2 Item To V1 List",
"operationId": "handle_v2_item_to_v1_list_v2_to_v1_item_to_list_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
},
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
},
"type": "array",
"title": "Response Handle V2 Item To V1 List V2 To V1 Item To List Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v2-to-v1/list-to-list": {
"post": {
"summary": "Handle V2 List To V1 List",
"operationId": "handle_v2_list_to_v1_list_v2_to_v1_list_to_list_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
},
"type": "array",
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
},
"type": "array",
"title": "Response Handle V2 List To V1 List V2 To V1 List To List Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v2-to-v1/list-to-item": {
"post": {
"summary": "Handle V2 List To V1 Item",
"operationId": "handle_v2_list_to_v1_item_v2_to_v1_list_to_item_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
},
"type": "array",
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v2-to-v1/same-name": {
"post": {
"summary": "Handle V2 Same Name To V1",
"operationId": "handle_v2_same_name_to_v1_v2_to_v1_same_name_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post"
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__Item"
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v2-to-v1/list-of-items-to-list-of-items": {
"post": {
"summary": "Handle V2 Items In List To V1 Item In List",
"operationId": "handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post"
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList"
},
"type": "array",
"title": "Response Handle V2 Items In List To V1 Item In List V2 To V1 List Of Items To List Of Items Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
},
"components": {
"schemas": {
"Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post": {
"properties": {
"data1": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList"
},
"type": "array",
"title": "Data1",
},
"data2": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList"
},
"type": "array",
"title": "Data2",
},
},
"type": "object",
"required": ["data1", "data2"],
"title": "Body_handle_v2_items_in_list_to_v1_item_in_list_v2_to_v1_list_of_items_to_list_of_items_post",
},
"Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post": {
"properties": {
"item1": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input"
},
"item2": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__Item"
},
},
"type": "object",
"required": ["item1", "item2"],
"title": "Body_handle_v2_same_name_to_v1_v2_to_v1_same_name_post",
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"type": "array",
"title": "Detail",
}
},
"type": "object",
"title": "HTTPValidationError",
},
"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",
},
"tests__test_pydantic_v1_v2_multifile__modelsv1__Item": {
"properties": {
"title": {"type": "string", "title": "Title"},
"size": {"type": "integer", "title": "Size"},
"description": {
"type": "string",
"title": "Description",
},
"sub": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
},
"multi": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem"
},
"type": "array",
"title": "Multi",
"default": [],
},
},
"type": "object",
"required": ["title", "size", "sub"],
"title": "Item",
},
"tests__test_pydantic_v1_v2_multifile__modelsv1__ItemInList": {
"properties": {"name1": {"type": "string", "title": "Name1"}},
"type": "object",
"required": ["name1"],
"title": "ItemInList",
},
"tests__test_pydantic_v1_v2_multifile__modelsv1__SubItem": {
"properties": {"name": {"type": "string", "title": "Name"}},
"type": "object",
"required": ["name"],
"title": "SubItem",
},
"tests__test_pydantic_v1_v2_multifile__modelsv2__Item": {
"properties": {
"new_title": {
"type": "string",
"title": "New Title",
},
"new_size": {
"type": "integer",
"title": "New Size",
},
"new_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "New Description",
},
"new_sub": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
},
"new_multi": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
},
"type": "array",
"title": "New Multi",
"default": [],
},
},
"type": "object",
"required": ["new_title", "new_size", "new_sub"],
"title": "Item",
},
"tests__test_pydantic_v1_v2_multifile__modelsv2__Item-Input": {
"properties": {
"new_title": {
"type": "string",
"title": "New Title",
},
"new_size": {
"type": "integer",
"title": "New Size",
},
"new_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "New Description",
},
"new_sub": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
},
"new_multi": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem"
},
"type": "array",
"title": "New Multi",
"default": [],
},
},
"type": "object",
"required": ["new_title", "new_size", "new_sub"],
"title": "Item",
},
"tests__test_pydantic_v1_v2_multifile__modelsv2__ItemInList": {
"properties": {"name2": {"type": "string", "title": "Name2"}},
"type": "object",
"required": ["name2"],
"title": "ItemInList",
},
"tests__test_pydantic_v1_v2_multifile__modelsv2__SubItem": {
"properties": {
"new_sub_name": {
"type": "string",
"title": "New Sub Name",
}
},
"type": "object",
"required": ["new_sub_name"],
"title": "SubItem",
},
"tests__test_pydantic_v1_v2_multifile__modelsv2b__Item": {
"properties": {
"dup_title": {
"type": "string",
"title": "Dup Title",
},
"dup_size": {
"type": "integer",
"title": "Dup Size",
},
"dup_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "Dup Description",
},
"dup_sub": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
},
"dup_multi": {
"items": {
"$ref": "#/components/schemas/tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem"
},
"type": "array",
"title": "Dup Multi",
"default": [],
},
},
"type": "object",
"required": ["dup_title", "dup_size", "dup_sub"],
"title": "Item",
},
"tests__test_pydantic_v1_v2_multifile__modelsv2b__ItemInList": {
"properties": {
"dup_name2": {
"type": "string",
"title": "Dup Name2",
}
},
"type": "object",
"required": ["dup_name2"],
"title": "ItemInList",
},
"tests__test_pydantic_v1_v2_multifile__modelsv2b__SubItem": {
"properties": {
"dup_sub_name": {
"type": "string",
"title": "Dup Sub Name",
}
},
"type": "object",
"required": ["dup_sub_name"],
"title": "SubItem",
},
},
},
}
)

692
tests/test_pydantic_v1_v2_noneable.py

@ -1,692 +0,0 @@
import sys
import warnings
from typing import Any, Union
from tests.utils import skip_module_if_py_gte_314
if sys.version_info >= (3, 14):
skip_module_if_py_gte_314()
from fastapi import FastAPI
from fastapi._compat.v1 import BaseModel
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from pydantic import BaseModel as NewBaseModel
class SubItem(BaseModel):
name: str
class Item(BaseModel):
title: str
size: int
description: Union[str, None] = None
sub: SubItem
multi: list[SubItem] = []
class NewSubItem(NewBaseModel):
new_sub_name: str
class NewItem(NewBaseModel):
new_title: str
new_size: int
new_description: Union[str, None] = None
new_sub: NewSubItem
new_multi: list[NewSubItem] = []
app = FastAPI()
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
@app.post("/v1-to-v2/")
def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]:
if data.size < 0:
return None
return NewItem(
new_title=data.title,
new_size=data.size,
new_description=data.description,
new_sub=NewSubItem(new_sub_name=data.sub.name),
new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi],
)
@app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None])
def handle_v1_item_to_v2_filter(data: Item) -> Any:
if data.size < 0:
return None
result = {
"new_title": data.title,
"new_size": data.size,
"new_description": data.description,
"new_sub": {
"new_sub_name": data.sub.name,
"new_sub_secret": "sub_hidden",
},
"new_multi": [
{"new_sub_name": s.name, "new_sub_secret": "sub_hidden"}
for s in data.multi
],
"secret": "hidden_v1_to_v2",
}
return result
@app.post("/v2-to-v1/item")
def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]:
if data.new_size < 0:
return None
return Item(
title=data.new_title,
size=data.new_size,
description=data.new_description,
sub=SubItem(name=data.new_sub.new_sub_name),
multi=[SubItem(name=s.new_sub_name) for s in data.new_multi],
)
@app.post("/v2-to-v1/item-filter", response_model=Union[Item, None])
def handle_v2_item_to_v1_filter(data: NewItem) -> Any:
if data.new_size < 0:
return None
result = {
"title": data.new_title,
"size": data.new_size,
"description": data.new_description,
"sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"},
"multi": [
{"name": s.new_sub_name, "sub_secret": "sub_hidden"}
for s in data.new_multi
],
"secret": "hidden_v2_to_v1",
}
return result
client = TestClient(app)
def test_v1_to_v2_item_success():
response = client.post(
"/v1-to-v2/",
json={
"title": "Old Item",
"size": 100,
"description": "V1 description",
"sub": {"name": "V1 Sub"},
"multi": [{"name": "M1"}, {"name": "M2"}],
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"new_title": "Old Item",
"new_size": 100,
"new_description": "V1 description",
"new_sub": {"new_sub_name": "V1 Sub"},
"new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}],
}
def test_v1_to_v2_item_returns_none():
response = client.post(
"/v1-to-v2/",
json={"title": "Invalid Item", "size": -10, "sub": {"name": "Sub"}},
)
assert response.status_code == 200, response.text
assert response.json() is None
def test_v1_to_v2_item_minimal():
response = client.post(
"/v1-to-v2/", json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}}
)
assert response.status_code == 200, response.text
assert response.json() == {
"new_title": "Minimal",
"new_size": 50,
"new_description": None,
"new_sub": {"new_sub_name": "MinSub"},
"new_multi": [],
}
def test_v1_to_v2_item_filter_success():
response = client.post(
"/v1-to-v2/item-filter",
json={
"title": "Filtered Item",
"size": 50,
"sub": {"name": "Sub"},
"multi": [{"name": "Multi1"}],
},
)
assert response.status_code == 200, response.text
result = response.json()
assert result["new_title"] == "Filtered Item"
assert result["new_size"] == 50
assert result["new_sub"]["new_sub_name"] == "Sub"
assert result["new_multi"][0]["new_sub_name"] == "Multi1"
# Verify secret fields are filtered out
assert "secret" not in result
assert "new_sub_secret" not in result["new_sub"]
assert "new_sub_secret" not in result["new_multi"][0]
def test_v1_to_v2_item_filter_returns_none():
response = client.post(
"/v1-to-v2/item-filter",
json={"title": "Invalid", "size": -1, "sub": {"name": "Sub"}},
)
assert response.status_code == 200, response.text
assert response.json() is None
def test_v2_to_v1_item_success():
response = client.post(
"/v2-to-v1/item",
json={
"new_title": "New Item",
"new_size": 200,
"new_description": "V2 description",
"new_sub": {"new_sub_name": "V2 Sub"},
"new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}],
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"title": "New Item",
"size": 200,
"description": "V2 description",
"sub": {"name": "V2 Sub"},
"multi": [{"name": "N1"}, {"name": "N2"}],
}
def test_v2_to_v1_item_returns_none():
response = client.post(
"/v2-to-v1/item",
json={
"new_title": "Invalid New",
"new_size": -5,
"new_sub": {"new_sub_name": "NewSub"},
},
)
assert response.status_code == 200, response.text
assert response.json() is None
def test_v2_to_v1_item_minimal():
response = client.post(
"/v2-to-v1/item",
json={
"new_title": "MinimalNew",
"new_size": 75,
"new_sub": {"new_sub_name": "MinNewSub"},
},
)
assert response.status_code == 200, response.text
assert response.json() == {
"title": "MinimalNew",
"size": 75,
"description": None,
"sub": {"name": "MinNewSub"},
"multi": [],
}
def test_v2_to_v1_item_filter_success():
response = client.post(
"/v2-to-v1/item-filter",
json={
"new_title": "Filtered New",
"new_size": 75,
"new_sub": {"new_sub_name": "NewSub"},
"new_multi": [],
},
)
assert response.status_code == 200, response.text
result = response.json()
assert result["title"] == "Filtered New"
assert result["size"] == 75
assert result["sub"]["name"] == "NewSub"
# Verify secret fields are filtered out
assert "secret" not in result
assert "sub_secret" not in result["sub"]
def test_v2_to_v1_item_filter_returns_none():
response = client.post(
"/v2-to-v1/item-filter",
json={
"new_title": "Invalid Filtered",
"new_size": -100,
"new_sub": {"new_sub_name": "Sub"},
},
)
assert response.status_code == 200, response.text
assert response.json() is None
def test_v1_to_v2_validation_error():
response = client.post("/v1-to-v2/", json={"title": "Missing fields"})
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"loc": ["body", "size"],
"msg": "field required",
"type": "value_error.missing",
},
{
"loc": ["body", "sub"],
"msg": "field required",
"type": "value_error.missing",
},
]
}
)
def test_v1_to_v2_nested_validation_error():
response = client.post(
"/v1-to-v2/",
json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}},
)
assert response.status_code == 422, response.text
error_detail = response.json()["detail"]
assert len(error_detail) == 1
assert error_detail[0]["loc"] == ["body", "sub", "name"]
def test_v1_to_v2_type_validation_error():
response = client.post(
"/v1-to-v2/",
json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}},
)
assert response.status_code == 422, response.text
error_detail = response.json()["detail"]
assert len(error_detail) == 1
assert error_detail[0]["loc"] == ["body", "size"]
def test_v2_to_v1_validation_error():
response = client.post("/v2-to-v1/item", json={"new_title": "Missing fields"})
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"type": "missing",
"loc": ["body", "new_size"],
"msg": "Field required",
"input": {"new_title": "Missing fields"},
},
{
"type": "missing",
"loc": ["body", "new_sub"],
"msg": "Field required",
"input": {"new_title": "Missing fields"},
},
]
}
)
def test_v2_to_v1_nested_validation_error():
response = client.post(
"/v2-to-v1/item",
json={
"new_title": "Bad sub",
"new_size": 200,
"new_sub": {"wrong_field": "value"},
},
)
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"type": "missing",
"loc": ["body", "new_sub", "new_sub_name"],
"msg": "Field required",
"input": {"wrong_field": "value"},
}
]
}
)
def test_v2_to_v1_type_validation_error():
response = client.post(
"/v2-to-v1/item",
json={
"new_title": "Bad type",
"new_size": "not_a_number",
"new_sub": {"new_sub_name": "Sub"},
},
)
assert response.status_code == 422, response.text
assert response.json() == snapshot(
{
"detail": [
{
"type": "int_parsing",
"loc": ["body", "new_size"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "not_a_number",
}
]
}
)
def test_v1_to_v2_with_multi_items():
response = client.post(
"/v1-to-v2/",
json={
"title": "Complex Item",
"size": 300,
"description": "Item with multiple sub-items",
"sub": {"name": "Main Sub"},
"multi": [{"name": "Sub1"}, {"name": "Sub2"}, {"name": "Sub3"}],
},
)
assert response.status_code == 200, response.text
assert response.json() == snapshot(
{
"new_title": "Complex Item",
"new_size": 300,
"new_description": "Item with multiple sub-items",
"new_sub": {"new_sub_name": "Main Sub"},
"new_multi": [
{"new_sub_name": "Sub1"},
{"new_sub_name": "Sub2"},
{"new_sub_name": "Sub3"},
],
}
)
def test_v2_to_v1_with_multi_items():
response = client.post(
"/v2-to-v1/item",
json={
"new_title": "Complex New Item",
"new_size": 400,
"new_description": "New item with multiple sub-items",
"new_sub": {"new_sub_name": "Main New Sub"},
"new_multi": [{"new_sub_name": "NewSub1"}, {"new_sub_name": "NewSub2"}],
},
)
assert response.status_code == 200, response.text
assert response.json() == snapshot(
{
"title": "Complex New Item",
"size": 400,
"description": "New item with multiple sub-items",
"sub": {"name": "Main New Sub"},
"multi": [{"name": "NewSub1"}, {"name": "NewSub2"}],
}
)
def test_openapi_schema():
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": {
"/v1-to-v2/": {
"post": {
"summary": "Handle V1 Item To V2",
"operationId": "handle_v1_item_to_v2_v1_to_v2__post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{"$ref": "#/components/schemas/Item"}
],
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/NewItem"
},
{"type": "null"},
],
"title": "Response Handle V1 Item To V2 V1 To V2 Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v1-to-v2/item-filter": {
"post": {
"summary": "Handle V1 Item To V2 Filter",
"operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"allOf": [
{"$ref": "#/components/schemas/Item"}
],
"title": "Data",
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {
"anyOf": [
{
"$ref": "#/components/schemas/NewItem"
},
{"type": "null"},
],
"title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post",
}
}
},
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
},
},
}
},
"/v2-to-v1/item": {
"post": {
"summary": "Handle V2 Item To V1",
"operationId": "handle_v2_item_to_v1_v2_to_v1_item_post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/NewItem"}
}
},
"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"
}
}
},
},
},
}
},
"/v2-to-v1/item-filter": {
"post": {
"summary": "Handle V2 Item To V1 Filter",
"operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post",
"requestBody": {
"content": {
"application/json": {
"schema": {"$ref": "#/components/schemas/NewItem"}
}
},
"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": {
"title": {"type": "string", "title": "Title"},
"size": {"type": "integer", "title": "Size"},
"description": {"type": "string", "title": "Description"},
"sub": {"$ref": "#/components/schemas/SubItem"},
"multi": {
"items": {"$ref": "#/components/schemas/SubItem"},
"type": "array",
"title": "Multi",
"default": [],
},
},
"type": "object",
"required": ["title", "size", "sub"],
"title": "Item",
},
"NewItem": {
"properties": {
"new_title": {"type": "string", "title": "New Title"},
"new_size": {"type": "integer", "title": "New Size"},
"new_description": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"title": "New Description",
},
"new_sub": {"$ref": "#/components/schemas/NewSubItem"},
"new_multi": {
"items": {"$ref": "#/components/schemas/NewSubItem"},
"type": "array",
"title": "New Multi",
"default": [],
},
},
"type": "object",
"required": ["new_title", "new_size", "new_sub"],
"title": "NewItem",
},
"NewSubItem": {
"properties": {
"new_sub_name": {"type": "string", "title": "New Sub Name"}
},
"type": "object",
"required": ["new_sub_name"],
"title": "NewSubItem",
},
"SubItem": {
"properties": {"name": {"type": "string", "title": "Name"}},
"type": "object",
"required": ["name"],
"title": "SubItem",
},
"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",
},
}
},
}
)

50
tests/test_read_with_orm_mode.py

@ -1,12 +1,9 @@
import warnings
from typing import Any from typing import Any
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict from pydantic import BaseModel, ConfigDict
from .utils import needs_pydanticv1
def test_read_with_orm_mode() -> None: def test_read_with_orm_mode() -> None:
class PersonBase(BaseModel): class PersonBase(BaseModel):
@ -44,50 +41,3 @@ def test_read_with_orm_mode() -> None:
assert data["name"] == person_data["name"] assert data["name"] == person_data["name"]
assert data["lastname"] == person_data["lastname"] assert data["lastname"] == person_data["lastname"]
assert data["full_name"] == person_data["name"] + " " + person_data["lastname"] assert data["full_name"] == person_data["name"] + " " + person_data["lastname"]
@needs_pydanticv1
def test_read_with_orm_mode_pv1() -> None:
from pydantic import v1
class PersonBase(v1.BaseModel):
name: str
lastname: str
class Person(PersonBase):
@property
def full_name(self) -> str:
return f"{self.name} {self.lastname}"
class Config:
orm_mode = True
read_with_orm_mode = True
class PersonCreate(PersonBase):
pass
class PersonRead(PersonBase):
full_name: str
class Config:
orm_mode = True
app = FastAPI()
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
@app.post("/people/", response_model=PersonRead)
def create_person(person: PersonCreate) -> Any:
db_person = Person.from_orm(person)
return db_person
client = TestClient(app)
person_data = {"name": "Dive", "lastname": "Wilson"}
response = client.post("/people/", json=person_data)
data = response.json()
assert response.status_code == 200, response.text
assert data["name"] == person_data["name"]
assert data["lastname"] == person_data["lastname"]
assert data["full_name"] == person_data["name"] + " " + person_data["lastname"]

26
tests/test_response_model_as_return_annotation.py

@ -1,4 +1,3 @@
import warnings
from typing import Union from typing import Union
import pytest import pytest
@ -8,8 +7,6 @@ from fastapi.responses import JSONResponse, Response
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from pydantic import BaseModel from pydantic import BaseModel
from tests.utils import needs_pydanticv1
class BaseUser(BaseModel): class BaseUser(BaseModel):
name: str name: str
@ -512,29 +509,6 @@ def test_invalid_response_model_field():
assert "parameter response_model=None" in e.value.args[0] assert "parameter response_model=None" in e.value.args[0]
# TODO: remove when dropping Pydantic v1 support
@needs_pydanticv1
def test_invalid_response_model_field_pv1():
from fastapi._compat import v1
app = FastAPI()
class Model(v1.BaseModel):
foo: str
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
with pytest.raises(FastAPIError) as e:
@app.get("/")
def read_root() -> Union[Response, Model, None]:
return Response(content="Foo") # pragma: no cover
assert "valid Pydantic field type" in e.value.args[0]
assert "parameter response_model=None" in e.value.args[0]
def test_openapi_schema(): def test_openapi_schema():
response = client.get("/openapi.json") response = client.get("/openapi.json")
assert response.status_code == 200, response.text assert response.status_code == 200, response.text

0
tests/test_tutorial/test_pydantic_v1_in_v2/__init__.py

31
tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial001.py

@ -1,31 +0,0 @@
import sys
from typing import Any
import pytest
from tests.utils import skip_module_if_py_gte_314
if sys.version_info >= (3, 14):
skip_module_if_py_gte_314()
import importlib
from ...utils import needs_py310
@pytest.fixture(
name="mod",
params=[
"tutorial001_an_py39",
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}

142
tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial002.py

@ -1,142 +0,0 @@
import sys
import warnings
import pytest
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()
import importlib
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial002_an_py39",
pytest.param("tutorial002_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
with warnings.catch_warnings(record=True):
warnings.filterwarnings(
"ignore",
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*",
category=DeprecationWarning,
)
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",
},
}
},
}
)

157
tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial003.py

@ -1,157 +0,0 @@
import sys
import warnings
import pytest
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()
import importlib
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
"tutorial003_an_py39",
pytest.param("tutorial003_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
with warnings.catch_warnings(record=True):
warnings.filterwarnings(
"ignore",
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*",
category=DeprecationWarning,
)
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",
},
}
},
}
)

155
tests/test_tutorial/test_pydantic_v1_in_v2/test_tutorial004.py

@ -1,155 +0,0 @@
import sys
import warnings
import pytest
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()
import importlib
from fastapi.testclient import TestClient
from ...utils import needs_py310
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial004_an_py39"),
pytest.param("tutorial004_an_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
with warnings.catch_warnings(record=True):
warnings.filterwarnings(
"ignore",
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*",
category=DeprecationWarning,
)
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",
},
}
},
}
)

127
tests/test_tutorial/test_request_form_models/test_tutorial002_pv1.py

@ -1,127 +0,0 @@
import importlib
import warnings
import pytest
from fastapi.testclient import TestClient
from ...utils import needs_pydanticv1
@pytest.fixture(
name="client",
params=[
"tutorial002_pv1_py39",
"tutorial002_pv1_an_py39",
],
)
def get_client(request: pytest.FixtureRequest):
with warnings.catch_warnings(record=True):
warnings.filterwarnings(
"ignore",
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*",
category=DeprecationWarning,
)
mod = importlib.import_module(f"docs_src.request_form_models.{request.param}")
client = TestClient(mod.app)
return client
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_form(client: TestClient):
response = client.post("/login/", data={"username": "Foo", "password": "secret"})
assert response.status_code == 200
assert response.json() == {"username": "Foo", "password": "secret"}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_extra_form(client: TestClient):
response = client.post(
"/login/", data={"username": "Foo", "password": "secret", "extra": "extra"}
)
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "value_error.extra",
"loc": ["body", "extra"],
"msg": "extra fields not permitted",
}
]
}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_form_no_password(client: TestClient):
response = client.post("/login/", data={"username": "Foo"})
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "value_error.missing",
"loc": ["body", "password"],
"msg": "field required",
}
]
}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_form_no_username(client: TestClient):
response = client.post("/login/", data={"password": "secret"})
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "value_error.missing",
"loc": ["body", "username"],
"msg": "field required",
}
]
}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_form_no_data(client: TestClient):
response = client.post("/login/")
assert response.status_code == 422
assert response.json() == {
"detail": [
{
"type": "value_error.missing",
"loc": ["body", "username"],
"msg": "field required",
},
{
"type": "value_error.missing",
"loc": ["body", "password"],
"msg": "field required",
},
]
}
# TODO: remove when deprecating Pydantic v1
@needs_pydanticv1
def test_post_body_json(client: TestClient):
response = client.post("/login/", json={"username": "Foo", "password": "secret"})
assert response.status_code == 422, response.text
assert response.json() == {
"detail": [
{
"type": "value_error.missing",
"loc": ["body", "username"],
"msg": "field required",
},
{
"type": "value_error.missing",
"loc": ["body", "password"],
"msg": "field required",
},
]
}

151
tests/test_tutorial/test_schema_extra_example/test_tutorial001_pv1.py

@ -1,151 +0,0 @@
import importlib
import warnings
import pytest
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from ...utils import needs_py310, needs_pydanticv1
@pytest.fixture(
name="client",
params=[
pytest.param("tutorial001_pv1_py39"),
pytest.param("tutorial001_pv1_py310", marks=needs_py310),
],
)
def get_client(request: pytest.FixtureRequest):
with warnings.catch_warnings(record=True):
warnings.filterwarnings(
"ignore",
message=r"pydantic\.v1 is deprecated and will soon stop being supported by FastAPI\..*",
category=DeprecationWarning,
)
mod = importlib.import_module(f"docs_src.schema_extra_example.{request.param}")
client = TestClient(mod.app)
return client
@needs_pydanticv1
def test_post_body_example(client: TestClient):
response = client.put(
"/items/5",
json={
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
)
assert response.status_code == 200
@needs_pydanticv1
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/{item_id}": {
"put": {
"summary": "Update Item",
"operationId": "update_item_items__item_id__put",
"parameters": [
{
"required": True,
"schema": {"type": "integer", "title": "Item Id"},
"name": "item_id",
"in": "path",
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"title": "Item",
"allOf": [
{"$ref": "#/components/schemas/Item"}
],
}
}
},
"required": True,
},
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
},
"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"},
"price": {"type": "number", "title": "Price"},
"tax": {"type": "number", "title": "Tax"},
},
"type": "object",
"required": ["name", "price"],
"title": "Item",
"examples": [
{
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
}
],
},
"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",
},
}
},
}
)
Loading…
Cancel
Save