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

1import json 1arst

2import sys 1arst

3from collections.abc import Iterator 1arst

4from typing import Annotated, Any 1arst

5 

6import pytest 1arst

7from fastapi import Depends, FastAPI 1arst

8from fastapi.testclient import TestClient 1arst

9from pydantic import BaseModel 1arst

10 

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 ) 

16 

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] 

30 

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} 

37 

38LARGE_PAYLOAD: dict[str, Any] = {"items": LARGE_ITEMS, "metadata": LARGE_METADATA} 1a

39 

40 

41def dep_a(): 1a

42 return 40 1bcdefghi

43 

44 

45def dep_b(a: Annotated[int, Depends(dep_a)]): 1a

46 return a + 2 1bcdefghi

47 

48 

49class ItemIn(BaseModel): 1a

50 name: str 1a

51 value: int 1a

52 

53 

54class ItemOut(BaseModel): 1a

55 name: str 1a

56 value: int 1a

57 dep: int 1a

58 

59 

60class LargeIn(BaseModel): 1a

61 items: list[dict[str, Any]] 1a

62 metadata: dict[str, Any] 1a

63 

64 

65class LargeOut(BaseModel): 1a

66 items: list[dict[str, Any]] 1a

67 metadata: dict[str, Any] 1a

68 

69 

70app = FastAPI() 1a

71 

72 

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

76 

77 

78@app.get("/sync/dict-no-response-model") 1a

79def sync_dict_no_response_model(): 1a

80 return {"name": "foo", "value": 123} 1u

81 

82 

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

88 

89 

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

93 

94 

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

98 

99 

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

106 

107 

108@app.post("/sync/large-receive") 1a

109def sync_large_receive(payload: LargeIn): 1a

110 return {"received": len(payload.items)} 1v

111 

112 

113@app.post("/async/large-receive") 1a

114async def async_large_receive(payload: LargeIn): 1a

115 return {"received": len(payload.items)} 1w

116 

117 

118@app.get("/sync/large-dict-no-response-model") 1a

119def sync_large_dict_no_response_model(): 1a

120 return LARGE_PAYLOAD 1j

121 

122 

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

126 

127 

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

131 

132 

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

136 

137 

138@app.get("/async/large-dict-no-response-model") 1a

139async def async_large_dict_no_response_model(): 1a

140 return LARGE_PAYLOAD 1n

141 

142 

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

146 

147 

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

151 

152 

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

156 

157 

158@app.get("/async/dict-no-response-model") 1a

159async def async_dict_no_response_model(): 1a

160 return {"name": "foo", "value": 123} 1x

161 

162 

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

168 

169 

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

175 

176 

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

182 

183 

184@pytest.fixture(scope="module") 1a

185def client() -> Iterator[TestClient]: 1a

186 with TestClient(app) as client: 1a

187 yield client 1a

188 

189 

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

193 

194 def do_request() -> tuple[int, bytes]: 1cxonqpdegukjmlhi

195 response = client.get(path) 1cxonqpdegukjmlhi

196 return response.status_code, response.content 1cxonqpdegukjmlhi

197 

198 return benchmark(do_request) 1cxonqpdegukjmlhi

199 

200 

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

206 

207 def do_request() -> tuple[int, bytes]: 1wbvf

208 response = client.post(path, json=json) 1wbvf

209 return response.status_code, response.content 1wbvf

210 

211 return benchmark(do_request) 1wbvf

212 

213 

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

223 

224 

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

229 

230 

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

235 

236 

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

243 

244 

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

249 

250 

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

259 

260 

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

267 

268 

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

273 

274 

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

281 

282 

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

289 

290 

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

300 

301 

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

311 

312 

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") 

320 

321 

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

330 

331 

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

340 

341 

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

350 

351 

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

360 

361 

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

370 

371 

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

380 

381 

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

390 

391 

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