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 fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
from .utils import needs_pydanticv1
def test_pydanticv2():
from pydantic import field_serializer
@ -29,34 +26,3 @@ def test_pydanticv2():
with client:
response = client.get("/model")
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
from fastapi import FastAPI
from fastapi.testclient import TestClient
from inline_snapshot import snapshot
from .utils import needs_pydanticv1
@pytest.fixture(
name="client",
params=[
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
@pytest.fixture(name="client")
def client_fixture() -> TestClient:
from pydantic import BaseModel
class Address(BaseModel):
"""
@ -38,28 +25,12 @@ def client_fixture(request: pytest.FixtureRequest) -> TestClient:
app = FastAPI()
if request.param == "pydantic-v1":
with warnings.catch_warnings(record=True):
warnings.simplefilter("always")
@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"
),
)
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"
),
)
@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)
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 fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, ConfigDict
from .utils import needs_pydanticv1
def test_read_with_orm_mode() -> None:
class PersonBase(BaseModel):
@ -44,50 +41,3 @@ def test_read_with_orm_mode() -> None:
assert data["name"] == person_data["name"]
assert data["lastname"] == 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
import pytest
@ -8,8 +7,6 @@ from fastapi.responses import JSONResponse, Response
from fastapi.testclient import TestClient
from pydantic import BaseModel
from tests.utils import needs_pydanticv1
class BaseUser(BaseModel):
name: str
@ -512,29 +509,6 @@ def test_invalid_response_model_field():
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():
response = client.get("/openapi.json")
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