Coverage for tests / test_request_params / test_body / test_optional_list.py: 100%

171 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-12 18:15 +0000

1from typing import Annotated, Optional 1abcd

2 

3import pytest 1abcd

4from fastapi import Body, FastAPI 1abcd

5from fastapi.testclient import TestClient 1abcd

6from pydantic import BaseModel, Field 1abcd

7 

8from .utils import get_body_model_name 1abcd

9 

10app = FastAPI() 1abcd

11 

12# ===================================================================================== 

13# Without aliases 

14 

15 

16@app.post("/optional-list-str", operation_id="optional_list_str") 1abcd

17async def read_optional_list_str( 1abcd

18 p: Annotated[Optional[list[str]], Body(embed=True)] = None, 

19): 

20 return {"p": p} 1eOfgPhiQj

21 

22 

23class BodyModelOptionalListStr(BaseModel): 1abcd

24 p: Optional[list[str]] = None 1abcd

25 

26 

27@app.post("/model-optional-list-str", operation_id="model_optional_list_str") 1abcd

28async def read_model_optional_list_str(p: BodyModelOptionalListStr): 1abcd

29 return {"p": p.p} 1efghij

30 

31 

32@pytest.mark.parametrize( 1abcd

33 "path", 

34 ["/optional-list-str", "/model-optional-list-str"], 

35) 

36def test_optional_list_str_schema(path: str): 1abcd

37 openapi = app.openapi() 1$%'

38 body_model_name = get_body_model_name(openapi, path) 1$%'

39 

40 assert app.openapi()["components"]["schemas"][body_model_name] == { 1$%'

41 "properties": { 

42 "p": { 

43 "anyOf": [ 

44 {"items": {"type": "string"}, "type": "array"}, 

45 {"type": "null"}, 

46 ], 

47 "title": "P", 

48 }, 

49 }, 

50 "title": body_model_name, 

51 "type": "object", 

52 } 

53 

54 

55def test_optional_list_str_missing(): 1abcd

56 client = TestClient(app) 1OPQ

57 response = client.post("/optional-list-str") 1OPQ

58 assert response.status_code == 200, response.text 1OPQ

59 assert response.json() == {"p": None} 1OPQ

60 

61 

62def test_model_optional_list_str_missing(): 1abcd

63 client = TestClient(app) 1012

64 response = client.post("/model-optional-list-str") 1012

65 assert response.status_code == 422, response.text 1012

66 assert response.json() == { 1012

67 "detail": [ 

68 { 

69 "input": None, 

70 "loc": ["body"], 

71 "msg": "Field required", 

72 "type": "missing", 

73 }, 

74 ], 

75 } 

76 

77 

78@pytest.mark.parametrize( 1abcd

79 "path", 

80 ["/optional-list-str", "/model-optional-list-str"], 

81) 

82def test_optional_list_str_missing_empty_dict(path: str): 1abcd

83 client = TestClient(app) 1fhj

84 response = client.post(path, json={}) 1fhj

85 assert response.status_code == 200, response.text 1fhj

86 assert response.json() == {"p": None} 1fhj

87 

88 

89@pytest.mark.parametrize( 1abcd

90 "path", 

91 ["/optional-list-str", "/model-optional-list-str"], 

92) 

93def test_optional_list_str(path: str): 1abcd

94 client = TestClient(app) 1egi

95 response = client.post(path, json={"p": ["hello", "world"]}) 1egi

96 assert response.status_code == 200 1egi

97 assert response.json() == {"p": ["hello", "world"]} 1egi

98 

99 

100# ===================================================================================== 

101# Alias 

102 

103 

104@app.post("/optional-list-alias", operation_id="optional_list_alias") 1abcd

105async def read_optional_list_alias( 1abcd

106 p: Annotated[Optional[list[str]], Body(embed=True, alias="p_alias")] = None, 

107): 

108 return {"p": p} 1klRmnoSpqrTs

109 

110 

111class BodyModelOptionalListAlias(BaseModel): 1abcd

112 p: Optional[list[str]] = Field(None, alias="p_alias") 1abcd

113 

114 

115@app.post("/model-optional-list-alias", operation_id="model_optional_list_alias") 1abcd

116async def read_model_optional_list_alias(p: BodyModelOptionalListAlias): 1abcd

117 return {"p": p.p} 1klmnopqrs

118 

119 

120@pytest.mark.parametrize( 1abcd

121 "path", 

122 [ 

123 "/optional-list-alias", 

124 "/model-optional-list-alias", 

125 ], 

126) 

127def test_optional_list_str_alias_schema(path: str): 1abcd

128 openapi = app.openapi() 1()*

129 body_model_name = get_body_model_name(openapi, path) 1()*

130 

131 assert app.openapi()["components"]["schemas"][body_model_name] == { 1()*

132 "properties": { 

133 "p_alias": { 

134 "anyOf": [ 

135 {"items": {"type": "string"}, "type": "array"}, 

136 {"type": "null"}, 

137 ], 

138 "title": "P Alias", 

139 }, 

140 }, 

141 "title": body_model_name, 

142 "type": "object", 

143 } 

144 

145 

146def test_optional_list_alias_missing(): 1abcd

147 client = TestClient(app) 1RST

148 response = client.post("/optional-list-alias") 1RST

149 assert response.status_code == 200, response.text 1RST

150 assert response.json() == {"p": None} 1RST

151 

152 

153def test_model_optional_list_alias_missing(): 1abcd

154 client = TestClient(app) 1345

155 response = client.post("/model-optional-list-alias") 1345

156 assert response.status_code == 422, response.text 1345

157 assert response.json() == { 1345

158 "detail": [ 

159 { 

160 "input": None, 

161 "loc": ["body"], 

162 "msg": "Field required", 

163 "type": "missing", 

164 }, 

165 ], 

166 } 

167 

168 

169@pytest.mark.parametrize( 1abcd

170 "path", 

171 ["/optional-list-alias", "/model-optional-list-alias"], 

172) 

173def test_optional_list_alias_missing_empty_dict(path: str): 1abcd

174 client = TestClient(app) 1mps

175 response = client.post(path, json={}) 1mps

176 assert response.status_code == 200, response.text 1mps

177 assert response.json() == {"p": None} 1mps

178 

179 

180@pytest.mark.parametrize( 1abcd

181 "path", 

182 ["/optional-list-alias", "/model-optional-list-alias"], 

183) 

184def test_optional_list_alias_by_name(path: str): 1abcd

185 client = TestClient(app) 1lor

186 response = client.post(path, json={"p": ["hello", "world"]}) 1lor

187 assert response.status_code == 200 1lor

188 assert response.json() == {"p": None} 1lor

189 

190 

191@pytest.mark.parametrize( 1abcd

192 "path", 

193 ["/optional-list-alias", "/model-optional-list-alias"], 

194) 

195def test_optional_list_alias_by_alias(path: str): 1abcd

196 client = TestClient(app) 1knq

197 response = client.post(path, json={"p_alias": ["hello", "world"]}) 1knq

198 assert response.status_code == 200 1knq

199 assert response.json() == {"p": ["hello", "world"]} 1knq

200 

201 

202# ===================================================================================== 

203# Validation alias 

204 

205 

206@app.post( 1abcd

207 "/optional-list-validation-alias", operation_id="optional_list_validation_alias" 

208) 

209def read_optional_list_validation_alias( 1abcd

210 p: Annotated[ 

211 Optional[list[str]], Body(embed=True, validation_alias="p_val_alias") 

212 ] = None, 

213): 

214 return {"p": p} 1tuUvwxVyzAWB

215 

216 

217class BodyModelOptionalListValidationAlias(BaseModel): 1abcd

218 p: Optional[list[str]] = Field(None, validation_alias="p_val_alias") 1abcd

219 

220 

221@app.post( 1abcd

222 "/model-optional-list-validation-alias", 

223 operation_id="model_optional_list_validation_alias", 

224) 

225def read_model_optional_list_validation_alias( 1abcd

226 p: BodyModelOptionalListValidationAlias, 

227): 

228 return {"p": p.p} 1tuvwxyzAB

229 

230 

231@pytest.mark.parametrize( 1abcd

232 "path", 

233 ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], 

234) 

235def test_optional_list_validation_alias_schema(path: str): 1abcd

236 openapi = app.openapi() 1+,-

237 body_model_name = get_body_model_name(openapi, path) 1+,-

238 

239 assert app.openapi()["components"]["schemas"][body_model_name] == { 1+,-

240 "properties": { 

241 "p_val_alias": { 

242 "anyOf": [ 

243 {"items": {"type": "string"}, "type": "array"}, 

244 {"type": "null"}, 

245 ], 

246 "title": "P Val Alias", 

247 }, 

248 }, 

249 "title": body_model_name, 

250 "type": "object", 

251 } 

252 

253 

254def test_optional_list_validation_alias_missing(): 1abcd

255 client = TestClient(app) 1UVW

256 response = client.post("/optional-list-validation-alias") 1UVW

257 assert response.status_code == 200, response.text 1UVW

258 assert response.json() == {"p": None} 1UVW

259 

260 

261def test_model_optional_list_validation_alias_missing(): 1abcd

262 client = TestClient(app) 1678

263 response = client.post("/model-optional-list-validation-alias") 1678

264 assert response.status_code == 422, response.text 1678

265 assert response.json() == { 1678

266 "detail": [ 

267 { 

268 "input": None, 

269 "loc": ["body"], 

270 "msg": "Field required", 

271 "type": "missing", 

272 }, 

273 ], 

274 } 

275 

276 

277@pytest.mark.parametrize( 1abcd

278 "path", 

279 ["/optional-list-validation-alias", "/model-optional-list-validation-alias"], 

280) 

281def test_optional_list_validation_alias_missing_empty_dict(path: str): 1abcd

282 client = TestClient(app) 1vyB

283 response = client.post(path, json={}) 1vyB

284 assert response.status_code == 200, response.text 1vyB

285 assert response.json() == {"p": None} 1vyB

286 

287 

288@pytest.mark.parametrize( 1abcd

289 "path", 

290 [ 

291 "/optional-list-validation-alias", 

292 "/model-optional-list-validation-alias", 

293 ], 

294) 

295def test_optional_list_validation_alias_by_name(path: str): 1abcd

296 client = TestClient(app) 1twz

297 response = client.post(path, json={"p": ["hello", "world"]}) 1twz

298 assert response.status_code == 200 1twz

299 assert response.json() == {"p": None} 1twz

300 

301 

302@pytest.mark.parametrize( 1abcd

303 "path", 

304 [ 

305 "/optional-list-validation-alias", 

306 "/model-optional-list-validation-alias", 

307 ], 

308) 

309def test_optional_list_validation_alias_by_validation_alias(path: str): 1abcd

310 client = TestClient(app) 1uxA

311 response = client.post(path, json={"p_val_alias": ["hello", "world"]}) 1uxA

312 assert response.status_code == 200, response.text 1uxA

313 assert response.json() == {"p": ["hello", "world"]} 1uxA

314 

315 

316# ===================================================================================== 

317# Alias and validation alias 

318 

319 

320@app.post( 1abcd

321 "/optional-list-alias-and-validation-alias", 

322 operation_id="optional_list_alias_and_validation_alias", 

323) 

324def read_optional_list_alias_and_validation_alias( 1abcd

325 p: Annotated[ 

326 Optional[list[str]], 

327 Body(embed=True, alias="p_alias", validation_alias="p_val_alias"), 

328 ] = None, 

329): 

330 return {"p": p} 1CDEXFGHIYJKLMZN

331 

332 

333class BodyModelOptionalListAliasAndValidationAlias(BaseModel): 1abcd

334 p: Optional[list[str]] = Field( 1abcd

335 None, alias="p_alias", validation_alias="p_val_alias" 

336 ) 

337 

338 

339@app.post( 1abcd

340 "/model-optional-list-alias-and-validation-alias", 

341 operation_id="model_optional_list_alias_and_validation_alias", 

342) 

343def read_model_optional_list_alias_and_validation_alias( 1abcd

344 p: BodyModelOptionalListAliasAndValidationAlias, 

345): 

346 return {"p": p.p} 1CDEFGHIJKLMN

347 

348 

349@pytest.mark.parametrize( 1abcd

350 "path", 

351 [ 

352 "/optional-list-alias-and-validation-alias", 

353 "/model-optional-list-alias-and-validation-alias", 

354 ], 

355) 

356def test_optional_list_alias_and_validation_alias_schema(path: str): 1abcd

357 openapi = app.openapi() 1./:

358 body_model_name = get_body_model_name(openapi, path) 1./:

359 

360 assert app.openapi()["components"]["schemas"][body_model_name] == { 1./:

361 "properties": { 

362 "p_val_alias": { 

363 "anyOf": [ 

364 {"items": {"type": "string"}, "type": "array"}, 

365 {"type": "null"}, 

366 ], 

367 "title": "P Val Alias", 

368 }, 

369 }, 

370 "title": body_model_name, 

371 "type": "object", 

372 } 

373 

374 

375def test_optional_list_alias_and_validation_alias_missing(): 1abcd

376 client = TestClient(app) 1XYZ

377 response = client.post("/optional-list-alias-and-validation-alias") 1XYZ

378 assert response.status_code == 200, response.text 1XYZ

379 assert response.json() == {"p": None} 1XYZ

380 

381 

382def test_model_optional_list_alias_and_validation_alias_missing(): 1abcd

383 client = TestClient(app) 19!#

384 response = client.post("/model-optional-list-alias-and-validation-alias") 19!#

385 assert response.status_code == 422, response.text 19!#

386 assert response.json() == { 19!#

387 "detail": [ 

388 { 

389 "input": None, 

390 "loc": ["body"], 

391 "msg": "Field required", 

392 "type": "missing", 

393 }, 

394 ], 

395 } 

396 

397 

398@pytest.mark.parametrize( 1abcd

399 "path", 

400 [ 

401 "/optional-list-alias-and-validation-alias", 

402 "/model-optional-list-alias-and-validation-alias", 

403 ], 

404) 

405def test_optional_list_alias_and_validation_alias_missing_empty_dict(path: str): 1abcd

406 client = TestClient(app) 1FJN

407 response = client.post(path, json={}) 1FJN

408 assert response.status_code == 200, response.text 1FJN

409 assert response.json() == {"p": None} 1FJN

410 

411 

412@pytest.mark.parametrize( 1abcd

413 "path", 

414 [ 

415 "/optional-list-alias-and-validation-alias", 

416 "/model-optional-list-alias-and-validation-alias", 

417 ], 

418) 

419def test_optional_list_alias_and_validation_alias_by_name(path: str): 1abcd

420 client = TestClient(app) 1DHL

421 response = client.post(path, json={"p": ["hello", "world"]}) 1DHL

422 assert response.status_code == 200 1DHL

423 assert response.json() == {"p": None} 1DHL

424 

425 

426@pytest.mark.parametrize( 1abcd

427 "path", 

428 [ 

429 "/optional-list-alias-and-validation-alias", 

430 "/model-optional-list-alias-and-validation-alias", 

431 ], 

432) 

433def test_optional_list_alias_and_validation_alias_by_alias(path: str): 1abcd

434 client = TestClient(app) 1CGK

435 response = client.post(path, json={"p_alias": ["hello", "world"]}) 1CGK

436 assert response.status_code == 200 1CGK

437 assert response.json() == {"p": None} 1CGK

438 

439 

440@pytest.mark.parametrize( 1abcd

441 "path", 

442 [ 

443 "/optional-list-alias-and-validation-alias", 

444 "/model-optional-list-alias-and-validation-alias", 

445 ], 

446) 

447def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): 1abcd

448 client = TestClient(app) 1EIM

449 response = client.post(path, json={"p_val_alias": ["hello", "world"]}) 1EIM

450 assert response.status_code == 200, response.text 1EIM

451 assert response.json() == { 1EIM

452 "p": [ 

453 "hello", 

454 "world", 

455 ] 

456 }