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-02-21 17:29 +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} 1eOfgPhiQj

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} 1efghij

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() 1$%'

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

37 

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

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) 1OPQ

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

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

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

55 

56 

57def test_model_optional_str_missing(): 1abcd

58 client = TestClient(app) 1012

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

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

61 assert response.json() == { 1012

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) 1fhj

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

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

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

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) 1egi

90 response = client.post(path, json={"p": "hello"}) 1egi

91 assert response.status_code == 200 1egi

92 assert response.json() == {"p": "hello"} 1egi

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} 1klmRnopSqrsT

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} 1klmnopqrs

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() 1()*

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

125 

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

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) 1RST

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

141 assert response.status_code == 200 1RST

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

143 

144 

145def test_model_optional_alias_missing(): 1abcd

146 client = TestClient(app) 1345

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

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

149 assert response.json() == { 1345

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) 1knq

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

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

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

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) 1mps

178 response = client.post(path, json={"p": "hello"}) 1mps

179 assert response.status_code == 200 1mps

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

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) 1lor

189 response = client.post(path, json={"p_alias": "hello"}) 1lor

190 assert response.status_code == 200 1lor

191 assert response.json() == {"p": "hello"} 1lor

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} 1tuvUwxyVzABW

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} 1tuvwxyzAB

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() 1+,-

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

225 

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

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) 1UVW

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

241 assert response.status_code == 200 1UVW

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

243 

244 

245def test_model_optional_validation_alias_missing(): 1abcd

246 client = TestClient(app) 1678

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

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

249 assert response.json() == { 1678

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) 1twz

267 response = client.post(path, json={}) 1twz

268 assert response.status_code == 200, response.text 1twz

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

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) 1uxA

281 response = client.post(path, json={"p": "hello"}) 1uxA

282 assert response.status_code == 200 1uxA

283 assert response.json() == {"p": None} 1uxA

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) 1vyB

295 response = client.post(path, json={"p_val_alias": "hello"}) 1vyB

296 assert response.status_code == 200 1vyB

297 assert response.json() == {"p": "hello"} 1vyB

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} 1CDEFXGHIJYKLMNZ

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} 1CDEFGHIJKLMN

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() 1./:

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

340 

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

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) 1XYZ

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

356 assert response.status_code == 200 1XYZ

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

358 

359 

360def test_model_optional_alias_and_validation_alias_missing(): 1abcd

361 client = TestClient(app) 19!#

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

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

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

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) 1CGK

385 response = client.post(path, json={}) 1CGK

386 assert response.status_code == 200, response.text 1CGK

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

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) 1EIM

399 response = client.post(path, json={"p": "hello"}) 1EIM

400 assert response.status_code == 200 1EIM

401 assert response.json() == {"p": None} 1EIM

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) 1DHL

413 response = client.post(path, json={"p_alias": "hello"}) 1DHL

414 assert response.status_code == 200 1DHL

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

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) 1FJN

427 response = client.post(path, json={"p_val_alias": "hello"}) 1FJN

428 assert response.status_code == 200 1FJN

429 assert response.json() == {"p": "hello"} 1FJN