Coverage for tests/test_pydantic_v1_v2_noneable.py: 100%

142 statements  

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

1import sys 1abcdefs

2from typing import Any, List, Union 1abcdefs

3 

4from tests.utils import pydantic_snapshot, skip_module_if_py_gte_314 1abcdefs

5 

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

7 skip_module_if_py_gte_314() 1s

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

13from pydantic import BaseModel as NewBaseModel 1abcdef

14 

15 

16class SubItem(BaseModel): 1abcdef

17 name: str 1abcdef

18 

19 

20class Item(BaseModel): 1abcdef

21 title: str 1abcdef

22 size: int 1abcdef

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

24 sub: SubItem 1abcdef

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

26 

27 

28class NewSubItem(NewBaseModel): 1abcdef

29 new_sub_name: str 1abcdef

30 

31 

32class NewItem(NewBaseModel): 1abcdef

33 new_title: str 1abcdef

34 new_size: int 1abcdef

35 new_description: Union[str, None] = None 1abcdef

36 new_sub: NewSubItem 1abcdef

37 new_multi: List[NewSubItem] = [] 1abcdef

38 

39 

40app = FastAPI() 1abcdef

41 

42 

43@app.post("/v1-to-v2/") 1abcdef

44def handle_v1_item_to_v2(data: Item) -> Union[NewItem, None]: 1abcdef

45 if data.size < 0: 1tuvwxyzABCDEFGHIJKLMNOPQ

46 return None 1uyCGKO

47 return NewItem( 1tvwxzABDEFHIJLMNPQ

48 new_title=data.title, 

49 new_size=data.size, 

50 new_description=data.description, 

51 new_sub=NewSubItem(new_sub_name=data.sub.name), 

52 new_multi=[NewSubItem(new_sub_name=s.name) for s in data.multi], 

53 ) 

54 

55 

56@app.post("/v1-to-v2/item-filter", response_model=Union[NewItem, None]) 1abcdef

57def handle_v1_item_to_v2_filter(data: Item) -> Any: 1abcdef

58 if data.size < 0: 1RgShTiUjVkWl

59 return None 1RSTUVW

60 result = { 1ghijkl

61 "new_title": data.title, 

62 "new_size": data.size, 

63 "new_description": data.description, 

64 "new_sub": {"new_sub_name": data.sub.name, "new_sub_secret": "sub_hidden"}, 

65 "new_multi": [ 

66 {"new_sub_name": s.name, "new_sub_secret": "sub_hidden"} for s in data.multi 

67 ], 

68 "secret": "hidden_v1_to_v2", 

69 } 

70 return result 1ghijkl

71 

72 

73@app.post("/v2-to-v1/item") 1abcdef

74def handle_v2_item_to_v1(data: NewItem) -> Union[Item, None]: 1abcdef

75 if data.new_size < 0: 1XYZ0123456789!#$%'()*+,-

76 return None 1Y26!'+

77 return Item( 1XZ01345789#$%()*,-

78 title=data.new_title, 

79 size=data.new_size, 

80 description=data.new_description, 

81 sub=SubItem(name=data.new_sub.new_sub_name), 

82 multi=[SubItem(name=s.new_sub_name) for s in data.new_multi], 

83 ) 

84 

85 

86@app.post("/v2-to-v1/item-filter", response_model=Union[Item, None]) 1abcdef

87def handle_v2_item_to_v1_filter(data: NewItem) -> Any: 1abcdef

88 if data.new_size < 0: 1.m/n:o;p=q?r

89 return None 1./:;=?

90 result = { 1mnopqr

91 "title": data.new_title, 

92 "size": data.new_size, 

93 "description": data.new_description, 

94 "sub": {"name": data.new_sub.new_sub_name, "sub_secret": "sub_hidden"}, 

95 "multi": [ 

96 {"name": s.new_sub_name, "sub_secret": "sub_hidden"} for s in data.new_multi 

97 ], 

98 "secret": "hidden_v2_to_v1", 

99 } 

100 return result 1mnopqr

101 

102 

103client = TestClient(app) 1abcdef

104 

105 

106def test_v1_to_v2_item_success(): 1abcdef

107 response = client.post( 1vzDHLP

108 "/v1-to-v2/", 

109 json={ 

110 "title": "Old Item", 

111 "size": 100, 

112 "description": "V1 description", 

113 "sub": {"name": "V1 Sub"}, 

114 "multi": [{"name": "M1"}, {"name": "M2"}], 

115 }, 

116 ) 

117 assert response.status_code == 200, response.text 1vzDHLP

118 assert response.json() == { 1vzDHLP

119 "new_title": "Old Item", 

120 "new_size": 100, 

121 "new_description": "V1 description", 

122 "new_sub": {"new_sub_name": "V1 Sub"}, 

123 "new_multi": [{"new_sub_name": "M1"}, {"new_sub_name": "M2"}], 

124 } 

125 

126 

127def test_v1_to_v2_item_returns_none(): 1abcdef

128 response = client.post( 1uyCGKO

129 "/v1-to-v2/", 

130 json={"title": "Invalid Item", "size": -10, "sub": {"name": "Sub"}}, 

131 ) 

132 assert response.status_code == 200, response.text 1uyCGKO

133 assert response.json() is None 1uyCGKO

134 

135 

136def test_v1_to_v2_item_minimal(): 1abcdef

137 response = client.post( 1txBFJN

138 "/v1-to-v2/", json={"title": "Minimal", "size": 50, "sub": {"name": "MinSub"}} 

139 ) 

140 assert response.status_code == 200, response.text 1txBFJN

141 assert response.json() == { 1txBFJN

142 "new_title": "Minimal", 

143 "new_size": 50, 

144 "new_description": None, 

145 "new_sub": {"new_sub_name": "MinSub"}, 

146 "new_multi": [], 

147 } 

148 

149 

150def test_v1_to_v2_item_filter_success(): 1abcdef

151 response = client.post( 1ghijkl

152 "/v1-to-v2/item-filter", 

153 json={ 

154 "title": "Filtered Item", 

155 "size": 50, 

156 "sub": {"name": "Sub"}, 

157 "multi": [{"name": "Multi1"}], 

158 }, 

159 ) 

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

161 result = response.json() 1ghijkl

162 assert result["new_title"] == "Filtered Item" 1ghijkl

163 assert result["new_size"] == 50 1ghijkl

164 assert result["new_sub"]["new_sub_name"] == "Sub" 1ghijkl

165 assert result["new_multi"][0]["new_sub_name"] == "Multi1" 1ghijkl

166 # Verify secret fields are filtered out 

167 assert "secret" not in result 1ghijkl

168 assert "new_sub_secret" not in result["new_sub"] 1ghijkl

169 assert "new_sub_secret" not in result["new_multi"][0] 1ghijkl

170 

171 

172def test_v1_to_v2_item_filter_returns_none(): 1abcdef

173 response = client.post( 1RSTUVW

174 "/v1-to-v2/item-filter", 

175 json={"title": "Invalid", "size": -1, "sub": {"name": "Sub"}}, 

176 ) 

177 assert response.status_code == 200, response.text 1RSTUVW

178 assert response.json() is None 1RSTUVW

179 

180 

181def test_v2_to_v1_item_success(): 1abcdef

182 response = client.post( 1Z37#(,

183 "/v2-to-v1/item", 

184 json={ 

185 "new_title": "New Item", 

186 "new_size": 200, 

187 "new_description": "V2 description", 

188 "new_sub": {"new_sub_name": "V2 Sub"}, 

189 "new_multi": [{"new_sub_name": "N1"}, {"new_sub_name": "N2"}], 

190 }, 

191 ) 

192 assert response.status_code == 200, response.text 1Z37#(,

193 assert response.json() == { 1Z37#(,

194 "title": "New Item", 

195 "size": 200, 

196 "description": "V2 description", 

197 "sub": {"name": "V2 Sub"}, 

198 "multi": [{"name": "N1"}, {"name": "N2"}], 

199 } 

200 

201 

202def test_v2_to_v1_item_returns_none(): 1abcdef

203 response = client.post( 1Y26!'+

204 "/v2-to-v1/item", 

205 json={ 

206 "new_title": "Invalid New", 

207 "new_size": -5, 

208 "new_sub": {"new_sub_name": "NewSub"}, 

209 }, 

210 ) 

211 assert response.status_code == 200, response.text 1Y26!'+

212 assert response.json() is None 1Y26!'+

213 

214 

215def test_v2_to_v1_item_minimal(): 1abcdef

216 response = client.post( 1X159%*

217 "/v2-to-v1/item", 

218 json={ 

219 "new_title": "MinimalNew", 

220 "new_size": 75, 

221 "new_sub": {"new_sub_name": "MinNewSub"}, 

222 }, 

223 ) 

224 assert response.status_code == 200, response.text 1X159%*

225 assert response.json() == { 1X159%*

226 "title": "MinimalNew", 

227 "size": 75, 

228 "description": None, 

229 "sub": {"name": "MinNewSub"}, 

230 "multi": [], 

231 } 

232 

233 

234def test_v2_to_v1_item_filter_success(): 1abcdef

235 response = client.post( 1mnopqr

236 "/v2-to-v1/item-filter", 

237 json={ 

238 "new_title": "Filtered New", 

239 "new_size": 75, 

240 "new_sub": {"new_sub_name": "NewSub"}, 

241 "new_multi": [], 

242 }, 

243 ) 

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

245 result = response.json() 1mnopqr

246 assert result["title"] == "Filtered New" 1mnopqr

247 assert result["size"] == 75 1mnopqr

248 assert result["sub"]["name"] == "NewSub" 1mnopqr

249 # Verify secret fields are filtered out 

250 assert "secret" not in result 1mnopqr

251 assert "sub_secret" not in result["sub"] 1mnopqr

252 

253 

254def test_v2_to_v1_item_filter_returns_none(): 1abcdef

255 response = client.post( 1./:;=?

256 "/v2-to-v1/item-filter", 

257 json={ 

258 "new_title": "Invalid Filtered", 

259 "new_size": -100, 

260 "new_sub": {"new_sub_name": "Sub"}, 

261 }, 

262 ) 

263 assert response.status_code == 200, response.text 1./:;=?

264 assert response.json() is None 1./:;=?

265 

266 

267def test_v1_to_v2_validation_error(): 1abcdef

268 response = client.post("/v1-to-v2/", json={"title": "Missing fields"}) 2cbdbebfbgbhb

269 assert response.status_code == 422, response.text 2cbdbebfbgbhb

270 assert response.json() == snapshot( 2cbdbebfbgbhb

271 { 

272 "detail": [ 

273 { 

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

275 "msg": "field required", 

276 "type": "value_error.missing", 

277 }, 

278 { 

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

280 "msg": "field required", 

281 "type": "value_error.missing", 

282 }, 

283 ] 

284 } 

285 ) 

286 

287 

288def test_v1_to_v2_nested_validation_error(): 1abcdef

289 response = client.post( 1@[]^_`

290 "/v1-to-v2/", 

291 json={"title": "Bad sub", "size": 100, "sub": {"wrong_field": "value"}}, 

292 ) 

293 assert response.status_code == 422, response.text 1@[]^_`

294 error_detail = response.json()["detail"] 1@[]^_`

295 assert len(error_detail) == 1 1@[]^_`

296 assert error_detail[0]["loc"] == ["body", "sub", "name"] 1@[]^_`

297 

298 

299def test_v1_to_v2_type_validation_error(): 1abcdef

300 response = client.post( 2{ | } ~ abbb

301 "/v1-to-v2/", 

302 json={"title": "Bad type", "size": "not_a_number", "sub": {"name": "Sub"}}, 

303 ) 

304 assert response.status_code == 422, response.text 2{ | } ~ abbb

305 error_detail = response.json()["detail"] 2{ | } ~ abbb

306 assert len(error_detail) == 1 2{ | } ~ abbb

307 assert error_detail[0]["loc"] == ["body", "size"] 2{ | } ~ abbb

308 

309 

310def test_v2_to_v1_validation_error(): 1abcdef

311 response = client.post("/v2-to-v1/item", json={"new_title": "Missing fields"}) 2ibjbkblbmbnb

312 assert response.status_code == 422, response.text 2ibjbkblbmbnb

313 assert response.json() == snapshot( 2ibjbkblbmbnb

314 { 

315 "detail": pydantic_snapshot( 

316 v2=snapshot( 

317 [ 

318 { 

319 "type": "missing", 

320 "loc": ["body", "new_size"], 

321 "msg": "Field required", 

322 "input": {"new_title": "Missing fields"}, 

323 }, 

324 { 

325 "type": "missing", 

326 "loc": ["body", "new_sub"], 

327 "msg": "Field required", 

328 "input": {"new_title": "Missing fields"}, 

329 }, 

330 ] 

331 ), 

332 v1=snapshot( 

333 [ 

334 { 

335 "loc": ["body", "new_size"], 

336 "msg": "field required", 

337 "type": "value_error.missing", 

338 }, 

339 { 

340 "loc": ["body", "new_sub"], 

341 "msg": "field required", 

342 "type": "value_error.missing", 

343 }, 

344 ] 

345 ), 

346 ) 

347 } 

348 ) 

349 

350 

351def test_v2_to_v1_nested_validation_error(): 1abcdef

352 response = client.post( 2obpbqbrbsbtb

353 "/v2-to-v1/item", 

354 json={ 

355 "new_title": "Bad sub", 

356 "new_size": 200, 

357 "new_sub": {"wrong_field": "value"}, 

358 }, 

359 ) 

360 assert response.status_code == 422, response.text 2obpbqbrbsbtb

361 assert response.json() == snapshot( 2obpbqbrbsbtb

362 { 

363 "detail": [ 

364 pydantic_snapshot( 

365 v2=snapshot( 

366 { 

367 "type": "missing", 

368 "loc": ["body", "new_sub", "new_sub_name"], 

369 "msg": "Field required", 

370 "input": {"wrong_field": "value"}, 

371 } 

372 ), 

373 v1=snapshot( 

374 { 

375 "loc": ["body", "new_sub", "new_sub_name"], 

376 "msg": "field required", 

377 "type": "value_error.missing", 

378 } 

379 ), 

380 ) 

381 ] 

382 } 

383 ) 

384 

385 

386def test_v2_to_v1_type_validation_error(): 1abcdef

387 response = client.post( 2ubvbwbxbybzb

388 "/v2-to-v1/item", 

389 json={ 

390 "new_title": "Bad type", 

391 "new_size": "not_a_number", 

392 "new_sub": {"new_sub_name": "Sub"}, 

393 }, 

394 ) 

395 assert response.status_code == 422, response.text 2ubvbwbxbybzb

396 assert response.json() == snapshot( 2ubvbwbxbybzb

397 { 

398 "detail": [ 

399 pydantic_snapshot( 

400 v2=snapshot( 

401 { 

402 "type": "int_parsing", 

403 "loc": ["body", "new_size"], 

404 "msg": "Input should be a valid integer, unable to parse string as an integer", 

405 "input": "not_a_number", 

406 } 

407 ), 

408 v1=snapshot( 

409 { 

410 "loc": ["body", "new_size"], 

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

412 "type": "type_error.integer", 

413 } 

414 ), 

415 ) 

416 ] 

417 } 

418 ) 

419 

420 

421def test_v1_to_v2_with_multi_items(): 1abcdef

422 response = client.post( 1wAEIMQ

423 "/v1-to-v2/", 

424 json={ 

425 "title": "Complex Item", 

426 "size": 300, 

427 "description": "Item with multiple sub-items", 

428 "sub": {"name": "Main Sub"}, 

429 "multi": [{"name": "Sub1"}, {"name": "Sub2"}, {"name": "Sub3"}], 

430 }, 

431 ) 

432 assert response.status_code == 200, response.text 1wAEIMQ

433 assert response.json() == snapshot( 1wAEIMQ

434 { 

435 "new_title": "Complex Item", 

436 "new_size": 300, 

437 "new_description": "Item with multiple sub-items", 

438 "new_sub": {"new_sub_name": "Main Sub"}, 

439 "new_multi": [ 

440 {"new_sub_name": "Sub1"}, 

441 {"new_sub_name": "Sub2"}, 

442 {"new_sub_name": "Sub3"}, 

443 ], 

444 } 

445 ) 

446 

447 

448def test_v2_to_v1_with_multi_items(): 1abcdef

449 response = client.post( 1048$)-

450 "/v2-to-v1/item", 

451 json={ 

452 "new_title": "Complex New Item", 

453 "new_size": 400, 

454 "new_description": "New item with multiple sub-items", 

455 "new_sub": {"new_sub_name": "Main New Sub"}, 

456 "new_multi": [{"new_sub_name": "NewSub1"}, {"new_sub_name": "NewSub2"}], 

457 }, 

458 ) 

459 assert response.status_code == 200, response.text 1048$)-

460 assert response.json() == snapshot( 1048$)-

461 { 

462 "title": "Complex New Item", 

463 "size": 400, 

464 "description": "New item with multiple sub-items", 

465 "sub": {"name": "Main New Sub"}, 

466 "multi": [{"name": "NewSub1"}, {"name": "NewSub2"}], 

467 } 

468 ) 

469 

470 

471def test_openapi_schema(): 1abcdef

472 response = client.get("/openapi.json") 2AbBbCbDbEbFb

473 assert response.status_code == 200, response.text 2AbBbCbDbEbFb

474 assert response.json() == snapshot( 2AbBbCbDbEbFb

475 { 

476 "openapi": "3.1.0", 

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

478 "paths": { 

479 "/v1-to-v2/": { 

480 "post": { 

481 "summary": "Handle V1 Item To V2", 

482 "operationId": "handle_v1_item_to_v2_v1_to_v2__post", 

483 "requestBody": { 

484 "content": { 

485 "application/json": { 

486 "schema": pydantic_snapshot( 

487 v2=snapshot( 

488 { 

489 "allOf": [ 

490 { 

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

492 } 

493 ], 

494 "title": "Data", 

495 } 

496 ), 

497 v1=snapshot( 

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

499 ), 

500 ) 

501 } 

502 }, 

503 "required": True, 

504 }, 

505 "responses": { 

506 "200": { 

507 "description": "Successful Response", 

508 "content": { 

509 "application/json": { 

510 "schema": pydantic_snapshot( 

511 v2=snapshot( 

512 { 

513 "anyOf": [ 

514 { 

515 "$ref": "#/components/schemas/NewItem" 

516 }, 

517 {"type": "null"}, 

518 ], 

519 "title": "Response Handle V1 Item To V2 V1 To V2 Post", 

520 } 

521 ), 

522 v1=snapshot( 

523 {"$ref": "#/components/schemas/NewItem"} 

524 ), 

525 ) 

526 } 

527 }, 

528 }, 

529 "422": { 

530 "description": "Validation Error", 

531 "content": { 

532 "application/json": { 

533 "schema": { 

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

535 } 

536 } 

537 }, 

538 }, 

539 }, 

540 } 

541 }, 

542 "/v1-to-v2/item-filter": { 

543 "post": { 

544 "summary": "Handle V1 Item To V2 Filter", 

545 "operationId": "handle_v1_item_to_v2_filter_v1_to_v2_item_filter_post", 

546 "requestBody": { 

547 "content": { 

548 "application/json": { 

549 "schema": pydantic_snapshot( 

550 v2=snapshot( 

551 { 

552 "allOf": [ 

553 { 

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

555 } 

556 ], 

557 "title": "Data", 

558 } 

559 ), 

560 v1=snapshot( 

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

562 ), 

563 ) 

564 } 

565 }, 

566 "required": True, 

567 }, 

568 "responses": { 

569 "200": { 

570 "description": "Successful Response", 

571 "content": { 

572 "application/json": { 

573 "schema": pydantic_snapshot( 

574 v2=snapshot( 

575 { 

576 "anyOf": [ 

577 { 

578 "$ref": "#/components/schemas/NewItem" 

579 }, 

580 {"type": "null"}, 

581 ], 

582 "title": "Response Handle V1 Item To V2 Filter V1 To V2 Item Filter Post", 

583 } 

584 ), 

585 v1=snapshot( 

586 {"$ref": "#/components/schemas/NewItem"} 

587 ), 

588 ) 

589 } 

590 }, 

591 }, 

592 "422": { 

593 "description": "Validation Error", 

594 "content": { 

595 "application/json": { 

596 "schema": { 

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

598 } 

599 } 

600 }, 

601 }, 

602 }, 

603 } 

604 }, 

605 "/v2-to-v1/item": { 

606 "post": { 

607 "summary": "Handle V2 Item To V1", 

608 "operationId": "handle_v2_item_to_v1_v2_to_v1_item_post", 

609 "requestBody": { 

610 "content": { 

611 "application/json": { 

612 "schema": {"$ref": "#/components/schemas/NewItem"} 

613 } 

614 }, 

615 "required": True, 

616 }, 

617 "responses": { 

618 "200": { 

619 "description": "Successful Response", 

620 "content": { 

621 "application/json": { 

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

623 } 

624 }, 

625 }, 

626 "422": { 

627 "description": "Validation Error", 

628 "content": { 

629 "application/json": { 

630 "schema": { 

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

632 } 

633 } 

634 }, 

635 }, 

636 }, 

637 } 

638 }, 

639 "/v2-to-v1/item-filter": { 

640 "post": { 

641 "summary": "Handle V2 Item To V1 Filter", 

642 "operationId": "handle_v2_item_to_v1_filter_v2_to_v1_item_filter_post", 

643 "requestBody": { 

644 "content": { 

645 "application/json": { 

646 "schema": {"$ref": "#/components/schemas/NewItem"} 

647 } 

648 }, 

649 "required": True, 

650 }, 

651 "responses": { 

652 "200": { 

653 "description": "Successful Response", 

654 "content": { 

655 "application/json": { 

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

657 } 

658 }, 

659 }, 

660 "422": { 

661 "description": "Validation Error", 

662 "content": { 

663 "application/json": { 

664 "schema": { 

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

666 } 

667 } 

668 }, 

669 }, 

670 }, 

671 } 

672 }, 

673 }, 

674 "components": { 

675 "schemas": { 

676 "HTTPValidationError": { 

677 "properties": { 

678 "detail": { 

679 "items": { 

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

681 }, 

682 "type": "array", 

683 "title": "Detail", 

684 } 

685 }, 

686 "type": "object", 

687 "title": "HTTPValidationError", 

688 }, 

689 "Item": { 

690 "properties": { 

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

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

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

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

695 "multi": { 

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

697 "type": "array", 

698 "title": "Multi", 

699 "default": [], 

700 }, 

701 }, 

702 "type": "object", 

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

704 "title": "Item", 

705 }, 

706 "NewItem": { 

707 "properties": { 

708 "new_title": {"type": "string", "title": "New Title"}, 

709 "new_size": {"type": "integer", "title": "New Size"}, 

710 "new_description": pydantic_snapshot( 

711 v2=snapshot( 

712 { 

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

714 "title": "New Description", 

715 } 

716 ), 

717 v1=snapshot( 

718 {"type": "string", "title": "New Description"} 

719 ), 

720 ), 

721 "new_sub": {"$ref": "#/components/schemas/NewSubItem"}, 

722 "new_multi": { 

723 "items": {"$ref": "#/components/schemas/NewSubItem"}, 

724 "type": "array", 

725 "title": "New Multi", 

726 "default": [], 

727 }, 

728 }, 

729 "type": "object", 

730 "required": ["new_title", "new_size", "new_sub"], 

731 "title": "NewItem", 

732 }, 

733 "NewSubItem": { 

734 "properties": { 

735 "new_sub_name": {"type": "string", "title": "New Sub Name"} 

736 }, 

737 "type": "object", 

738 "required": ["new_sub_name"], 

739 "title": "NewSubItem", 

740 }, 

741 "SubItem": { 

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

743 "type": "object", 

744 "required": ["name"], 

745 "title": "SubItem", 

746 }, 

747 "ValidationError": { 

748 "properties": { 

749 "loc": { 

750 "items": { 

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

752 }, 

753 "type": "array", 

754 "title": "Location", 

755 }, 

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

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

758 }, 

759 "type": "object", 

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

761 "title": "ValidationError", 

762 }, 

763 } 

764 }, 

765 } 

766 )