Coverage for tests/test_pydantic_v1_v2_01.py: 100%

82 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-12-04 08:29 +0000

1import sys 1abcdefy

2from typing import Any, List, Union 1abcdefy

3 

4from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 1abcdefy

5 

6if sys.version_info >= (3, 14): 1abcdefy

7 skip_module_if_py_gte_314() 1y

8 

9from fastapi import FastAPI 1abcdef

10from fastapi._compat.v1 import BaseModel 1abcdef

11from fastapi.testclient import TestClient 1abcdef

12from inline_snapshot import snapshot 1abcdef

13 

14 

15class SubItem(BaseModel): 1abcdef

16 name: str 1abcdef

17 

18 

19class Item(BaseModel): 1abcdef

20 title: str 1abcdef

21 size: int 1abcdef

22 description: Union[str, None] = None 1abcdef

23 sub: SubItem 1abcdef

24 multi: List[SubItem] = [] 1abcdef

25 

26 

27app = FastAPI() 1abcdef

28 

29 

30@app.post("/simple-model") 1abcdef

31def handle_simple_model(data: SubItem) -> SubItem: 1abcdef

32 return data 1zABCDE

33 

34 

35@app.post("/simple-model-filter", response_model=SubItem) 1abcdef

36def handle_simple_model_filter(data: SubItem) -> Any: 1abcdef

37 extended_data = data.dict() 1mnopqr

38 extended_data.update({"secret_price": 42}) 1mnopqr

39 return extended_data 1mnopqr

40 

41 

42@app.post("/item") 1abcdef

43def handle_item(data: Item) -> Item: 1abcdef

44 return data 1FGHIJKLMNOPQ

45 

46 

47@app.post("/item-filter", response_model=Item) 1abcdef

48def handle_item_filter(data: Item) -> Any: 1abcdef

49 extended_data = data.dict() 1ghijkl

50 extended_data.update({"secret_data": "classified", "internal_id": 12345}) 1ghijkl

51 extended_data["sub"].update({"internal_id": 67890}) 1ghijkl

52 return extended_data 1ghijkl

53 

54 

55client = TestClient(app) 1abcdef

56 

57 

58def test_old_simple_model(): 1abcdef

59 response = client.post( 1zABCDE

60 "/simple-model", 

61 json={"name": "Foo"}, 

62 ) 

63 assert response.status_code == 200, response.text 1zABCDE

64 assert response.json() == {"name": "Foo"} 1zABCDE

65 

66 

67def test_old_simple_model_validation_error(): 1abcdef

68 response = client.post( 1RSTUVW

69 "/simple-model", 

70 json={"wrong_name": "Foo"}, 

71 ) 

72 assert response.status_code == 422, response.text 1RSTUVW

73 assert response.json() == snapshot( 1RSTUVW

74 { 

75 "detail": [ 

76 { 

77 "loc": ["body", "name"], 

78 "msg": "field required", 

79 "type": "value_error.missing", 

80 } 

81 ] 

82 } 

83 ) 

84 

85 

86def test_old_simple_model_filter(): 1abcdef

87 response = client.post( 1mnopqr

88 "/simple-model-filter", 

89 json={"name": "Foo"}, 

90 ) 

91 assert response.status_code == 200, response.text 1mnopqr

92 assert response.json() == {"name": "Foo"} 1mnopqr

93 

94 

95def test_item_model(): 1abcdef

96 response = client.post( 1FHJLNP

97 "/item", 

98 json={ 

99 "title": "Test Item", 

100 "size": 100, 

101 "description": "This is a test item", 

102 "sub": {"name": "SubItem1"}, 

103 "multi": [{"name": "Multi1"}, {"name": "Multi2"}], 

104 }, 

105 ) 

106 assert response.status_code == 200, response.text 1FHJLNP

107 assert response.json() == { 1FHJLNP

108 "title": "Test Item", 

109 "size": 100, 

110 "description": "This is a test item", 

111 "sub": {"name": "SubItem1"}, 

112 "multi": [{"name": "Multi1"}, {"name": "Multi2"}], 

113 } 

114 

115 

116def test_item_model_minimal(): 1abcdef

117 response = client.post( 1GIKMOQ

118 "/item", 

119 json={"title": "Minimal Item", "size": 50, "sub": {"name": "SubMin"}}, 

120 ) 

121 assert response.status_code == 200, response.text 1GIKMOQ

122 assert response.json() == { 1GIKMOQ

123 "title": "Minimal Item", 

124 "size": 50, 

125 "description": None, 

126 "sub": {"name": "SubMin"}, 

127 "multi": [], 

128 } 

129 

130 

131def test_item_model_validation_errors(): 1abcdef

132 response = client.post( 1stuvwx

133 "/item", 

134 json={"title": "Missing fields"}, 

135 ) 

136 assert response.status_code == 422, response.text 1stuvwx

137 error_detail = response.json()["detail"] 1stuvwx

138 assert len(error_detail) == 2 1stuvwx

139 assert { 1stuvwx

140 "loc": ["body", "size"], 

141 "msg": "field required", 

142 "type": "value_error.missing", 

143 } in error_detail 

144 assert { 1stuvwx

145 "loc": ["body", "sub"], 

146 "msg": "field required", 

147 "type": "value_error.missing", 

148 } in error_detail 

149 

150 

151def test_item_model_nested_validation_error(): 1abcdef

152 response = client.post( 1XYZ012

153 "/item", 

154 json={"title": "Test Item", "size": 100, "sub": {"wrong_field": "test"}}, 

155 ) 

156 assert response.status_code == 422, response.text 1XYZ012

157 assert response.json() == snapshot( 1XYZ012

158 { 

159 "detail": [ 

160 { 

161 "loc": ["body", "sub", "name"], 

162 "msg": "field required", 

163 "type": "value_error.missing", 

164 } 

165 ] 

166 } 

167 ) 

168 

169 

170def test_item_model_invalid_type(): 1abcdef

171 response = client.post( 1345678

172 "/item", 

173 json={"title": "Test Item", "size": "not_a_number", "sub": {"name": "SubItem"}}, 

174 ) 

175 assert response.status_code == 422, response.text 1345678

176 assert response.json() == snapshot( 1345678

177 { 

178 "detail": [ 

179 { 

180 "loc": ["body", "size"], 

181 "msg": "value is not a valid integer", 

182 "type": "type_error.integer", 

183 } 

184 ] 

185 } 

186 ) 

187 

188 

189def test_item_filter(): 1abcdef

190 response = client.post( 1ghijkl

191 "/item-filter", 

192 json={ 

193 "title": "Filtered Item", 

194 "size": 200, 

195 "description": "Test filtering", 

196 "sub": {"name": "SubFiltered"}, 

197 "multi": [], 

198 }, 

199 ) 

200 assert response.status_code == 200, response.text 1ghijkl

201 result = response.json() 1ghijkl

202 assert result == { 1ghijkl

203 "title": "Filtered Item", 

204 "size": 200, 

205 "description": "Test filtering", 

206 "sub": {"name": "SubFiltered"}, 

207 "multi": [], 

208 } 

209 assert "secret_data" not in result 1ghijkl

210 assert "internal_id" not in result 1ghijkl

211 

212 

213def test_openapi_schema(): 1abcdef

214 response = client.get("/openapi.json") 19!#$%'

215 assert response.status_code == 200, response.text 19!#$%'

216 assert response.json() == snapshot( 19!#$%'

217 { 

218 "openapi": "3.1.0", 

219 "info": {"title": "FastAPI", "version": "0.1.0"}, 

220 "paths": { 

221 "/simple-model": { 

222 "post": { 

223 "summary": "Handle Simple Model", 

224 "operationId": "handle_simple_model_simple_model_post", 

225 "requestBody": { 

226 "content": { 

227 "application/json": { 

228 "schema": pydantic_snapshot( 

229 v2=snapshot( 

230 { 

231 "allOf": [ 

232 { 

233 "$ref": "#/components/schemas/SubItem" 

234 } 

235 ], 

236 "title": "Data", 

237 } 

238 ), 

239 v1=snapshot( 

240 {"$ref": "#/components/schemas/SubItem"} 

241 ), 

242 ) 

243 } 

244 }, 

245 "required": True, 

246 }, 

247 "responses": { 

248 "200": { 

249 "description": "Successful Response", 

250 "content": { 

251 "application/json": { 

252 "schema": { 

253 "$ref": "#/components/schemas/SubItem" 

254 } 

255 } 

256 }, 

257 }, 

258 "422": { 

259 "description": "Validation Error", 

260 "content": { 

261 "application/json": { 

262 "schema": { 

263 "$ref": "#/components/schemas/HTTPValidationError" 

264 } 

265 } 

266 }, 

267 }, 

268 }, 

269 } 

270 }, 

271 "/simple-model-filter": { 

272 "post": { 

273 "summary": "Handle Simple Model Filter", 

274 "operationId": "handle_simple_model_filter_simple_model_filter_post", 

275 "requestBody": { 

276 "content": { 

277 "application/json": { 

278 "schema": pydantic_snapshot( 

279 v2=snapshot( 

280 { 

281 "allOf": [ 

282 { 

283 "$ref": "#/components/schemas/SubItem" 

284 } 

285 ], 

286 "title": "Data", 

287 } 

288 ), 

289 v1=snapshot( 

290 {"$ref": "#/components/schemas/SubItem"} 

291 ), 

292 ) 

293 } 

294 }, 

295 "required": True, 

296 }, 

297 "responses": { 

298 "200": { 

299 "description": "Successful Response", 

300 "content": { 

301 "application/json": { 

302 "schema": { 

303 "$ref": "#/components/schemas/SubItem" 

304 } 

305 } 

306 }, 

307 }, 

308 "422": { 

309 "description": "Validation Error", 

310 "content": { 

311 "application/json": { 

312 "schema": { 

313 "$ref": "#/components/schemas/HTTPValidationError" 

314 } 

315 } 

316 }, 

317 }, 

318 }, 

319 } 

320 }, 

321 "/item": { 

322 "post": { 

323 "summary": "Handle Item", 

324 "operationId": "handle_item_item_post", 

325 "requestBody": { 

326 "content": { 

327 "application/json": { 

328 "schema": pydantic_snapshot( 

329 v2=snapshot( 

330 { 

331 "allOf": [ 

332 { 

333 "$ref": "#/components/schemas/Item" 

334 } 

335 ], 

336 "title": "Data", 

337 } 

338 ), 

339 v1=snapshot( 

340 {"$ref": "#/components/schemas/Item"} 

341 ), 

342 ) 

343 } 

344 }, 

345 "required": True, 

346 }, 

347 "responses": { 

348 "200": { 

349 "description": "Successful Response", 

350 "content": { 

351 "application/json": { 

352 "schema": {"$ref": "#/components/schemas/Item"} 

353 } 

354 }, 

355 }, 

356 "422": { 

357 "description": "Validation Error", 

358 "content": { 

359 "application/json": { 

360 "schema": { 

361 "$ref": "#/components/schemas/HTTPValidationError" 

362 } 

363 } 

364 }, 

365 }, 

366 }, 

367 } 

368 }, 

369 "/item-filter": { 

370 "post": { 

371 "summary": "Handle Item Filter", 

372 "operationId": "handle_item_filter_item_filter_post", 

373 "requestBody": { 

374 "content": { 

375 "application/json": { 

376 "schema": pydantic_snapshot( 

377 v2=snapshot( 

378 { 

379 "allOf": [ 

380 { 

381 "$ref": "#/components/schemas/Item" 

382 } 

383 ], 

384 "title": "Data", 

385 } 

386 ), 

387 v1=snapshot( 

388 {"$ref": "#/components/schemas/Item"} 

389 ), 

390 ) 

391 } 

392 }, 

393 "required": True, 

394 }, 

395 "responses": { 

396 "200": { 

397 "description": "Successful Response", 

398 "content": { 

399 "application/json": { 

400 "schema": {"$ref": "#/components/schemas/Item"} 

401 } 

402 }, 

403 }, 

404 "422": { 

405 "description": "Validation Error", 

406 "content": { 

407 "application/json": { 

408 "schema": { 

409 "$ref": "#/components/schemas/HTTPValidationError" 

410 } 

411 } 

412 }, 

413 }, 

414 }, 

415 } 

416 }, 

417 }, 

418 "components": { 

419 "schemas": { 

420 "HTTPValidationError": { 

421 "properties": { 

422 "detail": { 

423 "items": { 

424 "$ref": "#/components/schemas/ValidationError" 

425 }, 

426 "type": "array", 

427 "title": "Detail", 

428 } 

429 }, 

430 "type": "object", 

431 "title": "HTTPValidationError", 

432 }, 

433 "Item": { 

434 "properties": { 

435 "title": {"type": "string", "title": "Title"}, 

436 "size": {"type": "integer", "title": "Size"}, 

437 "description": {"type": "string", "title": "Description"}, 

438 "sub": {"$ref": "#/components/schemas/SubItem"}, 

439 "multi": { 

440 "items": {"$ref": "#/components/schemas/SubItem"}, 

441 "type": "array", 

442 "title": "Multi", 

443 "default": [], 

444 }, 

445 }, 

446 "type": "object", 

447 "required": ["title", "size", "sub"], 

448 "title": "Item", 

449 }, 

450 "SubItem": { 

451 "properties": {"name": {"type": "string", "title": "Name"}}, 

452 "type": "object", 

453 "required": ["name"], 

454 "title": "SubItem", 

455 }, 

456 "ValidationError": { 

457 "properties": { 

458 "loc": { 

459 "items": { 

460 "anyOf": [{"type": "string"}, {"type": "integer"}] 

461 }, 

462 "type": "array", 

463 "title": "Location", 

464 }, 

465 "msg": {"type": "string", "title": "Message"}, 

466 "type": {"type": "string", "title": "Error Type"}, 

467 }, 

468 "type": "object", 

469 "required": ["loc", "msg", "type"], 

470 "title": "ValidationError", 

471 }, 

472 } 

473 }, 

474 } 

475 )