Browse Source
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>pull/14571/head
committed by
GitHub
5 changed files with 412 additions and 0 deletions
@ -0,0 +1,396 @@ |
|||
import json |
|||
import sys |
|||
from collections.abc import Iterator |
|||
from typing import Annotated, Any |
|||
|
|||
import pytest |
|||
from fastapi import Depends, FastAPI |
|||
from fastapi.testclient import TestClient |
|||
|
|||
if "--codspeed" not in sys.argv: |
|||
pytest.skip( |
|||
"Benchmark tests are skipped by default; run with --codspeed.", |
|||
allow_module_level=True, |
|||
) |
|||
|
|||
LARGE_ITEMS: list[dict[str, Any]] = [ |
|||
{ |
|||
"id": i, |
|||
"name": f"item-{i}", |
|||
"values": list(range(25)), |
|||
"meta": { |
|||
"active": True, |
|||
"group": i % 10, |
|||
"tag": f"t{i % 5}", |
|||
}, |
|||
} |
|||
for i in range(300) |
|||
] |
|||
|
|||
LARGE_METADATA: dict[str, Any] = { |
|||
"source": "benchmark", |
|||
"version": 1, |
|||
"flags": {"a": True, "b": False, "c": True}, |
|||
"notes": ["x" * 50, "y" * 50, "z" * 50], |
|||
} |
|||
|
|||
LARGE_PAYLOAD: dict[str, Any] = {"items": LARGE_ITEMS, "metadata": LARGE_METADATA} |
|||
|
|||
|
|||
def dep_a(): |
|||
return 40 |
|||
|
|||
|
|||
def dep_b(a: Annotated[int, Depends(dep_a)]): |
|||
return a + 2 |
|||
|
|||
|
|||
@pytest.fixture( |
|||
scope="module", |
|||
params=[ |
|||
"pydantic-v2", |
|||
"pydantic-v1", |
|||
], |
|||
) |
|||
def basemodel_class(request: pytest.FixtureRequest) -> type[Any]: |
|||
if request.param == "pydantic-v2": |
|||
from pydantic import BaseModel |
|||
|
|||
return BaseModel |
|||
else: |
|||
from pydantic.v1 import BaseModel |
|||
|
|||
return BaseModel |
|||
|
|||
|
|||
@pytest.fixture(scope="module") |
|||
def app(basemodel_class: type[Any]) -> FastAPI: |
|||
class ItemIn(basemodel_class): |
|||
name: str |
|||
value: int |
|||
|
|||
class ItemOut(basemodel_class): |
|||
name: str |
|||
value: int |
|||
dep: int |
|||
|
|||
class LargeIn(basemodel_class): |
|||
items: list[dict[str, Any]] |
|||
metadata: dict[str, Any] |
|||
|
|||
class LargeOut(basemodel_class): |
|||
items: list[dict[str, Any]] |
|||
metadata: dict[str, Any] |
|||
|
|||
app = FastAPI() |
|||
|
|||
@app.post("/sync/validated", response_model=ItemOut) |
|||
def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]): |
|||
return ItemOut(name=item.name, value=item.value, dep=dep) |
|||
|
|||
@app.get("/sync/dict-no-response-model") |
|||
def sync_dict_no_response_model(): |
|||
return {"name": "foo", "value": 123} |
|||
|
|||
@app.get("/sync/dict-with-response-model", response_model=ItemOut) |
|||
def sync_dict_with_response_model( |
|||
dep: Annotated[int, Depends(dep_b)], |
|||
): |
|||
return {"name": "foo", "value": 123, "dep": dep} |
|||
|
|||
@app.get("/sync/model-no-response-model") |
|||
def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]): |
|||
return ItemOut(name="foo", value=123, dep=dep) |
|||
|
|||
@app.get("/sync/model-with-response-model", response_model=ItemOut) |
|||
def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]): |
|||
return ItemOut(name="foo", value=123, dep=dep) |
|||
|
|||
@app.post("/async/validated", response_model=ItemOut) |
|||
async def async_validated( |
|||
item: ItemIn, |
|||
dep: Annotated[int, Depends(dep_b)], |
|||
): |
|||
return ItemOut(name=item.name, value=item.value, dep=dep) |
|||
|
|||
@app.post("/sync/large-receive") |
|||
def sync_large_receive(payload: LargeIn): |
|||
return {"received": len(payload.items)} |
|||
|
|||
@app.post("/async/large-receive") |
|||
async def async_large_receive(payload: LargeIn): |
|||
return {"received": len(payload.items)} |
|||
|
|||
@app.get("/sync/large-dict-no-response-model") |
|||
def sync_large_dict_no_response_model(): |
|||
return LARGE_PAYLOAD |
|||
|
|||
@app.get("/sync/large-dict-with-response-model", response_model=LargeOut) |
|||
def sync_large_dict_with_response_model(): |
|||
return LARGE_PAYLOAD |
|||
|
|||
@app.get("/sync/large-model-no-response-model") |
|||
def sync_large_model_no_response_model(): |
|||
return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) |
|||
|
|||
@app.get("/sync/large-model-with-response-model", response_model=LargeOut) |
|||
def sync_large_model_with_response_model(): |
|||
return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) |
|||
|
|||
@app.get("/async/large-dict-no-response-model") |
|||
async def async_large_dict_no_response_model(): |
|||
return LARGE_PAYLOAD |
|||
|
|||
@app.get("/async/large-dict-with-response-model", response_model=LargeOut) |
|||
async def async_large_dict_with_response_model(): |
|||
return LARGE_PAYLOAD |
|||
|
|||
@app.get("/async/large-model-no-response-model") |
|||
async def async_large_model_no_response_model(): |
|||
return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) |
|||
|
|||
@app.get("/async/large-model-with-response-model", response_model=LargeOut) |
|||
async def async_large_model_with_response_model(): |
|||
return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) |
|||
|
|||
@app.get("/async/dict-no-response-model") |
|||
async def async_dict_no_response_model(): |
|||
return {"name": "foo", "value": 123} |
|||
|
|||
@app.get("/async/dict-with-response-model", response_model=ItemOut) |
|||
async def async_dict_with_response_model( |
|||
dep: Annotated[int, Depends(dep_b)], |
|||
): |
|||
return {"name": "foo", "value": 123, "dep": dep} |
|||
|
|||
@app.get("/async/model-no-response-model") |
|||
async def async_model_no_response_model( |
|||
dep: Annotated[int, Depends(dep_b)], |
|||
): |
|||
return ItemOut(name="foo", value=123, dep=dep) |
|||
|
|||
@app.get("/async/model-with-response-model", response_model=ItemOut) |
|||
async def async_model_with_response_model( |
|||
dep: Annotated[int, Depends(dep_b)], |
|||
): |
|||
return ItemOut(name="foo", value=123, dep=dep) |
|||
|
|||
return app |
|||
|
|||
|
|||
@pytest.fixture(scope="module") |
|||
def client(app: FastAPI) -> Iterator[TestClient]: |
|||
with TestClient(app) as client: |
|||
yield client |
|||
|
|||
|
|||
def _bench_get(benchmark, client: TestClient, path: str) -> tuple[int, bytes]: |
|||
warmup = client.get(path) |
|||
assert warmup.status_code == 200 |
|||
|
|||
def do_request() -> tuple[int, bytes]: |
|||
response = client.get(path) |
|||
return response.status_code, response.content |
|||
|
|||
return benchmark(do_request) |
|||
|
|||
|
|||
def _bench_post_json( |
|||
benchmark, client: TestClient, path: str, json: dict[str, Any] |
|||
) -> tuple[int, bytes]: |
|||
warmup = client.post(path, json=json) |
|||
assert warmup.status_code == 200 |
|||
|
|||
def do_request() -> tuple[int, bytes]: |
|||
response = client.post(path, json=json) |
|||
return response.status_code, response.content |
|||
|
|||
return benchmark(do_request) |
|||
|
|||
|
|||
def test_sync_receiving_validated_pydantic_model(benchmark, client: TestClient) -> None: |
|||
status_code, body = _bench_post_json( |
|||
benchmark, |
|||
client, |
|||
"/sync/validated", |
|||
json={"name": "foo", "value": 123}, |
|||
) |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123,"dep":42}' |
|||
|
|||
|
|||
def test_sync_return_dict_without_response_model(benchmark, client: TestClient) -> None: |
|||
status_code, body = _bench_get(benchmark, client, "/sync/dict-no-response-model") |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123}' |
|||
|
|||
|
|||
def test_sync_return_dict_with_response_model(benchmark, client: TestClient) -> None: |
|||
status_code, body = _bench_get(benchmark, client, "/sync/dict-with-response-model") |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123,"dep":42}' |
|||
|
|||
|
|||
def test_sync_return_model_without_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get(benchmark, client, "/sync/model-no-response-model") |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123,"dep":42}' |
|||
|
|||
|
|||
def test_sync_return_model_with_response_model(benchmark, client: TestClient) -> None: |
|||
status_code, body = _bench_get(benchmark, client, "/sync/model-with-response-model") |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123,"dep":42}' |
|||
|
|||
|
|||
def test_async_receiving_validated_pydantic_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_post_json( |
|||
benchmark, client, "/async/validated", json={"name": "foo", "value": 123} |
|||
) |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123,"dep":42}' |
|||
|
|||
|
|||
def test_async_return_dict_without_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get(benchmark, client, "/async/dict-no-response-model") |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123}' |
|||
|
|||
|
|||
def test_async_return_dict_with_response_model(benchmark, client: TestClient) -> None: |
|||
status_code, body = _bench_get(benchmark, client, "/async/dict-with-response-model") |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123,"dep":42}' |
|||
|
|||
|
|||
def test_async_return_model_without_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get(benchmark, client, "/async/model-no-response-model") |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123,"dep":42}' |
|||
|
|||
|
|||
def test_async_return_model_with_response_model(benchmark, client: TestClient) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/async/model-with-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == b'{"name":"foo","value":123,"dep":42}' |
|||
|
|||
|
|||
def test_sync_receiving_large_payload(benchmark, client: TestClient) -> None: |
|||
status_code, body = _bench_post_json( |
|||
benchmark, |
|||
client, |
|||
"/sync/large-receive", |
|||
json=LARGE_PAYLOAD, |
|||
) |
|||
assert status_code == 200 |
|||
assert body == b'{"received":300}' |
|||
|
|||
|
|||
def test_async_receiving_large_payload(benchmark, client: TestClient) -> None: |
|||
status_code, body = _bench_post_json( |
|||
benchmark, |
|||
client, |
|||
"/async/large-receive", |
|||
json=LARGE_PAYLOAD, |
|||
) |
|||
assert status_code == 200 |
|||
assert body == b'{"received":300}' |
|||
|
|||
|
|||
def _expected_large_payload_json_bytes() -> bytes: |
|||
return json.dumps( |
|||
LARGE_PAYLOAD, |
|||
ensure_ascii=False, |
|||
allow_nan=False, |
|||
separators=(",", ":"), |
|||
).encode("utf-8") |
|||
|
|||
|
|||
def test_sync_return_large_dict_without_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/sync/large-dict-no-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == _expected_large_payload_json_bytes() |
|||
|
|||
|
|||
def test_sync_return_large_dict_with_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/sync/large-dict-with-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == _expected_large_payload_json_bytes() |
|||
|
|||
|
|||
def test_sync_return_large_model_without_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/sync/large-model-no-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == _expected_large_payload_json_bytes() |
|||
|
|||
|
|||
def test_sync_return_large_model_with_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/sync/large-model-with-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == _expected_large_payload_json_bytes() |
|||
|
|||
|
|||
def test_async_return_large_dict_without_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/async/large-dict-no-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == _expected_large_payload_json_bytes() |
|||
|
|||
|
|||
def test_async_return_large_dict_with_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/async/large-dict-with-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == _expected_large_payload_json_bytes() |
|||
|
|||
|
|||
def test_async_return_large_model_without_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/async/large-model-no-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == _expected_large_payload_json_bytes() |
|||
|
|||
|
|||
def test_async_return_large_model_with_response_model( |
|||
benchmark, client: TestClient |
|||
) -> None: |
|||
status_code, body = _bench_get( |
|||
benchmark, client, "/async/large-model-with-response-model" |
|||
) |
|||
assert status_code == 200 |
|||
assert body == _expected_large_payload_json_bytes() |
|||
Loading…
Reference in new issue