4 changed files with 554 additions and 19 deletions
@ -0,0 +1,214 @@ |
|||||
|
from collections.abc import Iterator |
||||
|
|
||||
|
import pytest |
||||
|
from fastapi import FastAPI |
||||
|
from fastapi._compat import PYDANTIC_VERSION_MINOR_TUPLE |
||||
|
from fastapi.responses import JSONResponse |
||||
|
from fastapi.testclient import TestClient |
||||
|
from pydantic import BaseModel |
||||
|
|
||||
|
|
||||
|
class User(BaseModel): |
||||
|
name: str |
||||
|
|
||||
|
|
||||
|
class UserLogin(User): |
||||
|
password: str |
||||
|
|
||||
|
|
||||
|
class OuterModel(BaseModel): |
||||
|
user: User |
||||
|
|
||||
|
|
||||
|
class Item(BaseModel): |
||||
|
name: str |
||||
|
description: str |
||||
|
|
||||
|
|
||||
|
class ItemWithPrice(Item): |
||||
|
price: float |
||||
|
|
||||
|
|
||||
|
requires_pydantic_v2_13 = pytest.mark.skipif( |
||||
|
PYDANTIC_VERSION_MINOR_TUPLE < (2, 13), |
||||
|
reason="polymorphic_serialization requires Pydantic >= 2.13", |
||||
|
) |
||||
|
|
||||
|
only_for_pydantic_below_v2_13 = pytest.mark.skipif( |
||||
|
PYDANTIC_VERSION_MINOR_TUPLE >= (2, 13), |
||||
|
reason="This test only applies to Pydantic < 2.13", |
||||
|
) |
||||
|
requires_pydantic_v2_13 = pytest.mark.skipif( |
||||
|
PYDANTIC_VERSION_MINOR_TUPLE < (2, 13), |
||||
|
reason="polymorphic_serialization requires Pydantic >= 2.13", |
||||
|
) |
||||
|
|
||||
|
only_for_pydantic_below_v2_13 = pytest.mark.skipif( |
||||
|
PYDANTIC_VERSION_MINOR_TUPLE >= (2, 13), |
||||
|
reason="This test only applies to Pydantic < 2.13", |
||||
|
) |
||||
|
|
||||
|
|
||||
|
def test_default_behavior_filters_subclass_fields(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.get("/user", response_model=OuterModel) |
||||
|
def get_user(): |
||||
|
return OuterModel(user=UserLogin(name="pydantic", password="password")) |
||||
|
|
||||
|
response = TestClient(app).get("/user") |
||||
|
|
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"user": {"name": "pydantic"}} |
||||
|
|
||||
|
|
||||
|
@requires_pydantic_v2_13 |
||||
|
def test_polymorphic_flag_includes_subclass_fields(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.get( |
||||
|
"/user", |
||||
|
response_model=OuterModel, |
||||
|
response_model_polymorphic_serialization=True, |
||||
|
) |
||||
|
def get_user(): |
||||
|
return OuterModel(user=UserLogin(name="pydantic", password="password")) |
||||
|
|
||||
|
response = TestClient(app).get("/user") |
||||
|
|
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"user": {"name": "pydantic", "password": "password"}} |
||||
|
|
||||
|
|
||||
|
@requires_pydantic_v2_13 |
||||
|
def test_polymorphic_with_inferred_response_model(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.get("/user", response_model_polymorphic_serialization=True) |
||||
|
def get_user() -> OuterModel: |
||||
|
return OuterModel(user=UserLogin(name="pydantic", password="password")) |
||||
|
|
||||
|
response = TestClient(app).get("/user") |
||||
|
|
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"user": {"name": "pydantic", "password": "password"}} |
||||
|
|
||||
|
|
||||
|
@requires_pydantic_v2_13 |
||||
|
def test_polymorphic_with_fast_json_path(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.post( |
||||
|
"/item", |
||||
|
response_model=Item, |
||||
|
response_model_polymorphic_serialization=True, |
||||
|
status_code=201, |
||||
|
) |
||||
|
def create_item() -> Item: |
||||
|
return ItemWithPrice(name="Widget", description="A useful widget", price=9.99) |
||||
|
|
||||
|
response = TestClient(app).post("/item") |
||||
|
|
||||
|
assert response.status_code == 201 |
||||
|
assert response.json() == { |
||||
|
"name": "Widget", |
||||
|
"description": "A useful widget", |
||||
|
"price": 9.99, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@requires_pydantic_v2_13 |
||||
|
def test_polymorphic_with_custom_response_class(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
class CustomJSONResponse(JSONResponse): |
||||
|
media_type = "application/json" |
||||
|
|
||||
|
@app.get( |
||||
|
"/user", |
||||
|
response_model=OuterModel, |
||||
|
response_model_polymorphic_serialization=True, |
||||
|
response_class=CustomJSONResponse, |
||||
|
) |
||||
|
def get_user(): |
||||
|
return OuterModel(user=UserLogin(name="pydantic", password="password")) |
||||
|
|
||||
|
response = TestClient(app).get("/user") |
||||
|
|
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"user": {"name": "pydantic", "password": "password"}} |
||||
|
|
||||
|
|
||||
|
@requires_pydantic_v2_13 |
||||
|
def test_polymorphic_composes_with_exclude(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.get( |
||||
|
"/user", |
||||
|
response_model=OuterModel, |
||||
|
response_model_polymorphic_serialization=True, |
||||
|
response_model_exclude={"user": {"password"}}, |
||||
|
) |
||||
|
def get_user(): |
||||
|
return OuterModel(user=UserLogin(name="pydantic", password="password")) |
||||
|
|
||||
|
response = TestClient(app).get("/user") |
||||
|
|
||||
|
assert response.status_code == 200 |
||||
|
assert response.json() == {"user": {"name": "pydantic"}} |
||||
|
|
||||
|
|
||||
|
def test_list_response_filters_subclass_fields_by_default(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
def generate_items() -> Iterator[Item]: |
||||
|
yield ItemWithPrice(name="Item1", description="First item", price=10.0) |
||||
|
|
||||
|
@app.get("/items/stream", response_model=list[Item]) |
||||
|
def stream_items(): |
||||
|
return list(generate_items()) |
||||
|
|
||||
|
response = TestClient(app).get("/items/stream") |
||||
|
|
||||
|
assert response.status_code == 200 |
||||
|
data = response.json() |
||||
|
assert len(data) == 1 |
||||
|
assert data[0] == {"name": "Item1", "description": "First item"} |
||||
|
|
||||
|
|
||||
|
@only_for_pydantic_below_v2_13 |
||||
|
def test_raises_error_for_unsupported_pydantic_version(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.get( |
||||
|
"/user", |
||||
|
response_model=OuterModel, |
||||
|
response_model_polymorphic_serialization=True, |
||||
|
) |
||||
|
def get_user(): |
||||
|
return OuterModel(user=UserLogin(name="pydantic", password="password")) |
||||
|
|
||||
|
with pytest.raises(ValueError) as exc_info: |
||||
|
TestClient(app).get("/user") |
||||
|
|
||||
|
assert "polymorphic_serialization requires Pydantic >= 2.13" in str(exc_info.value) |
||||
|
|
||||
|
|
||||
|
def test_flag_defaults_to_false(): |
||||
|
app = FastAPI() |
||||
|
|
||||
|
@app.get("/user1", response_model=OuterModel) |
||||
|
def get_user1(): |
||||
|
return OuterModel(user=UserLogin(name="user1", password="pass1")) |
||||
|
|
||||
|
@app.post("/user2", response_model=OuterModel) |
||||
|
def post_user2(): |
||||
|
return OuterModel(user=UserLogin(name="user2", password="pass2")) |
||||
|
|
||||
|
client = TestClient(app) |
||||
|
|
||||
|
response1 = client.get("/user1") |
||||
|
assert response1.json() == {"user": {"name": "user1"}} |
||||
|
|
||||
|
response2 = client.post("/user2") |
||||
|
assert response2.json() == {"user": {"name": "user2"}} |
||||
Loading…
Reference in new issue