Browse Source

Add performance tests with CodSpeed

pull/14558/head
Sebastián Ramírez 7 months ago
parent
commit
2b91f3a512
  1. 0
      tests/benchmarks/__init__.py
  2. 378
      tests/benchmarks/test_general_performance.py

0
tests/benchmarks/__init__.py

378
tests/benchmarks/test_general_performance.py

@ -0,0 +1,378 @@
import json
from collections.abc import Iterator
from typing import Any, Dict, List, Tuple
import pytest
from fastapi import Depends, FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
from typing_extensions import Annotated
class ItemIn(BaseModel):
name: str
value: int
class ItemOut(BaseModel):
name: str
value: int
dep: int
class LargeIn(BaseModel):
items: List[Dict[str, Any]]
metadata: Dict[str, Any]
class LargeOut(BaseModel):
items: List[Dict[str, Any]]
metadata: Dict[str, Any]
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")
def app() -> FastAPI:
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…
Cancel
Save