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

171 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-04-06 01:24 +0000

1from typing import Annotated 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-str", operation_id="optional_str") 1abcd

17async def read_optional_str(p: Annotated[str | None, Body(embed=True)] = None): 1abcd

18 return {"p": p} 1efghijklmn

19 

20 

21class BodyModelOptionalStr(BaseModel): 1abcd

22 p: str | None = None 1abcd

23 

24 

25@app.post("/model-optional-str", operation_id="model_optional_str") 1abcd

26async def read_model_optional_str(p: BodyModelOptionalStr): 1abcd

27 return {"p": p.p} 1opqrstu

28 

29 

30@pytest.mark.parametrize( 1abcd

31 "path", 

32 ["/optional-str", "/model-optional-str"], 

33) 

34def test_optional_str_schema(path: str): 1abcd

35 openapi = app.openapi() 2RbSbTbUbVbWbXbYb

36 body_model_name = get_body_model_name(openapi, path) 2RbSbTbUbVbWbXbYb

37 

38 assert app.openapi()["components"]["schemas"][body_model_name] == { 2RbSbTbUbVbWbXbYb

39 "properties": { 

40 "p": { 

41 "anyOf": [{"type": "string"}, {"type": "null"}], 

42 "title": "P", 

43 }, 

44 }, 

45 "title": body_model_name, 

46 "type": "object", 

47 } 

48 

49 

50def test_optional_str_missing(): 1abcd

51 client = TestClient(app) 1ghkn

52 response = client.post("/optional-str") 1ghkn

53 assert response.status_code == 200, response.text 1ghkn

54 assert response.json() == {"p": None} 1ghkn

55 

56 

57def test_model_optional_str_missing(): 1abcd

58 client = TestClient(app) 2kblbmbnb

59 response = client.post("/model-optional-str") 2kblbmbnb

60 assert response.status_code == 422, response.text 2kblbmbnb

61 assert response.json() == { 2kblbmbnb

62 "detail": [ 

63 { 

64 "input": None, 

65 "loc": ["body"], 

66 "msg": "Field required", 

67 "type": "missing", 

68 }, 

69 ], 

70 } 

71 

72 

73@pytest.mark.parametrize( 1abcd

74 "path", 

75 ["/optional-str", "/model-optional-str"], 

76) 

77def test_optional_str_missing_empty_dict(path: str): 1abcd

78 client = TestClient(app) 2p f q obs j u m

79 response = client.post(path, json={}) 2p f q obs j u m

80 assert response.status_code == 200, response.text 2p f q obs j u m

81 assert response.json() == {"p": None} 2p f q obs j u m

82 

83 

84@pytest.mark.parametrize( 1abcd

85 "path", 

86 ["/optional-str", "/model-optional-str"], 

87) 

88def test_optional_str(path: str): 1abcd

89 client = TestClient(app) 2o e pbqbr i t l

90 response = client.post(path, json={"p": "hello"}) 2o e pbqbr i t l

91 assert response.status_code == 200 2o e pbqbr i t l

92 assert response.json() == {"p": "hello"} 2o e pbqbr i t l

93 

94 

95# ===================================================================================== 

96# Alias 

97 

98 

99@app.post("/optional-alias", operation_id="optional_alias") 1abcd

100async def read_optional_alias( 1abcd

101 p: Annotated[str | None, Body(embed=True, alias="p_alias")] = None, 

102): 

103 return {"p": p} 1vwxyzABCDEFGHI

104 

105 

106class BodyModelOptionalAlias(BaseModel): 1abcd

107 p: str | None = Field(None, alias="p_alias") 1abcd

108 

109 

110@app.post("/model-optional-alias", operation_id="model_optional_alias") 1abcd

111async def read_model_optional_alias(p: BodyModelOptionalAlias): 1abcd

112 return {"p": p.p} 1JKLMNOPQRS

113 

114 

115@pytest.mark.parametrize( 1abcd

116 "path", 

117 [ 

118 "/optional-alias", 

119 "/model-optional-alias", 

120 ], 

121) 

122def test_optional_str_alias_schema(path: str): 1abcd

123 openapi = app.openapi() 2Zb0b1b2b3b4b5b6b

124 body_model_name = get_body_model_name(openapi, path) 2Zb0b1b2b3b4b5b6b

125 

126 assert app.openapi()["components"]["schemas"][body_model_name] == { 2Zb0b1b2b3b4b5b6b

127 "properties": { 

128 "p_alias": { 

129 "anyOf": [{"type": "string"}, {"type": "null"}], 

130 "title": "P Alias", 

131 }, 

132 }, 

133 "title": body_model_name, 

134 "type": "object", 

135 } 

136 

137 

138def test_optional_alias_missing(): 1abcd

139 client = TestClient(app) 1yAEI

140 response = client.post("/optional-alias") 1yAEI

141 assert response.status_code == 200 1yAEI

142 assert response.json() == {"p": None} 1yAEI

143 

144 

145def test_model_optional_alias_missing(): 1abcd

146 client = TestClient(app) 2rbsbtbub

147 response = client.post("/model-optional-alias") 2rbsbtbub

148 assert response.status_code == 422, response.text 2rbsbtbub

149 assert response.json() == { 2rbsbtbub

150 "detail": [ 

151 { 

152 "input": None, 

153 "loc": ["body"], 

154 "msg": "Field required", 

155 "type": "missing", 

156 }, 

157 ], 

158 } 

159 

160 

161@pytest.mark.parametrize( 1abcd

162 "path", 

163 ["/optional-alias", "/model-optional-alias"], 

164) 

165def test_model_optional_alias_missing_empty_dict(path: str): 1abcd

166 client = TestClient(app) 1JvMzNBQF

167 response = client.post(path, json={}) 1JvMzNBQF

168 assert response.status_code == 200, response.text 1JvMzNBQF

169 assert response.json() == {"p": None} 1JvMzNBQF

170 

171 

172@pytest.mark.parametrize( 1abcd

173 "path", 

174 ["/optional-alias", "/model-optional-alias"], 

175) 

176def test_optional_alias_by_name(path: str): 1abcd

177 client = TestClient(app) 2L x vbwbP D S H

178 response = client.post(path, json={"p": "hello"}) 2L x vbwbP D S H

179 assert response.status_code == 200 2L x vbwbP D S H

180 assert response.json() == {"p": None} 2L x vbwbP D S H

181 

182 

183@pytest.mark.parametrize( 1abcd

184 "path", 

185 ["/optional-alias", "/model-optional-alias"], 

186) 

187def test_optional_alias_by_alias(path: str): 1abcd

188 client = TestClient(app) 2K w xbybO C R G

189 response = client.post(path, json={"p_alias": "hello"}) 2K w xbybO C R G

190 assert response.status_code == 200 2K w xbybO C R G

191 assert response.json() == {"p": "hello"} 2K w xbybO C R G

192 

193 

194# ===================================================================================== 

195# Validation alias 

196 

197 

198@app.post("/optional-validation-alias", operation_id="optional_validation_alias") 1abcd

199def read_optional_validation_alias( 1abcd

200 p: Annotated[str | None, Body(embed=True, validation_alias="p_val_alias")] = None, 

201): 

202 return {"p": p} 1TUVWXYZ0123456

203 

204 

205class BodyModelOptionalValidationAlias(BaseModel): 1abcd

206 p: str | None = Field(None, validation_alias="p_val_alias") 1abcd

207 

208 

209@app.post( 1abcd

210 "/model-optional-validation-alias", operation_id="model_optional_validation_alias" 

211) 

212def read_model_optional_validation_alias( 1abcd

213 p: BodyModelOptionalValidationAlias, 

214): 

215 return {"p": p.p} 1789!#$%'()*

216 

217 

218@pytest.mark.parametrize( 1abcd

219 "path", 

220 ["/optional-validation-alias", "/model-optional-validation-alias"], 

221) 

222def test_optional_validation_alias_schema(path: str): 1abcd

223 openapi = app.openapi() 27b8b9b!b#b$b%b'b

224 body_model_name = get_body_model_name(openapi, path) 27b8b9b!b#b$b%b'b

225 

226 assert app.openapi()["components"]["schemas"][body_model_name] == { 27b8b9b!b#b$b%b'b

227 "properties": { 

228 "p_val_alias": { 

229 "anyOf": [{"type": "string"}, {"type": "null"}], 

230 "title": "P Val Alias", 

231 }, 

232 }, 

233 "title": body_model_name, 

234 "type": "object", 

235 } 

236 

237 

238def test_optional_validation_alias_missing(): 1abcd

239 client = TestClient(app) 1WY26

240 response = client.post("/optional-validation-alias") 1WY26

241 assert response.status_code == 200 1WY26

242 assert response.json() == {"p": None} 1WY26

243 

244 

245def test_model_optional_validation_alias_missing(): 1abcd

246 client = TestClient(app) 2zbAbBbCb

247 response = client.post("/model-optional-validation-alias") 2zbAbBbCb

248 assert response.status_code == 422, response.text 2zbAbBbCb

249 assert response.json() == { 2zbAbBbCb

250 "detail": [ 

251 { 

252 "input": None, 

253 "loc": ["body"], 

254 "msg": "Field required", 

255 "type": "missing", 

256 }, 

257 ], 

258 } 

259 

260 

261@pytest.mark.parametrize( 1abcd

262 "path", 

263 ["/optional-validation-alias", "/model-optional-validation-alias"], 

264) 

265def test_model_optional_validation_alias_missing_empty_dict(path: str): 1abcd

266 client = TestClient(app) 17T!X$Z(3

267 response = client.post(path, json={}) 17T!X$Z(3

268 assert response.status_code == 200, response.text 17T!X$Z(3

269 assert response.json() == {"p": None} 17T!X$Z(3

270 

271 

272@pytest.mark.parametrize( 1abcd

273 "path", 

274 [ 

275 "/optional-validation-alias", 

276 "/model-optional-validation-alias", 

277 ], 

278) 

279def test_optional_validation_alias_by_name(path: str): 1abcd

280 client = TestClient(app) 28 U # Db% 0 ) 4

281 response = client.post(path, json={"p": "hello"}) 28 U # Db% 0 ) 4

282 assert response.status_code == 200 28 U # Db% 0 ) 4

283 assert response.json() == {"p": None} 28 U # Db% 0 ) 4

284 

285 

286@pytest.mark.parametrize( 1abcd

287 "path", 

288 [ 

289 "/optional-validation-alias", 

290 "/model-optional-validation-alias", 

291 ], 

292) 

293def test_optional_validation_alias_by_validation_alias(path: str): 1abcd

294 client = TestClient(app) 29 V EbFb' 1 * 5

295 response = client.post(path, json={"p_val_alias": "hello"}) 29 V EbFb' 1 * 5

296 assert response.status_code == 200 29 V EbFb' 1 * 5

297 assert response.json() == {"p": "hello"} 29 V EbFb' 1 * 5

298 

299 

300# ===================================================================================== 

301# Alias and validation alias 

302 

303 

304@app.post( 1abcd

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

306 operation_id="optional_alias_and_validation_alias", 

307) 

308def read_optional_alias_and_validation_alias( 1abcd

309 p: Annotated[ 

310 str | None, Body(embed=True, alias="p_alias", validation_alias="p_val_alias") 

311 ] = None, 

312): 

313 return {"p": p} 1+,-./:;=?@[]^_`{

314 

315 

316class BodyModelOptionalAliasAndValidationAlias(BaseModel): 1abcd

317 p: str | None = Field(None, alias="p_alias", validation_alias="p_val_alias") 1abcd

318 

319 

320@app.post( 1abcd

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

322 operation_id="model_optional_alias_and_validation_alias", 

323) 

324def read_model_optional_alias_and_validation_alias( 1abcd

325 p: BodyModelOptionalAliasAndValidationAlias, 

326): 

327 return {"p": p.p} 2| } ~ abbbcbdbebfbgbhbibjb

328 

329 

330@pytest.mark.parametrize( 1abcd

331 "path", 

332 [ 

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

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

335 ], 

336) 

337def test_optional_alias_and_validation_alias_schema(path: str): 1abcd

338 openapi = app.openapi() 2(b)b*b+b,b-b.b/b

339 body_model_name = get_body_model_name(openapi, path) 2(b)b*b+b,b-b.b/b

340 

341 assert app.openapi()["components"]["schemas"][body_model_name] == { 2(b)b*b+b,b-b.b/b

342 "properties": { 

343 "p_val_alias": { 

344 "anyOf": [{"type": "string"}, {"type": "null"}], 

345 "title": "P Val Alias", 

346 }, 

347 }, 

348 "title": body_model_name, 

349 "type": "object", 

350 } 

351 

352 

353def test_optional_alias_and_validation_alias_missing(): 1abcd

354 client = TestClient(app) 1/:[{

355 response = client.post("/optional-alias-and-validation-alias") 1/:[{

356 assert response.status_code == 200 1/:[{

357 assert response.json() == {"p": None} 1/:[{

358 

359 

360def test_model_optional_alias_and_validation_alias_missing(): 1abcd

361 client = TestClient(app) 2GbHbIbJb

362 response = client.post("/model-optional-alias-and-validation-alias") 2GbHbIbJb

363 assert response.status_code == 422, response.text 2GbHbIbJb

364 assert response.json() == { 2GbHbIbJb

365 "detail": [ 

366 { 

367 "input": None, 

368 "loc": ["body"], 

369 "msg": "Field required", 

370 "type": "missing", 

371 }, 

372 ], 

373 } 

374 

375 

376@pytest.mark.parametrize( 1abcd

377 "path", 

378 [ 

379 "/optional-alias-and-validation-alias", 

380 "/model-optional-alias-and-validation-alias", 

381 ], 

382) 

383def test_model_optional_alias_and_validation_alias_missing_empty_dict(path: str): 1abcd

384 client = TestClient(app) 2| + bbKbcb; gb]

385 response = client.post(path, json={}) 2| + bbKbcb; gb]

386 assert response.status_code == 200, response.text 2| + bbKbcb; gb]

387 assert response.json() == {"p": None} 2| + bbKbcb; gb]

388 

389 

390@pytest.mark.parametrize( 1abcd

391 "path", 

392 [ 

393 "/optional-alias-and-validation-alias", 

394 "/model-optional-alias-and-validation-alias", 

395 ], 

396) 

397def test_optional_alias_and_validation_alias_by_name(path: str): 1abcd

398 client = TestClient(app) 2~ - LbMbeb? ib_

399 response = client.post(path, json={"p": "hello"}) 2~ - LbMbeb? ib_

400 assert response.status_code == 200 2~ - LbMbeb? ib_

401 assert response.json() == {"p": None} 2~ - LbMbeb? ib_

402 

403 

404@pytest.mark.parametrize( 1abcd

405 "path", 

406 [ 

407 "/optional-alias-and-validation-alias", 

408 "/model-optional-alias-and-validation-alias", 

409 ], 

410) 

411def test_optional_alias_and_validation_alias_by_alias(path: str): 1abcd

412 client = TestClient(app) 2} , NbObdb= hb^

413 response = client.post(path, json={"p_alias": "hello"}) 2} , NbObdb= hb^

414 assert response.status_code == 200 2} , NbObdb= hb^

415 assert response.json() == {"p": None} 2} , NbObdb= hb^

416 

417 

418@pytest.mark.parametrize( 1abcd

419 "path", 

420 [ 

421 "/optional-alias-and-validation-alias", 

422 "/model-optional-alias-and-validation-alias", 

423 ], 

424) 

425def test_optional_alias_and_validation_alias_by_validation_alias(path: str): 1abcd

426 client = TestClient(app) 2ab. PbQbfb@ jb`

427 response = client.post(path, json={"p_val_alias": "hello"}) 2ab. PbQbfb@ jb`

428 assert response.status_code == 200 2ab. PbQbfb@ jb`

429 assert response.json() == {"p": "hello"} 2ab. PbQbfb@ jb`