Skip to content

Commit 9bf0911

Browse files
committed
Support multi content type in request body and responses
1 parent a64533d commit 9bf0911

24 files changed

+687
-148
lines changed

docs/Usage/Request.md

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,88 @@ def get_book(query: BookQuery, client_id:str = None):
167167
...
168168
```
169169

170+
## Multiple content types in the request body
171+
172+
```python
173+
from typing import Union
174+
175+
from flask import Request
176+
from pydantic import BaseModel
177+
178+
from flask_openapi3 import OpenAPI
179+
180+
app = OpenAPI(__name__)
181+
182+
183+
class DogBody(BaseModel):
184+
a: int = None
185+
b: str = None
186+
187+
model_config = {
188+
"openapi_extra": {
189+
"content_type": "application/vnd.dog+json"
190+
}
191+
}
192+
193+
194+
class CatBody(BaseModel):
195+
c: int = None
196+
d: str = None
197+
198+
model_config = {
199+
"openapi_extra": {
200+
"content_type": "application/vnd.cat+json"
201+
}
202+
}
203+
204+
205+
class BsonModel(BaseModel):
206+
e: int = None
207+
f: str = None
208+
209+
model_config = {
210+
"openapi_extra": {
211+
"content_type": "application/bson"
212+
}
213+
}
214+
215+
216+
class ContentTypeModel(BaseModel):
217+
model_config = {
218+
"openapi_extra": {
219+
"content_type": "text/csv"
220+
}
221+
}
222+
223+
224+
@app.post("/a", responses={200: DogBody | CatBody | ContentTypeModel | BsonModel})
225+
def index_a(body: DogBody | CatBody | ContentTypeModel | BsonModel):
226+
"""
227+
multiple content types examples.
228+
229+
This may be confusing, if the content-type is application/json, the type of body will be auto parsed to
230+
DogBody or CatBody, otherwise it cannot be parsed to ContentTypeModel or BsonModel.
231+
The body is equivalent to the request variable in Flask, and you can use body.data, body.text, etc ...
232+
"""
233+
print(body)
234+
if isinstance(body, Request):
235+
if body.mimetype == "text/csv":
236+
# processing csv data
237+
...
238+
elif body.mimetype == "application/bson":
239+
# processing bson data
240+
...
241+
else:
242+
# DogBody or CatBody
243+
...
244+
return {"hello": "world"}
245+
```
246+
247+
The effect in swagger:
248+
249+
![](../assets/Snipaste_2025-01-14_10-44-00.png)
250+
251+
170252
## Request model
171253

172254
First, you need to define a [pydantic](https://github.com/pydantic/pydantic) model:
@@ -191,7 +273,7 @@ class BookQuery(BaseModel):
191273
author: str = Field(None, description='Author', json_schema_extra={"deprecated": True})
192274
```
193275

194-
Magic:
276+
The effect in swagger:
195277

196278
![](../assets/Snipaste_2022-09-04_10-10-03.png)
197279

docs/Usage/Response.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,122 @@ def hello(path: HelloPath):
5656

5757
![image-20210526104627124](../assets/image-20210526104627124.png)
5858

59+
*Sometimes you may need more description fields about the response, such as description, headers and links.
60+
61+
You can use the following form:
62+
63+
```python
64+
@app.get(
65+
"/test",
66+
responses={
67+
"201": {
68+
"model": BaseResponse,
69+
"description": "Custom description",
70+
"headers": {
71+
"location": {
72+
"description": "URL of the new resource",
73+
"schema": {"type": "string"}
74+
}
75+
},
76+
"links": {
77+
"dummy": {
78+
"description": "dummy link"
79+
}
80+
}
81+
}
82+
}
83+
)
84+
def endpoint_test():
85+
...
86+
```
87+
88+
The effect in swagger:
89+
90+
![](../assets/Snipaste_2025-01-14_11-08-40.png)
91+
92+
93+
## Multiple content types in the responses
94+
95+
```python
96+
from typing import Union
97+
98+
from flask import Request
99+
from pydantic import BaseModel
100+
101+
from flask_openapi3 import OpenAPI
102+
103+
app = OpenAPI(__name__)
104+
105+
106+
class DogBody(BaseModel):
107+
a: int = None
108+
b: str = None
109+
110+
model_config = {
111+
"openapi_extra": {
112+
"content_type": "application/vnd.dog+json"
113+
}
114+
}
115+
116+
117+
class CatBody(BaseModel):
118+
c: int = None
119+
d: str = None
120+
121+
model_config = {
122+
"openapi_extra": {
123+
"content_type": "application/vnd.cat+json"
124+
}
125+
}
126+
127+
128+
class BsonModel(BaseModel):
129+
e: int = None
130+
f: str = None
131+
132+
model_config = {
133+
"openapi_extra": {
134+
"content_type": "application/bson"
135+
}
136+
}
137+
138+
139+
class ContentTypeModel(BaseModel):
140+
model_config = {
141+
"openapi_extra": {
142+
"content_type": "text/csv"
143+
}
144+
}
145+
146+
147+
@app.post("/a", responses={200: DogBody | CatBody | ContentTypeModel | BsonModel})
148+
def index_a(body: DogBody | CatBody | ContentTypeModel | BsonModel):
149+
"""
150+
multiple content types examples.
151+
152+
This may be confusing, if the content-type is application/json, the type of body will be auto parsed to
153+
DogBody or CatBody, otherwise it cannot be parsed to ContentTypeModel or BsonModel.
154+
The body is equivalent to the request variable in Flask, and you can use body.data, body.text, etc ...
155+
"""
156+
print(body)
157+
if isinstance(body, Request):
158+
if body.mimetype == "text/csv":
159+
# processing csv data
160+
...
161+
elif body.mimetype == "application/bson":
162+
# processing bson data
163+
...
164+
else:
165+
# DogBody or CatBody
166+
...
167+
return {"hello": "world"}
168+
```
169+
170+
The effect in swagger:
171+
172+
![](../assets/Snipaste_2025-01-14_10-49-19.png)
173+
174+
59175
## More information about OpenAPI responses
60176

61177
- [OpenAPI Responses Object](https://spec.openapis.org/oas/v3.1.0#responses-object), it includes the Response Object.

docs/Usage/Route_Operation.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,29 @@ class BookListAPIView:
289289
app.register_api_view(api_view)
290290
```
291291

292+
## request_body_description
293+
294+
A brief description of the request body.
295+
296+
```python
297+
from flask_openapi3 import OpenAPI
298+
299+
app = OpenAPI(__name__)
300+
301+
@app.post(
302+
"/",
303+
request_body_description="A brief description of the request body."
304+
)
305+
def create_book(body: Bookbody):
306+
...
307+
```
308+
309+
![](../assets/Snipaste_2025-01-14_10-56-40.png)
310+
311+
## request_body_required
312+
313+
Determines if the request body is required in the request.
314+
292315
## doc_ui
293316

294317
You can pass `doc_ui=False` to disable the `OpenAPI spec` when init `OpenAPI `.
18.1 KB
Loading
19.4 KB
Loading
7.53 KB
Loading
18 KB
Loading

examples/multi_content_type.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- coding: utf-8 -*-
2+
# @Author : llc
3+
# @Time : 2024/12/27 15:30
4+
from flask import Request
5+
from pydantic import BaseModel
6+
7+
from flask_openapi3 import OpenAPI
8+
9+
app = OpenAPI(__name__)
10+
11+
12+
class DogBody(BaseModel):
13+
a: int = None
14+
b: str = None
15+
16+
model_config = {"openapi_extra": {"content_type": "application/vnd.dog+json"}}
17+
18+
19+
class CatBody(BaseModel):
20+
c: int = None
21+
d: str = None
22+
23+
model_config = {"openapi_extra": {"content_type": "application/vnd.cat+json"}}
24+
25+
26+
class BsonModel(BaseModel):
27+
e: int = None
28+
f: str = None
29+
30+
model_config = {"openapi_extra": {"content_type": "application/bson"}}
31+
32+
33+
class ContentTypeModel(BaseModel):
34+
model_config = {"openapi_extra": {"content_type": "text/csv"}}
35+
36+
37+
@app.post("/a", responses={200: DogBody | CatBody | ContentTypeModel | BsonModel})
38+
def index_a(body: DogBody | CatBody | ContentTypeModel | BsonModel):
39+
"""
40+
multiple content types examples.
41+
42+
This may be confusing, if the content-type is application/json, the type of body will be auto parsed to
43+
DogBody or CatBody, otherwise it cannot be parsed to ContentTypeModel or BsonModel.
44+
The body is equivalent to the request variable in Flask, and you can use body.data, body.text, etc ...
45+
"""
46+
print(body)
47+
if isinstance(body, Request):
48+
if body.mimetype == "text/csv":
49+
# processing csv data
50+
...
51+
elif body.mimetype == "application/bson":
52+
# processing bson data
53+
from bson import BSON
54+
55+
obj = BSON(body.data).decode()
56+
new_body = body.model_validate(obj=obj)
57+
print(new_body)
58+
else:
59+
# DogBody or CatBody
60+
...
61+
return {"hello": "world"}
62+
63+
64+
if __name__ == "__main__":
65+
app.run(debug=True)

flask_openapi3/blueprint.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ def _collect_openapi_info(
121121
security: list[dict[str, list[Any]]] | None = None,
122122
servers: list[Server] | None = None,
123123
openapi_extensions: dict[str, Any] | None = None,
124+
request_body_description: str | None = None,
125+
request_body_required: bool | None = True,
124126
doc_ui: bool = True,
125127
method: str = HTTPMethod.GET,
126128
) -> ParametersTuple:
@@ -140,6 +142,8 @@ def _collect_openapi_info(
140142
security: A declaration of which security mechanisms can be used for this operation.
141143
servers: An alternative server array to service this operation.
142144
openapi_extensions: Allows extensions to the OpenAPI Schema.
145+
request_body_description: A brief description of the request body.
146+
request_body_required: Determines if the request body is required in the request.
143147
doc_ui: Declares this operation to be shown. Default to True.
144148
"""
145149
if self.doc_ui is True and doc_ui is True:
@@ -191,6 +195,12 @@ def _collect_openapi_info(
191195
parse_method(uri, method, self.paths, operation)
192196

193197
# Parse parameters
194-
return parse_parameters(func, components_schemas=self.components_schemas, operation=operation)
198+
return parse_parameters(
199+
func,
200+
components_schemas=self.components_schemas,
201+
operation=operation,
202+
request_body_description=request_body_description,
203+
request_body_required=request_body_required,
204+
)
195205
else:
196206
return parse_parameters(func, doc_ui=False)

flask_openapi3/models/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#table-of-contents
1010
"""
1111

12-
from typing import Optional, Union
13-
1412
from flask import Request
1513
from pydantic import BaseModel
1614

0 commit comments

Comments
 (0)