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

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

18 return {"p": p} 1eOfgPhiQj

19 

20 

21class BodyModelOptionalStr(BaseModel): 1abcd

22 p: Optional[str] = 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[Optional[str], Body(embed=True, alias="p_alias")] = None, 

102): 

103 return {"p": p} 1klmRnopSqrsT

104 

105 

106class BodyModelOptionalAlias(BaseModel): 1abcd

107 p: Optional[str] = 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[ 

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

202 ] = None, 

203): 

204 return {"p": p} 1tuvUwxyVzABW

205 

206 

207class BodyModelOptionalValidationAlias(BaseModel): 1abcd

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

209 

210 

211@app.post( 1abcd

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

213) 

214def read_model_optional_validation_alias( 1abcd

215 p: BodyModelOptionalValidationAlias, 

216): 

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

218 

219 

220@pytest.mark.parametrize( 1abcd

221 "path", 

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

223) 

224def test_optional_validation_alias_schema(path: str): 1abcd

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

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

227 

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

229 "properties": { 

230 "p_val_alias": { 

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

232 "title": "P Val Alias", 

233 }, 

234 }, 

235 "title": body_model_name, 

236 "type": "object", 

237 } 

238 

239 

240def test_optional_validation_alias_missing(): 1abcd

241 client = TestClient(app) 1UVW

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

243 assert response.status_code == 200 1UVW

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

245 

246 

247def test_model_optional_validation_alias_missing(): 1abcd

248 client = TestClient(app) 1678

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

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

251 assert response.json() == { 1678

252 "detail": [ 

253 { 

254 "input": None, 

255 "loc": ["body"], 

256 "msg": "Field required", 

257 "type": "missing", 

258 }, 

259 ], 

260 } 

261 

262 

263@pytest.mark.parametrize( 1abcd

264 "path", 

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

266) 

267def test_model_optional_validation_alias_missing_empty_dict(path: str): 1abcd

268 client = TestClient(app) 1twz

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

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

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

272 

273 

274@pytest.mark.parametrize( 1abcd

275 "path", 

276 [ 

277 "/optional-validation-alias", 

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

279 ], 

280) 

281def test_optional_validation_alias_by_name(path: str): 1abcd

282 client = TestClient(app) 1uxA

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

284 assert response.status_code == 200 1uxA

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

286 

287 

288@pytest.mark.parametrize( 1abcd

289 "path", 

290 [ 

291 "/optional-validation-alias", 

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

293 ], 

294) 

295def test_optional_validation_alias_by_validation_alias(path: str): 1abcd

296 client = TestClient(app) 1vyB

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

298 assert response.status_code == 200 1vyB

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

300 

301 

302# ===================================================================================== 

303# Alias and validation alias 

304 

305 

306@app.post( 1abcd

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

308 operation_id="optional_alias_and_validation_alias", 

309) 

310def read_optional_alias_and_validation_alias( 1abcd

311 p: Annotated[ 

312 Optional[str], Body(embed=True, alias="p_alias", validation_alias="p_val_alias") 

313 ] = None, 

314): 

315 return {"p": p} 1CDEFXGHIJYKLMNZ

316 

317 

318class BodyModelOptionalAliasAndValidationAlias(BaseModel): 1abcd

319 p: Optional[str] = Field(None, alias="p_alias", validation_alias="p_val_alias") 1abcd

320 

321 

322@app.post( 1abcd

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

324 operation_id="model_optional_alias_and_validation_alias", 

325) 

326def read_model_optional_alias_and_validation_alias( 1abcd

327 p: BodyModelOptionalAliasAndValidationAlias, 

328): 

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

330 

331 

332@pytest.mark.parametrize( 1abcd

333 "path", 

334 [ 

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

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

337 ], 

338) 

339def test_optional_alias_and_validation_alias_schema(path: str): 1abcd

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

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

342 

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

344 "properties": { 

345 "p_val_alias": { 

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

347 "title": "P Val Alias", 

348 }, 

349 }, 

350 "title": body_model_name, 

351 "type": "object", 

352 } 

353 

354 

355def test_optional_alias_and_validation_alias_missing(): 1abcd

356 client = TestClient(app) 1XYZ

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

358 assert response.status_code == 200 1XYZ

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

360 

361 

362def test_model_optional_alias_and_validation_alias_missing(): 1abcd

363 client = TestClient(app) 19!#

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

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

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

367 "detail": [ 

368 { 

369 "input": None, 

370 "loc": ["body"], 

371 "msg": "Field required", 

372 "type": "missing", 

373 }, 

374 ], 

375 } 

376 

377 

378@pytest.mark.parametrize( 1abcd

379 "path", 

380 [ 

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

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

383 ], 

384) 

385def test_model_optional_alias_and_validation_alias_missing_empty_dict(path: str): 1abcd

386 client = TestClient(app) 1CGK

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

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

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

390 

391 

392@pytest.mark.parametrize( 1abcd

393 "path", 

394 [ 

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

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

397 ], 

398) 

399def test_optional_alias_and_validation_alias_by_name(path: str): 1abcd

400 client = TestClient(app) 1EIM

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

402 assert response.status_code == 200 1EIM

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

404 

405 

406@pytest.mark.parametrize( 1abcd

407 "path", 

408 [ 

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

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

411 ], 

412) 

413def test_optional_alias_and_validation_alias_by_alias(path: str): 1abcd

414 client = TestClient(app) 1DHL

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

416 assert response.status_code == 200 1DHL

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

418 

419 

420@pytest.mark.parametrize( 1abcd

421 "path", 

422 [ 

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

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

425 ], 

426) 

427def test_optional_alias_and_validation_alias_by_validation_alias(path: str): 1abcd

428 client = TestClient(app) 1FJN

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

430 assert response.status_code == 200 1FJN

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