Coverage for tests/test_openapi_separate_input_output_schemas.py: 100%

73 statements  

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

1from typing import List, Optional 1abcdefg

2 

3from fastapi import FastAPI 1abcdefg

4from fastapi.testclient import TestClient 1abcdefg

5from inline_snapshot import snapshot 1abcdefg

6from pydantic import BaseModel 1abcdefg

7 

8from .utils import PYDANTIC_V2, needs_pydanticv2 1abcdefg

9 

10 

11class SubItem(BaseModel): 1abcdefg

12 subname: str 1abcdefg

13 sub_description: Optional[str] = None 1abcdefg

14 tags: List[str] = [] 1abcdefg

15 if PYDANTIC_V2: 1abcdefg

16 model_config = {"json_schema_serialization_defaults_required": True} 1abcdefg

17 

18 

19class Item(BaseModel): 1abcdefg

20 name: str 1abcdefg

21 description: Optional[str] = None 1abcdefg

22 sub: Optional[SubItem] = None 1abcdefg

23 if PYDANTIC_V2: 1abcdefg

24 model_config = {"json_schema_serialization_defaults_required": True} 1abcdefg

25 

26 

27def get_app_client(separate_input_output_schemas: bool = True) -> TestClient: 1abcdefg

28 app = FastAPI(separate_input_output_schemas=separate_input_output_schemas) 1vhiJKwxjkLMyzlmNOABnoPQCDpqRSEFrsTUGHtuVWI

29 

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

31 def create_item(item: Item) -> Item: 1vhiJKwxjkLMyzlmNOABnoPQCDpqRSEFrsTUGHtuVWI

32 return item 1vixkzmBoDqFsHu

33 

34 @app.post("/items-list/") 1vhiJKwxjkLMyzlmNOABnoPQCDpqRSEFrsTUGHtuVWI

35 def create_item_list(item: List[Item]): 1vhiJKwxjkLMyzlmNOABnoPQCDpqRSEFrsTUGHtuVWI

36 return item 1hjlnprt

37 

38 @app.get("/items/") 1vhiJKwxjkLMyzlmNOABnoPQCDpqRSEFrsTUGHtuVWI

39 def read_items() -> List[Item]: 1vhiJKwxjkLMyzlmNOABnoPQCDpqRSEFrsTUGHtuVWI

40 return [ 1wyACEGI

41 Item( 

42 name="Portal Gun", 

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

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

45 ), 

46 Item(name="Plumbus"), 

47 ] 

48 

49 client = TestClient(app) 1vhiJKwxjkLMyzlmNOABnoPQCDpqRSEFrsTUGHtuVWI

50 return client 1vhiJKwxjkLMyzlmNOABnoPQCDpqRSEFrsTUGHtuVWI

51 

52 

53def test_create_item(): 1abcdefg

54 client = get_app_client() 1vxzBDFH

55 client_no = get_app_client(separate_input_output_schemas=False) 1vxzBDFH

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

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

58 assert response.status_code == response2.status_code == 200, response.text 1vxzBDFH

59 assert ( 1vxzBDFH

60 response.json() 

61 == response2.json() 

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

63 ) 

64 

65 

66def test_create_item_with_sub(): 1abcdefg

67 client = get_app_client() 1ikmoqsu

68 client_no = get_app_client(separate_input_output_schemas=False) 1ikmoqsu

69 data = { 1ikmoqsu

70 "name": "Plumbus", 

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

72 } 

73 response = client.post("/items/", json=data) 1ikmoqsu

74 response2 = client_no.post("/items/", json=data) 1ikmoqsu

75 assert response.status_code == response2.status_code == 200, response.text 1ikmoqsu

76 assert ( 1ikmoqsu

77 response.json() 

78 == response2.json() 

79 == { 

80 "name": "Plumbus", 

81 "description": None, 

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

83 } 

84 ) 

85 

86 

87def test_create_item_list(): 1abcdefg

88 client = get_app_client() 1hjlnprt

89 client_no = get_app_client(separate_input_output_schemas=False) 1hjlnprt

90 data = [ 1hjlnprt

91 {"name": "Plumbus"}, 

92 { 

93 "name": "Portal Gun", 

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

95 }, 

96 ] 

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

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

99 assert response.status_code == response2.status_code == 200, response.text 1hjlnprt

100 assert ( 1hjlnprt

101 response.json() 

102 == response2.json() 

103 == [ 

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

105 { 

106 "name": "Portal Gun", 

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

108 "sub": None, 

109 }, 

110 ] 

111 ) 

112 

113 

114def test_read_items(): 1abcdefg

115 client = get_app_client() 1wyACEGI

116 client_no = get_app_client(separate_input_output_schemas=False) 1wyACEGI

117 response = client.get("/items/") 1wyACEGI

118 response2 = client_no.get("/items/") 1wyACEGI

119 assert response.status_code == response2.status_code == 200, response.text 1wyACEGI

120 assert ( 1wyACEGI

121 response.json() 

122 == response2.json() 

123 == [ 

124 { 

125 "name": "Portal Gun", 

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

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

128 }, 

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

130 ] 

131 ) 

132 

133 

134@needs_pydanticv2 1abcdefg

135def test_openapi_schema(): 1abcdefg

136 client = get_app_client() 1JLNPRTV

137 response = client.get("/openapi.json") 1JLNPRTV

138 assert response.status_code == 200, response.text 1JLNPRTV

139 assert response.json() == snapshot( 1JLNPRTV

140 { 

141 "openapi": "3.1.0", 

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

143 "paths": { 

144 "/items/": { 

145 "get": { 

146 "summary": "Read Items", 

147 "operationId": "read_items_items__get", 

148 "responses": { 

149 "200": { 

150 "description": "Successful Response", 

151 "content": { 

152 "application/json": { 

153 "schema": { 

154 "items": { 

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

156 }, 

157 "type": "array", 

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

159 } 

160 } 

161 }, 

162 } 

163 }, 

164 }, 

165 "post": { 

166 "summary": "Create Item", 

167 "operationId": "create_item_items__post", 

168 "requestBody": { 

169 "content": { 

170 "application/json": { 

171 "schema": { 

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

173 } 

174 } 

175 }, 

176 "required": True, 

177 }, 

178 "responses": { 

179 "200": { 

180 "description": "Successful Response", 

181 "content": { 

182 "application/json": { 

183 "schema": { 

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

185 } 

186 } 

187 }, 

188 }, 

189 "402": { 

190 "description": "Payment Required", 

191 "content": { 

192 "application/json": { 

193 "schema": { 

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

195 } 

196 } 

197 }, 

198 }, 

199 "422": { 

200 "description": "Validation Error", 

201 "content": { 

202 "application/json": { 

203 "schema": { 

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

205 } 

206 } 

207 }, 

208 }, 

209 }, 

210 }, 

211 }, 

212 "/items-list/": { 

213 "post": { 

214 "summary": "Create Item List", 

215 "operationId": "create_item_list_items_list__post", 

216 "requestBody": { 

217 "content": { 

218 "application/json": { 

219 "schema": { 

220 "items": { 

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

222 }, 

223 "type": "array", 

224 "title": "Item", 

225 } 

226 } 

227 }, 

228 "required": True, 

229 }, 

230 "responses": { 

231 "200": { 

232 "description": "Successful Response", 

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

234 }, 

235 "422": { 

236 "description": "Validation Error", 

237 "content": { 

238 "application/json": { 

239 "schema": { 

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

241 } 

242 } 

243 }, 

244 }, 

245 }, 

246 } 

247 }, 

248 }, 

249 "components": { 

250 "schemas": { 

251 "HTTPValidationError": { 

252 "properties": { 

253 "detail": { 

254 "items": { 

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

256 }, 

257 "type": "array", 

258 "title": "Detail", 

259 } 

260 }, 

261 "type": "object", 

262 "title": "HTTPValidationError", 

263 }, 

264 "Item-Input": { 

265 "properties": { 

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

267 "description": { 

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

269 "title": "Description", 

270 }, 

271 "sub": { 

272 "anyOf": [ 

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

274 {"type": "null"}, 

275 ] 

276 }, 

277 }, 

278 "type": "object", 

279 "required": ["name"], 

280 "title": "Item", 

281 }, 

282 "Item-Output": { 

283 "properties": { 

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

285 "description": { 

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

287 "title": "Description", 

288 }, 

289 "sub": { 

290 "anyOf": [ 

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

292 {"type": "null"}, 

293 ] 

294 }, 

295 }, 

296 "type": "object", 

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

298 "title": "Item", 

299 }, 

300 "SubItem-Input": { 

301 "properties": { 

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

303 "sub_description": { 

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

305 "title": "Sub Description", 

306 }, 

307 "tags": { 

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

309 "type": "array", 

310 "title": "Tags", 

311 "default": [], 

312 }, 

313 }, 

314 "type": "object", 

315 "required": ["subname"], 

316 "title": "SubItem", 

317 }, 

318 "SubItem-Output": { 

319 "properties": { 

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

321 "sub_description": { 

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

323 "title": "Sub Description", 

324 }, 

325 "tags": { 

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

327 "type": "array", 

328 "title": "Tags", 

329 "default": [], 

330 }, 

331 }, 

332 "type": "object", 

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

334 "title": "SubItem", 

335 }, 

336 "ValidationError": { 

337 "properties": { 

338 "loc": { 

339 "items": { 

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

341 }, 

342 "type": "array", 

343 "title": "Location", 

344 }, 

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

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

347 }, 

348 "type": "object", 

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

350 "title": "ValidationError", 

351 }, 

352 } 

353 }, 

354 } 

355 ) 

356 

357 

358@needs_pydanticv2 1abcdefg

359def test_openapi_schema_no_separate(): 1abcdefg

360 client = get_app_client(separate_input_output_schemas=False) 1KMOQSUW

361 response = client.get("/openapi.json") 1KMOQSUW

362 assert response.status_code == 200, response.text 1KMOQSUW

363 assert response.json() == { 1KMOQSUW

364 "openapi": "3.1.0", 

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

366 "paths": { 

367 "/items/": { 

368 "get": { 

369 "summary": "Read Items", 

370 "operationId": "read_items_items__get", 

371 "responses": { 

372 "200": { 

373 "description": "Successful Response", 

374 "content": { 

375 "application/json": { 

376 "schema": { 

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

378 "type": "array", 

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

380 } 

381 } 

382 }, 

383 } 

384 }, 

385 }, 

386 "post": { 

387 "summary": "Create Item", 

388 "operationId": "create_item_items__post", 

389 "requestBody": { 

390 "content": { 

391 "application/json": { 

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

393 } 

394 }, 

395 "required": True, 

396 }, 

397 "responses": { 

398 "200": { 

399 "description": "Successful Response", 

400 "content": { 

401 "application/json": { 

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

403 } 

404 }, 

405 }, 

406 "402": { 

407 "description": "Payment Required", 

408 "content": { 

409 "application/json": { 

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

411 } 

412 }, 

413 }, 

414 "422": { 

415 "description": "Validation Error", 

416 "content": { 

417 "application/json": { 

418 "schema": { 

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

420 } 

421 } 

422 }, 

423 }, 

424 }, 

425 }, 

426 }, 

427 "/items-list/": { 

428 "post": { 

429 "summary": "Create Item List", 

430 "operationId": "create_item_list_items_list__post", 

431 "requestBody": { 

432 "content": { 

433 "application/json": { 

434 "schema": { 

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

436 "type": "array", 

437 "title": "Item", 

438 } 

439 } 

440 }, 

441 "required": True, 

442 }, 

443 "responses": { 

444 "200": { 

445 "description": "Successful Response", 

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

447 }, 

448 "422": { 

449 "description": "Validation Error", 

450 "content": { 

451 "application/json": { 

452 "schema": { 

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

454 } 

455 } 

456 }, 

457 }, 

458 }, 

459 } 

460 }, 

461 }, 

462 "components": { 

463 "schemas": { 

464 "HTTPValidationError": { 

465 "properties": { 

466 "detail": { 

467 "items": {"$ref": "#/components/schemas/ValidationError"}, 

468 "type": "array", 

469 "title": "Detail", 

470 } 

471 }, 

472 "type": "object", 

473 "title": "HTTPValidationError", 

474 }, 

475 "Item": { 

476 "properties": { 

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

478 "description": { 

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

480 "title": "Description", 

481 }, 

482 "sub": { 

483 "anyOf": [ 

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

485 {"type": "null"}, 

486 ] 

487 }, 

488 }, 

489 "type": "object", 

490 "required": ["name"], 

491 "title": "Item", 

492 }, 

493 "SubItem": { 

494 "properties": { 

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

496 "sub_description": { 

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

498 "title": "Sub Description", 

499 }, 

500 "tags": { 

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

502 "type": "array", 

503 "title": "Tags", 

504 "default": [], 

505 }, 

506 }, 

507 "type": "object", 

508 "required": ["subname"], 

509 "title": "SubItem", 

510 }, 

511 "ValidationError": { 

512 "properties": { 

513 "loc": { 

514 "items": { 

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

516 }, 

517 "type": "array", 

518 "title": "Location", 

519 }, 

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

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

522 }, 

523 "type": "object", 

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

525 "title": "ValidationError", 

526 }, 

527 } 

528 }, 

529 }