Coverage for tests / test_openapi_separate_input_output_schemas.py: 100%

84 statements  

« prev     ^ index     » next       coverage.py v7.13.3, created at 2026-02-12 18:15 +0000

1from typing import Optional 1abcd

2 

3from fastapi import FastAPI 1abcd

4from fastapi.testclient import TestClient 1abcd

5from inline_snapshot import snapshot 1abcd

6from pydantic import BaseModel, computed_field 1abcd

7 

8 

9class SubItem(BaseModel): 1abcd

10 subname: str 1abcd

11 sub_description: Optional[str] = None 1abcd

12 tags: list[str] = [] 1abcd

13 model_config = {"json_schema_serialization_defaults_required": True} 1abcd

14 

15 

16class Item(BaseModel): 1abcd

17 name: str 1abcd

18 description: Optional[str] = None 1abcd

19 sub: Optional[SubItem] = None 1abcd

20 model_config = {"json_schema_serialization_defaults_required": True} 1abcd

21 

22 

23class WithComputedField(BaseModel): 1abcd

24 name: str 1abcd

25 

26 @computed_field 1abcd

27 @property 1abcd

28 def computed_field(self) -> str: 1abcd

29 return f"computed {self.name}" 1efk

30 

31 

32def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: 1abcd

33 app = FastAPI(separate_input_output_schemas=separate_input_output_schemas) 1lghtumenijvwofrpqxysk

34 

35 @app.post("/items/", responses={402: {"model": Item}}) 1lghtumenijvwofrpqxysk

36 def create_item(item: Item) -> Item: 1lghtumenijvwofrpqxysk

37 return item 1lhnjrq

38 

39 @app.post("/items-list/") 1lghtumenijvwofrpqxysk

40 def create_item_list(item: list[Item]): 1lghtumenijvwofrpqxysk

41 return item 1gip

42 

43 @app.get("/items/") 1lghtumenijvwofrpqxysk

44 def read_items() -> list[Item]: 1lghtumenijvwofrpqxysk

45 return [ 1mos

46 Item( 

47 name="Portal Gun", 

48 description="Device to travel through the multi-rick-verse", 

49 sub=SubItem(subname="subname"), 

50 ), 

51 Item(name="Plumbus"), 

52 ] 

53 

54 @app.post("/with-computed-field/") 1lghtumenijvwofrpqxysk

55 def create_with_computed_field( 1lghtumenijvwofrpqxysk

56 with_computed_field: WithComputedField, 

57 ) -> WithComputedField: 

58 return with_computed_field 1efk

59 

60 client = TestClient(app) 1lghtumenijvwofrpqxysk

61 return client 1lghtumenijvwofrpqxysk

62 

63 

64def test_create_item(): 1abcd

65 client = get_app_client() 1lnr

66 client_no = get_app_client(separate_input_output_schemas=False) 1lnr

67 response = client.post("/items/", json={"name": "Plumbus"}) 1lnr

68 response2 = client_no.post("/items/", json={"name": "Plumbus"}) 1lnr

69 assert response.status_code == response2.status_code == 200, response.text 1lnr

70 assert ( 1ln

71 response.json() 

72 == response2.json() 

73 == {"name": "Plumbus", "description": None, "sub": None} 

74 ) 

75 

76 

77def test_create_item_with_sub(): 1abcd

78 client = get_app_client() 1hjq

79 client_no = get_app_client(separate_input_output_schemas=False) 1hjq

80 data = { 1hjq

81 "name": "Plumbus", 

82 "sub": {"subname": "SubPlumbus", "sub_description": "Sub WTF"}, 

83 } 

84 response = client.post("/items/", json=data) 1hjq

85 response2 = client_no.post("/items/", json=data) 1hjq

86 assert response.status_code == response2.status_code == 200, response.text 1hjq

87 assert ( 1hj

88 response.json() 

89 == response2.json() 

90 == { 

91 "name": "Plumbus", 

92 "description": None, 

93 "sub": {"subname": "SubPlumbus", "sub_description": "Sub WTF", "tags": []}, 

94 } 

95 ) 

96 

97 

98def test_create_item_list(): 1abcd

99 client = get_app_client() 1gip

100 client_no = get_app_client(separate_input_output_schemas=False) 1gip

101 data = [ 1gip

102 {"name": "Plumbus"}, 

103 { 

104 "name": "Portal Gun", 

105 "description": "Device to travel through the multi-rick-verse", 

106 }, 

107 ] 

108 response = client.post("/items-list/", json=data) 1gip

109 response2 = client_no.post("/items-list/", json=data) 1gip

110 assert response.status_code == response2.status_code == 200, response.text 1gip

111 assert ( 1gi

112 response.json() 

113 == response2.json() 

114 == [ 

115 {"name": "Plumbus", "description": None, "sub": None}, 

116 { 

117 "name": "Portal Gun", 

118 "description": "Device to travel through the multi-rick-verse", 

119 "sub": None, 

120 }, 

121 ] 

122 ) 

123 

124 

125def test_read_items(): 1abcd

126 client = get_app_client() 1mos

127 client_no = get_app_client(separate_input_output_schemas=False) 1mos

128 response = client.get("/items/") 1mos

129 response2 = client_no.get("/items/") 1mos

130 assert response.status_code == response2.status_code == 200, response.text 1mos

131 assert ( 1mo

132 response.json() 

133 == response2.json() 

134 == [ 

135 { 

136 "name": "Portal Gun", 

137 "description": "Device to travel through the multi-rick-verse", 

138 "sub": {"subname": "subname", "sub_description": None, "tags": []}, 

139 }, 

140 {"name": "Plumbus", "description": None, "sub": None}, 

141 ] 

142 ) 

143 

144 

145def test_with_computed_field(): 1abcd

146 client = get_app_client() 1efk

147 client_no = get_app_client(separate_input_output_schemas=False) 1efk

148 response = client.post("/with-computed-field/", json={"name": "example"}) 1efk

149 response2 = client_no.post("/with-computed-field/", json={"name": "example"}) 1efk

150 assert response.status_code == response2.status_code == 200, response.text 1efk

151 assert ( 1ef

152 response.json() 

153 == response2.json() 

154 == { 

155 "name": "example", 

156 "computed_field": "computed example", 

157 } 

158 ) 

159 

160 

161def test_openapi_schema(): 1abcd

162 client = get_app_client() 1tvx

163 response = client.get("/openapi.json") 1tvx

164 assert response.status_code == 200, response.text 1tvx

165 assert response.json() == snapshot( 1tvx

166 { 

167 "openapi": "3.1.0", 

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

169 "paths": { 

170 "/items/": { 

171 "get": { 

172 "summary": "Read Items", 

173 "operationId": "read_items_items__get", 

174 "responses": { 

175 "200": { 

176 "description": "Successful Response", 

177 "content": { 

178 "application/json": { 

179 "schema": { 

180 "items": { 

181 "$ref": "#/components/schemas/Item-Output" 

182 }, 

183 "type": "array", 

184 "title": "Response Read Items Items Get", 

185 } 

186 } 

187 }, 

188 } 

189 }, 

190 }, 

191 "post": { 

192 "summary": "Create Item", 

193 "operationId": "create_item_items__post", 

194 "requestBody": { 

195 "content": { 

196 "application/json": { 

197 "schema": { 

198 "$ref": "#/components/schemas/Item-Input" 

199 } 

200 } 

201 }, 

202 "required": True, 

203 }, 

204 "responses": { 

205 "200": { 

206 "description": "Successful Response", 

207 "content": { 

208 "application/json": { 

209 "schema": { 

210 "$ref": "#/components/schemas/Item-Output" 

211 } 

212 } 

213 }, 

214 }, 

215 "402": { 

216 "description": "Payment Required", 

217 "content": { 

218 "application/json": { 

219 "schema": { 

220 "$ref": "#/components/schemas/Item-Output" 

221 } 

222 } 

223 }, 

224 }, 

225 "422": { 

226 "description": "Validation Error", 

227 "content": { 

228 "application/json": { 

229 "schema": { 

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

231 } 

232 } 

233 }, 

234 }, 

235 }, 

236 }, 

237 }, 

238 "/items-list/": { 

239 "post": { 

240 "summary": "Create Item List", 

241 "operationId": "create_item_list_items_list__post", 

242 "requestBody": { 

243 "content": { 

244 "application/json": { 

245 "schema": { 

246 "items": { 

247 "$ref": "#/components/schemas/Item-Input" 

248 }, 

249 "type": "array", 

250 "title": "Item", 

251 } 

252 } 

253 }, 

254 "required": True, 

255 }, 

256 "responses": { 

257 "200": { 

258 "description": "Successful Response", 

259 "content": {"application/json": {"schema": {}}}, 

260 }, 

261 "422": { 

262 "description": "Validation Error", 

263 "content": { 

264 "application/json": { 

265 "schema": { 

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

267 } 

268 } 

269 }, 

270 }, 

271 }, 

272 } 

273 }, 

274 "/with-computed-field/": { 

275 "post": { 

276 "summary": "Create With Computed Field", 

277 "operationId": "create_with_computed_field_with_computed_field__post", 

278 "requestBody": { 

279 "content": { 

280 "application/json": { 

281 "schema": { 

282 "$ref": "#/components/schemas/WithComputedField-Input" 

283 } 

284 } 

285 }, 

286 "required": True, 

287 }, 

288 "responses": { 

289 "200": { 

290 "description": "Successful Response", 

291 "content": { 

292 "application/json": { 

293 "schema": { 

294 "$ref": "#/components/schemas/WithComputedField-Output" 

295 } 

296 } 

297 }, 

298 }, 

299 "422": { 

300 "description": "Validation Error", 

301 "content": { 

302 "application/json": { 

303 "schema": { 

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

305 } 

306 } 

307 }, 

308 }, 

309 }, 

310 }, 

311 }, 

312 }, 

313 "components": { 

314 "schemas": { 

315 "HTTPValidationError": { 

316 "properties": { 

317 "detail": { 

318 "items": { 

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

320 }, 

321 "type": "array", 

322 "title": "Detail", 

323 } 

324 }, 

325 "type": "object", 

326 "title": "HTTPValidationError", 

327 }, 

328 "Item-Input": { 

329 "properties": { 

330 "name": {"type": "string", "title": "Name"}, 

331 "description": { 

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

333 "title": "Description", 

334 }, 

335 "sub": { 

336 "anyOf": [ 

337 {"$ref": "#/components/schemas/SubItem-Input"}, 

338 {"type": "null"}, 

339 ] 

340 }, 

341 }, 

342 "type": "object", 

343 "required": ["name"], 

344 "title": "Item", 

345 }, 

346 "Item-Output": { 

347 "properties": { 

348 "name": {"type": "string", "title": "Name"}, 

349 "description": { 

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

351 "title": "Description", 

352 }, 

353 "sub": { 

354 "anyOf": [ 

355 {"$ref": "#/components/schemas/SubItem-Output"}, 

356 {"type": "null"}, 

357 ] 

358 }, 

359 }, 

360 "type": "object", 

361 "required": ["name", "description", "sub"], 

362 "title": "Item", 

363 }, 

364 "SubItem-Input": { 

365 "properties": { 

366 "subname": {"type": "string", "title": "Subname"}, 

367 "sub_description": { 

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

369 "title": "Sub Description", 

370 }, 

371 "tags": { 

372 "items": {"type": "string"}, 

373 "type": "array", 

374 "title": "Tags", 

375 "default": [], 

376 }, 

377 }, 

378 "type": "object", 

379 "required": ["subname"], 

380 "title": "SubItem", 

381 }, 

382 "SubItem-Output": { 

383 "properties": { 

384 "subname": {"type": "string", "title": "Subname"}, 

385 "sub_description": { 

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

387 "title": "Sub Description", 

388 }, 

389 "tags": { 

390 "items": {"type": "string"}, 

391 "type": "array", 

392 "title": "Tags", 

393 "default": [], 

394 }, 

395 }, 

396 "type": "object", 

397 "required": ["subname", "sub_description", "tags"], 

398 "title": "SubItem", 

399 }, 

400 "WithComputedField-Input": { 

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

402 "type": "object", 

403 "required": ["name"], 

404 "title": "WithComputedField", 

405 }, 

406 "WithComputedField-Output": { 

407 "properties": { 

408 "name": {"type": "string", "title": "Name"}, 

409 "computed_field": { 

410 "type": "string", 

411 "title": "Computed Field", 

412 "readOnly": True, 

413 }, 

414 }, 

415 "type": "object", 

416 "required": ["name", "computed_field"], 

417 "title": "WithComputedField", 

418 }, 

419 "ValidationError": { 

420 "properties": { 

421 "ctx": {"title": "Context", "type": "object"}, 

422 "input": {"title": "Input"}, 

423 "loc": { 

424 "items": { 

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

426 }, 

427 "type": "array", 

428 "title": "Location", 

429 }, 

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

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

432 }, 

433 "type": "object", 

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

435 "title": "ValidationError", 

436 }, 

437 } 

438 }, 

439 } 

440 ) 

441 

442 

443def test_openapi_schema_no_separate(): 1abcd

444 client = get_app_client(separate_input_output_schemas=False) 1uwy

445 response = client.get("/openapi.json") 1uwy

446 assert response.status_code == 200, response.text 1uwy

447 assert response.json() == snapshot( 1uwy

448 { 

449 "openapi": "3.1.0", 

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

451 "paths": { 

452 "/items/": { 

453 "get": { 

454 "summary": "Read Items", 

455 "operationId": "read_items_items__get", 

456 "responses": { 

457 "200": { 

458 "description": "Successful Response", 

459 "content": { 

460 "application/json": { 

461 "schema": { 

462 "items": { 

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

464 }, 

465 "type": "array", 

466 "title": "Response Read Items Items Get", 

467 } 

468 } 

469 }, 

470 } 

471 }, 

472 }, 

473 "post": { 

474 "summary": "Create Item", 

475 "operationId": "create_item_items__post", 

476 "requestBody": { 

477 "content": { 

478 "application/json": { 

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

480 } 

481 }, 

482 "required": True, 

483 }, 

484 "responses": { 

485 "200": { 

486 "description": "Successful Response", 

487 "content": { 

488 "application/json": { 

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

490 } 

491 }, 

492 }, 

493 "402": { 

494 "description": "Payment Required", 

495 "content": { 

496 "application/json": { 

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

498 } 

499 }, 

500 }, 

501 "422": { 

502 "description": "Validation Error", 

503 "content": { 

504 "application/json": { 

505 "schema": { 

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

507 } 

508 } 

509 }, 

510 }, 

511 }, 

512 }, 

513 }, 

514 "/items-list/": { 

515 "post": { 

516 "summary": "Create Item List", 

517 "operationId": "create_item_list_items_list__post", 

518 "requestBody": { 

519 "content": { 

520 "application/json": { 

521 "schema": { 

522 "items": {"$ref": "#/components/schemas/Item"}, 

523 "type": "array", 

524 "title": "Item", 

525 } 

526 } 

527 }, 

528 "required": True, 

529 }, 

530 "responses": { 

531 "200": { 

532 "description": "Successful Response", 

533 "content": {"application/json": {"schema": {}}}, 

534 }, 

535 "422": { 

536 "description": "Validation Error", 

537 "content": { 

538 "application/json": { 

539 "schema": { 

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

541 } 

542 } 

543 }, 

544 }, 

545 }, 

546 } 

547 }, 

548 "/with-computed-field/": { 

549 "post": { 

550 "summary": "Create With Computed Field", 

551 "operationId": "create_with_computed_field_with_computed_field__post", 

552 "requestBody": { 

553 "content": { 

554 "application/json": { 

555 "schema": { 

556 "$ref": "#/components/schemas/WithComputedField-Input" 

557 } 

558 } 

559 }, 

560 "required": True, 

561 }, 

562 "responses": { 

563 "200": { 

564 "description": "Successful Response", 

565 "content": { 

566 "application/json": { 

567 "schema": { 

568 "$ref": "#/components/schemas/WithComputedField-Output" 

569 } 

570 } 

571 }, 

572 }, 

573 "422": { 

574 "description": "Validation Error", 

575 "content": { 

576 "application/json": { 

577 "schema": { 

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

579 } 

580 } 

581 }, 

582 }, 

583 }, 

584 }, 

585 }, 

586 }, 

587 "components": { 

588 "schemas": { 

589 "HTTPValidationError": { 

590 "properties": { 

591 "detail": { 

592 "items": { 

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

594 }, 

595 "type": "array", 

596 "title": "Detail", 

597 } 

598 }, 

599 "type": "object", 

600 "title": "HTTPValidationError", 

601 }, 

602 "Item": { 

603 "properties": { 

604 "name": {"type": "string", "title": "Name"}, 

605 "description": { 

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

607 "title": "Description", 

608 }, 

609 "sub": { 

610 "anyOf": [ 

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

612 {"type": "null"}, 

613 ] 

614 }, 

615 }, 

616 "type": "object", 

617 "required": ["name"], 

618 "title": "Item", 

619 }, 

620 "SubItem": { 

621 "properties": { 

622 "subname": {"type": "string", "title": "Subname"}, 

623 "sub_description": { 

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

625 "title": "Sub Description", 

626 }, 

627 "tags": { 

628 "items": {"type": "string"}, 

629 "type": "array", 

630 "title": "Tags", 

631 "default": [], 

632 }, 

633 }, 

634 "type": "object", 

635 "required": ["subname"], 

636 "title": "SubItem", 

637 }, 

638 "WithComputedField-Input": { 

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

640 "type": "object", 

641 "required": ["name"], 

642 "title": "WithComputedField", 

643 }, 

644 "WithComputedField-Output": { 

645 "properties": { 

646 "name": {"type": "string", "title": "Name"}, 

647 "computed_field": { 

648 "type": "string", 

649 "title": "Computed Field", 

650 "readOnly": True, 

651 }, 

652 }, 

653 "type": "object", 

654 "required": ["name", "computed_field"], 

655 "title": "WithComputedField", 

656 }, 

657 "ValidationError": { 

658 "properties": { 

659 "ctx": {"title": "Context", "type": "object"}, 

660 "input": {"title": "Input"}, 

661 "loc": { 

662 "items": { 

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

664 }, 

665 "type": "array", 

666 "title": "Location", 

667 }, 

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

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

670 }, 

671 "type": "object", 

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

673 "title": "ValidationError", 

674 }, 

675 } 

676 }, 

677 } 

678 )