25 changed files with 9 additions and 6527 deletions
File diff suppressed because it is too large
@ -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"}, |
|
||||
} |
|
||||
@ -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"}, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
) |
|
||||
@ -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", |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
) |
|
||||
@ -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", |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
) |
|
||||
File diff suppressed because it is too large
@ -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), |
|
||||
] |
|
||||
@ -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 |
|
||||
@ -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 |
|
||||
@ -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 |
|
||||
@ -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", |
|
||||
}, |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
) |
|
||||
@ -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", |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
) |
|
||||
@ -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} |
|
||||
@ -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", |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
) |
|
||||
@ -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", |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
) |
|
||||
@ -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", |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
) |
|
||||
@ -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", |
|
||||
}, |
|
||||
] |
|
||||
} |
|
||||
@ -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…
Reference in new issue