Coverage for tests / benchmarks / test_general_performance.py: 100%
191 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-12 18:15 +0000
1import json 1arst
2import sys 1arst
3from collections.abc import Iterator 1arst
4from typing import Annotated, Any 1arst
6import pytest 1arst
7from fastapi import Depends, FastAPI 1arst
8from fastapi.testclient import TestClient 1arst
9from pydantic import BaseModel 1arst
11if "--codspeed" not in sys.argv: 1arst
12 pytest.skip( 1arst
13 "Benchmark tests are skipped by default; run with --codspeed.",
14 allow_module_level=True,
15 )
17LARGE_ITEMS: list[dict[str, Any]] = [ 1a
18 {
19 "id": i,
20 "name": f"item-{i}",
21 "values": list(range(25)),
22 "meta": {
23 "active": True,
24 "group": i % 10,
25 "tag": f"t{i % 5}",
26 },
27 }
28 for i in range(300)
29]
31LARGE_METADATA: dict[str, Any] = { 1a
32 "source": "benchmark",
33 "version": 1,
34 "flags": {"a": True, "b": False, "c": True},
35 "notes": ["x" * 50, "y" * 50, "z" * 50],
36}
38LARGE_PAYLOAD: dict[str, Any] = {"items": LARGE_ITEMS, "metadata": LARGE_METADATA} 1a
41def dep_a(): 1a
42 return 40 1bcdefghi
45def dep_b(a: Annotated[int, Depends(dep_a)]): 1a
46 return a + 2 1bcdefghi
49class ItemIn(BaseModel): 1a
50 name: str 1a
51 value: int 1a
54class ItemOut(BaseModel): 1a
55 name: str 1a
56 value: int 1a
57 dep: int 1a
60class LargeIn(BaseModel): 1a
61 items: list[dict[str, Any]] 1a
62 metadata: dict[str, Any] 1a
65class LargeOut(BaseModel): 1a
66 items: list[dict[str, Any]] 1a
67 metadata: dict[str, Any] 1a
70app = FastAPI() 1a
73@app.post("/sync/validated", response_model=ItemOut) 1a
74def sync_validated(item: ItemIn, dep: Annotated[int, Depends(dep_b)]): 1a
75 return ItemOut(name=item.name, value=item.value, dep=dep) 1f
78@app.get("/sync/dict-no-response-model") 1a
79def sync_dict_no_response_model(): 1a
80 return {"name": "foo", "value": 123} 1u
83@app.get("/sync/dict-with-response-model", response_model=ItemOut) 1a
84def sync_dict_with_response_model( 1a
85 dep: Annotated[int, Depends(dep_b)],
86):
87 return {"name": "foo", "value": 123, "dep": dep} 1g
90@app.get("/sync/model-no-response-model") 1a
91def sync_model_no_response_model(dep: Annotated[int, Depends(dep_b)]): 1a
92 return ItemOut(name="foo", value=123, dep=dep) 1i
95@app.get("/sync/model-with-response-model", response_model=ItemOut) 1a
96def sync_model_with_response_model(dep: Annotated[int, Depends(dep_b)]): 1a
97 return ItemOut(name="foo", value=123, dep=dep) 1h
100@app.post("/async/validated", response_model=ItemOut) 1a
101async def async_validated( 1a
102 item: ItemIn,
103 dep: Annotated[int, Depends(dep_b)],
104):
105 return ItemOut(name=item.name, value=item.value, dep=dep) 1b
108@app.post("/sync/large-receive") 1a
109def sync_large_receive(payload: LargeIn): 1a
110 return {"received": len(payload.items)} 1v
113@app.post("/async/large-receive") 1a
114async def async_large_receive(payload: LargeIn): 1a
115 return {"received": len(payload.items)} 1w
118@app.get("/sync/large-dict-no-response-model") 1a
119def sync_large_dict_no_response_model(): 1a
120 return LARGE_PAYLOAD 1j
123@app.get("/sync/large-dict-with-response-model", response_model=LargeOut) 1a
124def sync_large_dict_with_response_model(): 1a
125 return LARGE_PAYLOAD 1k
128@app.get("/sync/large-model-no-response-model") 1a
129def sync_large_model_no_response_model(): 1a
130 return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) 1l
133@app.get("/sync/large-model-with-response-model", response_model=LargeOut) 1a
134def sync_large_model_with_response_model(): 1a
135 return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) 1m
138@app.get("/async/large-dict-no-response-model") 1a
139async def async_large_dict_no_response_model(): 1a
140 return LARGE_PAYLOAD 1n
143@app.get("/async/large-dict-with-response-model", response_model=LargeOut) 1a
144async def async_large_dict_with_response_model(): 1a
145 return LARGE_PAYLOAD 1o
148@app.get("/async/large-model-no-response-model") 1a
149async def async_large_model_no_response_model(): 1a
150 return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) 1p
153@app.get("/async/large-model-with-response-model", response_model=LargeOut) 1a
154async def async_large_model_with_response_model(): 1a
155 return LargeOut(items=LARGE_ITEMS, metadata=LARGE_METADATA) 1q
158@app.get("/async/dict-no-response-model") 1a
159async def async_dict_no_response_model(): 1a
160 return {"name": "foo", "value": 123} 1x
163@app.get("/async/dict-with-response-model", response_model=ItemOut) 1a
164async def async_dict_with_response_model( 1a
165 dep: Annotated[int, Depends(dep_b)],
166):
167 return {"name": "foo", "value": 123, "dep": dep} 1c
170@app.get("/async/model-no-response-model") 1a
171async def async_model_no_response_model( 1a
172 dep: Annotated[int, Depends(dep_b)],
173):
174 return ItemOut(name="foo", value=123, dep=dep) 1e
177@app.get("/async/model-with-response-model", response_model=ItemOut) 1a
178async def async_model_with_response_model( 1a
179 dep: Annotated[int, Depends(dep_b)],
180):
181 return ItemOut(name="foo", value=123, dep=dep) 1d
184@pytest.fixture(scope="module") 1a
185def client() -> Iterator[TestClient]: 1a
186 with TestClient(app) as client: 1a
187 yield client 1a
190def _bench_get(benchmark, client: TestClient, path: str) -> tuple[int, bytes]: 1a
191 warmup = client.get(path) 1cxonqpdegukjmlhi
192 assert warmup.status_code == 200 1cxonqpdegukjmlhi
194 def do_request() -> tuple[int, bytes]: 1cxonqpdegukjmlhi
195 response = client.get(path) 1cxonqpdegukjmlhi
196 return response.status_code, response.content 1cxonqpdegukjmlhi
198 return benchmark(do_request) 1cxonqpdegukjmlhi
201def _bench_post_json( 1a
202 benchmark, client: TestClient, path: str, json: dict[str, Any]
203) -> tuple[int, bytes]:
204 warmup = client.post(path, json=json) 1wbvf
205 assert warmup.status_code == 200 1wbvf
207 def do_request() -> tuple[int, bytes]: 1wbvf
208 response = client.post(path, json=json) 1wbvf
209 return response.status_code, response.content 1wbvf
211 return benchmark(do_request) 1wbvf
214def test_sync_receiving_validated_pydantic_model(benchmark, client: TestClient) -> None: 1a
215 status_code, body = _bench_post_json( 1f
216 benchmark,
217 client,
218 "/sync/validated",
219 json={"name": "foo", "value": 123},
220 )
221 assert status_code == 200 1f
222 assert body == b'{"name":"foo","value":123,"dep":42}' 1f
225def test_sync_return_dict_without_response_model(benchmark, client: TestClient) -> None: 1a
226 status_code, body = _bench_get(benchmark, client, "/sync/dict-no-response-model") 1u
227 assert status_code == 200 1u
228 assert body == b'{"name":"foo","value":123}' 1u
231def test_sync_return_dict_with_response_model(benchmark, client: TestClient) -> None: 1a
232 status_code, body = _bench_get(benchmark, client, "/sync/dict-with-response-model") 1g
233 assert status_code == 200 1g
234 assert body == b'{"name":"foo","value":123,"dep":42}' 1g
237def test_sync_return_model_without_response_model( 1a
238 benchmark, client: TestClient
239) -> None:
240 status_code, body = _bench_get(benchmark, client, "/sync/model-no-response-model") 1i
241 assert status_code == 200 1i
242 assert body == b'{"name":"foo","value":123,"dep":42}' 1i
245def test_sync_return_model_with_response_model(benchmark, client: TestClient) -> None: 1a
246 status_code, body = _bench_get(benchmark, client, "/sync/model-with-response-model") 1h
247 assert status_code == 200 1h
248 assert body == b'{"name":"foo","value":123,"dep":42}' 1h
251def test_async_receiving_validated_pydantic_model( 1a
252 benchmark, client: TestClient
253) -> None:
254 status_code, body = _bench_post_json( 1b
255 benchmark, client, "/async/validated", json={"name": "foo", "value": 123}
256 )
257 assert status_code == 200 1b
258 assert body == b'{"name":"foo","value":123,"dep":42}' 1b
261def test_async_return_dict_without_response_model( 1a
262 benchmark, client: TestClient
263) -> None:
264 status_code, body = _bench_get(benchmark, client, "/async/dict-no-response-model") 1x
265 assert status_code == 200 1x
266 assert body == b'{"name":"foo","value":123}' 1x
269def test_async_return_dict_with_response_model(benchmark, client: TestClient) -> None: 1a
270 status_code, body = _bench_get(benchmark, client, "/async/dict-with-response-model") 1c
271 assert status_code == 200 1c
272 assert body == b'{"name":"foo","value":123,"dep":42}' 1c
275def test_async_return_model_without_response_model( 1a
276 benchmark, client: TestClient
277) -> None:
278 status_code, body = _bench_get(benchmark, client, "/async/model-no-response-model") 1e
279 assert status_code == 200 1e
280 assert body == b'{"name":"foo","value":123,"dep":42}' 1e
283def test_async_return_model_with_response_model(benchmark, client: TestClient) -> None: 1a
284 status_code, body = _bench_get( 1d
285 benchmark, client, "/async/model-with-response-model"
286 )
287 assert status_code == 200 1d
288 assert body == b'{"name":"foo","value":123,"dep":42}' 1d
291def test_sync_receiving_large_payload(benchmark, client: TestClient) -> None: 1a
292 status_code, body = _bench_post_json( 1v
293 benchmark,
294 client,
295 "/sync/large-receive",
296 json=LARGE_PAYLOAD,
297 )
298 assert status_code == 200 1v
299 assert body == b'{"received":300}' 1v
302def test_async_receiving_large_payload(benchmark, client: TestClient) -> None: 1a
303 status_code, body = _bench_post_json( 1w
304 benchmark,
305 client,
306 "/async/large-receive",
307 json=LARGE_PAYLOAD,
308 )
309 assert status_code == 200 1w
310 assert body == b'{"received":300}' 1w
313def _expected_large_payload_json_bytes() -> bytes: 1a
314 return json.dumps( 1onqpkjml
315 LARGE_PAYLOAD,
316 ensure_ascii=False,
317 allow_nan=False,
318 separators=(",", ":"),
319 ).encode("utf-8")
322def test_sync_return_large_dict_without_response_model( 1a
323 benchmark, client: TestClient
324) -> None:
325 status_code, body = _bench_get( 1j
326 benchmark, client, "/sync/large-dict-no-response-model"
327 )
328 assert status_code == 200 1j
329 assert body == _expected_large_payload_json_bytes() 1j
332def test_sync_return_large_dict_with_response_model( 1a
333 benchmark, client: TestClient
334) -> None:
335 status_code, body = _bench_get( 1k
336 benchmark, client, "/sync/large-dict-with-response-model"
337 )
338 assert status_code == 200 1k
339 assert body == _expected_large_payload_json_bytes() 1k
342def test_sync_return_large_model_without_response_model( 1a
343 benchmark, client: TestClient
344) -> None:
345 status_code, body = _bench_get( 1l
346 benchmark, client, "/sync/large-model-no-response-model"
347 )
348 assert status_code == 200 1l
349 assert body == _expected_large_payload_json_bytes() 1l
352def test_sync_return_large_model_with_response_model( 1a
353 benchmark, client: TestClient
354) -> None:
355 status_code, body = _bench_get( 1m
356 benchmark, client, "/sync/large-model-with-response-model"
357 )
358 assert status_code == 200 1m
359 assert body == _expected_large_payload_json_bytes() 1m
362def test_async_return_large_dict_without_response_model( 1a
363 benchmark, client: TestClient
364) -> None:
365 status_code, body = _bench_get( 1n
366 benchmark, client, "/async/large-dict-no-response-model"
367 )
368 assert status_code == 200 1n
369 assert body == _expected_large_payload_json_bytes() 1n
372def test_async_return_large_dict_with_response_model( 1a
373 benchmark, client: TestClient
374) -> None:
375 status_code, body = _bench_get( 1o
376 benchmark, client, "/async/large-dict-with-response-model"
377 )
378 assert status_code == 200 1o
379 assert body == _expected_large_payload_json_bytes() 1o
382def test_async_return_large_model_without_response_model( 1a
383 benchmark, client: TestClient
384) -> None:
385 status_code, body = _bench_get( 1p
386 benchmark, client, "/async/large-model-no-response-model"
387 )
388 assert status_code == 200 1p
389 assert body == _expected_large_payload_json_bytes() 1p
392def test_async_return_large_model_with_response_model( 1a
393 benchmark, client: TestClient
394) -> None:
395 status_code, body = _bench_get( 1q
396 benchmark, client, "/async/large-model-with-response-model"
397 )
398 assert status_code == 200 1q
399 assert body == _expected_large_payload_json_bytes() 1q