Coverage for tests / test_request_params / test_form / test_list.py: 100%

132 statements  

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

1from typing import Annotated 1abcd

2 

3import pytest 1abcd

4from dirty_equals import IsOneOf, IsPartialDict 1abcd

5from fastapi import FastAPI, Form 1abcd

6from fastapi.testclient import TestClient 1abcd

7from pydantic import BaseModel, Field 1abcd

8 

9from .utils import get_body_model_name 1abcd

10 

11app = FastAPI() 1abcd

12 

13# ===================================================================================== 

14# Without aliases 

15 

16 

17@app.post("/required-list-str", operation_id="required_list_str") 1abcd

18async def read_required_list_str(p: Annotated[list[str], Form()]): 1abcd

19 return {"p": p} 1efg

20 

21 

22class FormModelRequiredListStr(BaseModel): 1abcd

23 p: list[str] 1abcd

24 

25 

26@app.post("/model-required-list-str", operation_id="model_required_list_str") 1abcd

27def read_model_required_list_str(p: Annotated[FormModelRequiredListStr, Form()]): 1abcd

28 return {"p": p.p} 1efg

29 

30 

31@pytest.mark.parametrize( 1abcd

32 "path", 

33 ["/required-list-str", "/model-required-list-str"], 

34) 

35def test_required_list_str_schema(path: str): 1abcd

36 openapi = app.openapi() 1OPQ

37 body_model_name = get_body_model_name(openapi, path) 1OPQ

38 

39 assert app.openapi()["components"]["schemas"][body_model_name] == { 1OPQ

40 "properties": { 

41 "p": { 

42 "items": {"type": "string"}, 

43 "title": "P", 

44 "type": "array", 

45 }, 

46 }, 

47 "required": ["p"], 

48 "title": body_model_name, 

49 "type": "object", 

50 } 

51 

52 

53@pytest.mark.parametrize( 1abcd

54 "path", 

55 ["/required-list-str", "/model-required-list-str"], 

56) 

57def test_required_list_str_missing(path: str): 1abcd

58 client = TestClient(app) 1qrs

59 response = client.post(path) 1qrs

60 assert response.status_code == 422 1qrs

61 assert response.json() == { 1qrs

62 "detail": [ 

63 { 

64 "type": "missing", 

65 "loc": ["body", "p"], 

66 "msg": "Field required", 

67 "input": IsOneOf(None, {}), 

68 } 

69 ] 

70 } 

71 

72 

73@pytest.mark.parametrize( 1abcd

74 "path", 

75 ["/required-list-str", "/model-required-list-str"], 

76) 

77def test_required_list_str(path: str): 1abcd

78 client = TestClient(app) 1efg

79 response = client.post(path, data={"p": ["hello", "world"]}) 1efg

80 assert response.status_code == 200 1efg

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

82 

83 

84# ===================================================================================== 

85# Alias 

86 

87 

88@app.post("/required-list-alias", operation_id="required_list_alias") 1abcd

89async def read_required_list_alias(p: Annotated[list[str], Form(alias="p_alias")]): 1abcd

90 return {"p": p} 1hij

91 

92 

93class FormModelRequiredListAlias(BaseModel): 1abcd

94 p: list[str] = Field(alias="p_alias") 1abcd

95 

96 

97@app.post("/model-required-list-alias", operation_id="model_required_list_alias") 1abcd

98async def read_model_required_list_alias( 1abcd

99 p: Annotated[FormModelRequiredListAlias, Form()], 

100): 

101 return {"p": p.p} 1hij

102 

103 

104@pytest.mark.parametrize( 1abcd

105 "path", 

106 [ 

107 "/required-list-alias", 

108 "/model-required-list-alias", 

109 ], 

110) 

111def test_required_list_str_alias_schema(path: str): 1abcd

112 openapi = app.openapi() 1RST

113 body_model_name = get_body_model_name(openapi, path) 1RST

114 

115 assert app.openapi()["components"]["schemas"][body_model_name] == { 1RST

116 "properties": { 

117 "p_alias": { 

118 "items": {"type": "string"}, 

119 "title": "P Alias", 

120 "type": "array", 

121 }, 

122 }, 

123 "required": ["p_alias"], 

124 "title": body_model_name, 

125 "type": "object", 

126 } 

127 

128 

129@pytest.mark.parametrize( 1abcd

130 "path", 

131 ["/required-list-alias", "/model-required-list-alias"], 

132) 

133def test_required_list_alias_missing(path: str): 1abcd

134 client = TestClient(app) 1tuv

135 response = client.post(path) 1tuv

136 assert response.status_code == 422 1tuv

137 assert response.json() == { 1tuv

138 "detail": [ 

139 { 

140 "type": "missing", 

141 "loc": ["body", "p_alias"], 

142 "msg": "Field required", 

143 "input": IsOneOf(None, {}), 

144 } 

145 ] 

146 } 

147 

148 

149@pytest.mark.parametrize( 1abcd

150 "path", 

151 [ 

152 "/required-list-alias", 

153 "/model-required-list-alias", 

154 ], 

155) 

156def test_required_list_alias_by_name(path: str): 1abcd

157 client = TestClient(app) 1wxy

158 response = client.post(path, data={"p": ["hello", "world"]}) 1wxy

159 assert response.status_code == 422 1wxy

160 assert response.json() == { 1wxy

161 "detail": [ 

162 { 

163 "type": "missing", 

164 "loc": ["body", "p_alias"], 

165 "msg": "Field required", 

166 "input": IsOneOf(None, {"p": ["hello", "world"]}), 

167 } 

168 ] 

169 } 

170 

171 

172@pytest.mark.parametrize( 1abcd

173 "path", 

174 ["/required-list-alias", "/model-required-list-alias"], 

175) 

176def test_required_list_alias_by_alias(path: str): 1abcd

177 client = TestClient(app) 1hij

178 response = client.post(path, data={"p_alias": ["hello", "world"]}) 1hij

179 assert response.status_code == 200, response.text 1hij

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

181 

182 

183# ===================================================================================== 

184# Validation alias 

185 

186 

187@app.post( 1abcd

188 "/required-list-validation-alias", operation_id="required_list_validation_alias" 

189) 

190def read_required_list_validation_alias( 1abcd

191 p: Annotated[list[str], Form(validation_alias="p_val_alias")], 

192): 

193 return {"p": p} 1klm

194 

195 

196class FormModelRequiredListValidationAlias(BaseModel): 1abcd

197 p: list[str] = Field(validation_alias="p_val_alias") 1abcd

198 

199 

200@app.post( 1abcd

201 "/model-required-list-validation-alias", 

202 operation_id="model_required_list_validation_alias", 

203) 

204async def read_model_required_list_validation_alias( 1abcd

205 p: Annotated[FormModelRequiredListValidationAlias, Form()], 

206): 

207 return {"p": p.p} 1klm

208 

209 

210@pytest.mark.parametrize( 1abcd

211 "path", 

212 ["/required-list-validation-alias", "/model-required-list-validation-alias"], 

213) 

214def test_required_list_validation_alias_schema(path: str): 1abcd

215 openapi = app.openapi() 1UVW

216 body_model_name = get_body_model_name(openapi, path) 1UVW

217 

218 assert app.openapi()["components"]["schemas"][body_model_name] == { 1UVW

219 "properties": { 

220 "p_val_alias": { 

221 "items": {"type": "string"}, 

222 "title": "P Val Alias", 

223 "type": "array", 

224 }, 

225 }, 

226 "required": ["p_val_alias"], 

227 "title": body_model_name, 

228 "type": "object", 

229 } 

230 

231 

232@pytest.mark.parametrize( 1abcd

233 "path", 

234 [ 

235 "/required-list-validation-alias", 

236 "/model-required-list-validation-alias", 

237 ], 

238) 

239def test_required_list_validation_alias_missing(path: str): 1abcd

240 client = TestClient(app) 1zAB

241 response = client.post(path) 1zAB

242 assert response.status_code == 422 1zAB

243 assert response.json() == { 1zAB

244 "detail": [ 

245 { 

246 "type": "missing", 

247 "loc": [ 

248 "body", 

249 "p_val_alias", 

250 ], 

251 "msg": "Field required", 

252 "input": IsOneOf(None, {}), 

253 } 

254 ] 

255 } 

256 

257 

258@pytest.mark.parametrize( 1abcd

259 "path", 

260 [ 

261 "/required-list-validation-alias", 

262 "/model-required-list-validation-alias", 

263 ], 

264) 

265def test_required_list_validation_alias_by_name(path: str): 1abcd

266 client = TestClient(app) 1CDE

267 response = client.post(path, data={"p": ["hello", "world"]}) 1CDE

268 assert response.status_code == 422, response.text 1CDE

269 

270 assert response.json() == { 1CDE

271 "detail": [ 

272 { 

273 "type": "missing", 

274 "loc": ["body", "p_val_alias"], 

275 "msg": "Field required", 

276 "input": IsOneOf(None, IsPartialDict({"p": ["hello", "world"]})), 

277 } 

278 ] 

279 } 

280 

281 

282@pytest.mark.parametrize( 1abcd

283 "path", 

284 ["/required-list-validation-alias", "/model-required-list-validation-alias"], 

285) 

286def test_required_list_validation_alias_by_validation_alias(path: str): 1abcd

287 client = TestClient(app) 1klm

288 response = client.post(path, data={"p_val_alias": ["hello", "world"]}) 1klm

289 assert response.status_code == 200, response.text 1klm

290 

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

292 

293 

294# ===================================================================================== 

295# Alias and validation alias 

296 

297 

298@app.post( 1abcd

299 "/required-list-alias-and-validation-alias", 

300 operation_id="required_list_alias_and_validation_alias", 

301) 

302def read_required_list_alias_and_validation_alias( 1abcd

303 p: Annotated[list[str], Form(alias="p_alias", validation_alias="p_val_alias")], 

304): 

305 return {"p": p} 1nop

306 

307 

308class FormModelRequiredListAliasAndValidationAlias(BaseModel): 1abcd

309 p: list[str] = Field(alias="p_alias", validation_alias="p_val_alias") 1abcd

310 

311 

312@app.post( 1abcd

313 "/model-required-list-alias-and-validation-alias", 

314 operation_id="model_required_list_alias_and_validation_alias", 

315) 

316def read_model_required_list_alias_and_validation_alias( 1abcd

317 p: Annotated[FormModelRequiredListAliasAndValidationAlias, Form()], 

318): 

319 return {"p": p.p} 1nop

320 

321 

322@pytest.mark.parametrize( 1abcd

323 "path", 

324 [ 

325 "/required-list-alias-and-validation-alias", 

326 "/model-required-list-alias-and-validation-alias", 

327 ], 

328) 

329def test_required_list_alias_and_validation_alias_schema(path: str): 1abcd

330 openapi = app.openapi() 1XYZ

331 body_model_name = get_body_model_name(openapi, path) 1XYZ

332 

333 assert app.openapi()["components"]["schemas"][body_model_name] == { 1XYZ

334 "properties": { 

335 "p_val_alias": { 

336 "items": {"type": "string"}, 

337 "title": "P Val Alias", 

338 "type": "array", 

339 }, 

340 }, 

341 "required": ["p_val_alias"], 

342 "title": body_model_name, 

343 "type": "object", 

344 } 

345 

346 

347@pytest.mark.parametrize( 1abcd

348 "path", 

349 [ 

350 "/required-list-alias-and-validation-alias", 

351 "/model-required-list-alias-and-validation-alias", 

352 ], 

353) 

354def test_required_list_alias_and_validation_alias_missing(path: str): 1abcd

355 client = TestClient(app) 1FGH

356 response = client.post(path) 1FGH

357 assert response.status_code == 422 1FGH

358 assert response.json() == { 1FGH

359 "detail": [ 

360 { 

361 "type": "missing", 

362 "loc": [ 

363 "body", 

364 "p_val_alias", 

365 ], 

366 "msg": "Field required", 

367 "input": IsOneOf(None, {}), 

368 } 

369 ] 

370 } 

371 

372 

373@pytest.mark.parametrize( 1abcd

374 "path", 

375 [ 

376 "/required-list-alias-and-validation-alias", 

377 "/model-required-list-alias-and-validation-alias", 

378 ], 

379) 

380def test_required_list_alias_and_validation_alias_by_name(path: str): 1abcd

381 client = TestClient(app) 1IJK

382 response = client.post(path, data={"p": ["hello", "world"]}) 1IJK

383 assert response.status_code == 422 1IJK

384 assert response.json() == { 1IJK

385 "detail": [ 

386 { 

387 "type": "missing", 

388 "loc": [ 

389 "body", 

390 "p_val_alias", 

391 ], 

392 "msg": "Field required", 

393 "input": IsOneOf( 

394 None, 

395 {"p": ["hello", "world"]}, 

396 ), 

397 } 

398 ] 

399 } 

400 

401 

402@pytest.mark.parametrize( 1abcd

403 "path", 

404 [ 

405 "/required-list-alias-and-validation-alias", 

406 "/model-required-list-alias-and-validation-alias", 

407 ], 

408) 

409def test_required_list_alias_and_validation_alias_by_alias(path: str): 1abcd

410 client = TestClient(app) 1LMN

411 response = client.post(path, data={"p_alias": ["hello", "world"]}) 1LMN

412 assert response.status_code == 422 1LMN

413 assert response.json() == { 1LMN

414 "detail": [ 

415 { 

416 "type": "missing", 

417 "loc": ["body", "p_val_alias"], 

418 "msg": "Field required", 

419 "input": IsOneOf(None, {"p_alias": ["hello", "world"]}), 

420 } 

421 ] 

422 } 

423 

424 

425@pytest.mark.parametrize( 1abcd

426 "path", 

427 [ 

428 "/required-list-alias-and-validation-alias", 

429 "/model-required-list-alias-and-validation-alias", 

430 ], 

431) 

432def test_required_list_alias_and_validation_alias_by_validation_alias(path: str): 1abcd

433 client = TestClient(app) 1nop

434 response = client.post(path, data={"p_val_alias": ["hello", "world"]}) 1nop

435 assert response.status_code == 200, response.text 1nop

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