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

131 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 FastAPI, Form 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]], Form()] = None, 

19): 

20 return {"p": p} 1efghij

21 

22 

23class FormModelOptionalListStr(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: Annotated[FormModelOptionalListStr, Form()]): 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() 1OPQ

38 body_model_name = get_body_model_name(openapi, path) 1OPQ

39 

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

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 

55@pytest.mark.parametrize( 1abcd

56 "path", 

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

58) 

59def test_optional_list_str_missing(path: str): 1abcd

60 client = TestClient(app) 1fhj

61 response = client.post(path) 1fhj

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

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

64 

65 

66@pytest.mark.parametrize( 1abcd

67 "path", 

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

69) 

70def test_optional_list_str(path: str): 1abcd

71 client = TestClient(app) 1egi

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

73 assert response.status_code == 200 1egi

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

75 

76 

77# ===================================================================================== 

78# Alias 

79 

80 

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

82async def read_optional_list_alias( 1abcd

83 p: Annotated[Optional[list[str]], Form(alias="p_alias")] = None, 

84): 

85 return {"p": p} 1klmnopqrs

86 

87 

88class FormModelOptionalListAlias(BaseModel): 1abcd

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

90 

91 

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

93async def read_model_optional_list_alias( 1abcd

94 p: Annotated[FormModelOptionalListAlias, Form()], 

95): 

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

97 

98 

99@pytest.mark.parametrize( 1abcd

100 "path", 

101 [ 

102 "/optional-list-alias", 

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

104 ], 

105) 

106def test_optional_list_str_alias_schema(path: str): 1abcd

107 openapi = app.openapi() 1RST

108 body_model_name = get_body_model_name(openapi, path) 1RST

109 

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

111 "properties": { 

112 "p_alias": { 

113 "anyOf": [ 

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

115 {"type": "null"}, 

116 ], 

117 "title": "P Alias", 

118 }, 

119 }, 

120 "title": body_model_name, 

121 "type": "object", 

122 } 

123 

124 

125@pytest.mark.parametrize( 1abcd

126 "path", 

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

128) 

129def test_optional_list_alias_missing(path: str): 1abcd

130 client = TestClient(app) 1mps

131 response = client.post(path) 1mps

132 assert response.status_code == 200 1mps

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

134 

135 

136@pytest.mark.parametrize( 1abcd

137 "path", 

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

139) 

140def test_optional_list_alias_by_name(path: str): 1abcd

141 client = TestClient(app) 1lor

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

143 assert response.status_code == 200 1lor

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

145 

146 

147@pytest.mark.parametrize( 1abcd

148 "path", 

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

150) 

151def test_optional_list_alias_by_alias(path: str): 1abcd

152 client = TestClient(app) 1knq

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

154 assert response.status_code == 200 1knq

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

156 

157 

158# ===================================================================================== 

159# Validation alias 

160 

161 

162@app.post( 1abcd

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

164) 

165def read_optional_list_validation_alias( 1abcd

166 p: Annotated[Optional[list[str]], Form(validation_alias="p_val_alias")] = None, 

167): 

168 return {"p": p} 1tuvwxyzAB

169 

170 

171class FormModelOptionalListValidationAlias(BaseModel): 1abcd

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

173 

174 

175@app.post( 1abcd

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

177 operation_id="model_optional_list_validation_alias", 

178) 

179def read_model_optional_list_validation_alias( 1abcd

180 p: Annotated[FormModelOptionalListValidationAlias, Form()], 

181): 

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

183 

184 

185@pytest.mark.parametrize( 1abcd

186 "path", 

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

188) 

189def test_optional_list_validation_alias_schema(path: str): 1abcd

190 openapi = app.openapi() 1UVW

191 body_model_name = get_body_model_name(openapi, path) 1UVW

192 

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

194 "properties": { 

195 "p_val_alias": { 

196 "anyOf": [ 

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

198 {"type": "null"}, 

199 ], 

200 "title": "P Val Alias", 

201 }, 

202 }, 

203 "title": body_model_name, 

204 "type": "object", 

205 } 

206 

207 

208@pytest.mark.parametrize( 1abcd

209 "path", 

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

211) 

212def test_optional_list_validation_alias_missing(path: str): 1abcd

213 client = TestClient(app) 1vyB

214 response = client.post(path) 1vyB

215 assert response.status_code == 200 1vyB

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

217 

218 

219@pytest.mark.parametrize( 1abcd

220 "path", 

221 [ 

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

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

224 ], 

225) 

226def test_optional_list_validation_alias_by_name(path: str): 1abcd

227 client = TestClient(app) 1twz

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

229 assert response.status_code == 200 1twz

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

231 

232 

233@pytest.mark.parametrize( 1abcd

234 "path", 

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

236) 

237def test_optional_list_validation_alias_by_validation_alias(path: str): 1abcd

238 client = TestClient(app) 1uxA

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

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

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

242 

243 

244# ===================================================================================== 

245# Alias and validation alias 

246 

247 

248@app.post( 1abcd

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

250 operation_id="optional_list_alias_and_validation_alias", 

251) 

252def read_optional_list_alias_and_validation_alias( 1abcd

253 p: Annotated[ 

254 Optional[list[str]], Form(alias="p_alias", validation_alias="p_val_alias") 

255 ] = None, 

256): 

257 return {"p": p} 1CDEFGHIJKLMN

258 

259 

260class FormModelOptionalListAliasAndValidationAlias(BaseModel): 1abcd

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

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

263 ) 

264 

265 

266@app.post( 1abcd

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

268 operation_id="model_optional_list_alias_and_validation_alias", 

269) 

270def read_model_optional_list_alias_and_validation_alias( 1abcd

271 p: Annotated[FormModelOptionalListAliasAndValidationAlias, Form()], 

272): 

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

274 

275 

276@pytest.mark.parametrize( 1abcd

277 "path", 

278 [ 

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

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

281 ], 

282) 

283def test_optional_list_alias_and_validation_alias_schema(path: str): 1abcd

284 openapi = app.openapi() 1XYZ

285 body_model_name = get_body_model_name(openapi, path) 1XYZ

286 

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

288 "properties": { 

289 "p_val_alias": { 

290 "anyOf": [ 

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

292 {"type": "null"}, 

293 ], 

294 "title": "P Val Alias", 

295 }, 

296 }, 

297 "title": body_model_name, 

298 "type": "object", 

299 } 

300 

301 

302@pytest.mark.parametrize( 1abcd

303 "path", 

304 [ 

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

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

307 ], 

308) 

309def test_optional_list_alias_and_validation_alias_missing(path: str): 1abcd

310 client = TestClient(app) 1FJN

311 response = client.post(path) 1FJN

312 assert response.status_code == 200 1FJN

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

314 

315 

316@pytest.mark.parametrize( 1abcd

317 "path", 

318 [ 

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

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

321 ], 

322) 

323def test_optional_list_alias_and_validation_alias_by_name(path: str): 1abcd

324 client = TestClient(app) 1DHL

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

326 assert response.status_code == 200 1DHL

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

328 

329 

330@pytest.mark.parametrize( 1abcd

331 "path", 

332 [ 

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

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

335 ], 

336) 

337def test_optional_list_alias_and_validation_alias_by_alias(path: str): 1abcd

338 client = TestClient(app) 1CGK

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

340 assert response.status_code == 200 1CGK

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

342 

343 

344@pytest.mark.parametrize( 1abcd

345 "path", 

346 [ 

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

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

349 ], 

350) 

351def test_optional_list_alias_and_validation_alias_by_validation_alias(path: str): 1abcd

352 client = TestClient(app) 1EIM

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

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

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

356 "p": [ 

357 "hello", 

358 "world", 

359 ] 

360 }