flask-openapi 4.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flask_openapi/__init__.py +44 -0
- flask_openapi/__version__.py +5 -0
- flask_openapi/blueprint.py +201 -0
- flask_openapi/commands.py +38 -0
- flask_openapi/models/__init__.py +91 -0
- flask_openapi/models/callback.py +18 -0
- flask_openapi/models/components.py +37 -0
- flask_openapi/models/contact.py +17 -0
- flask_openapi/models/data_type.py +18 -0
- flask_openapi/models/discriminator.py +16 -0
- flask_openapi/models/encoding.py +27 -0
- flask_openapi/models/example.py +19 -0
- flask_openapi/models/external_documentation.py +16 -0
- flask_openapi/models/file.py +27 -0
- flask_openapi/models/header.py +17 -0
- flask_openapi/models/info.py +24 -0
- flask_openapi/models/license.py +17 -0
- flask_openapi/models/link.py +23 -0
- flask_openapi/models/media_type.py +24 -0
- flask_openapi/models/oauth_flow.py +18 -0
- flask_openapi/models/oauth_flows.py +20 -0
- flask_openapi/models/operation.py +36 -0
- flask_openapi/models/parameter.py +34 -0
- flask_openapi/models/parameter_in_type.py +13 -0
- flask_openapi/models/path_item.py +36 -0
- flask_openapi/models/paths.py +9 -0
- flask_openapi/models/reference.py +14 -0
- flask_openapi/models/request_body.py +19 -0
- flask_openapi/models/response.py +23 -0
- flask_openapi/models/responses.py +11 -0
- flask_openapi/models/schema.py +59 -0
- flask_openapi/models/security_requirement.py +8 -0
- flask_openapi/models/security_scheme.py +25 -0
- flask_openapi/models/security_scheme_in_type.py +12 -0
- flask_openapi/models/server.py +19 -0
- flask_openapi/models/server_variable.py +17 -0
- flask_openapi/models/style_values.py +14 -0
- flask_openapi/models/tag.py +15 -0
- flask_openapi/models/validation_error.py +24 -0
- flask_openapi/models/xml.py +19 -0
- flask_openapi/openapi.py +449 -0
- flask_openapi/plugins.py +17 -0
- flask_openapi/py.typed +0 -0
- flask_openapi/request.py +250 -0
- flask_openapi/scaffold.py +547 -0
- flask_openapi/templates.py +113 -0
- flask_openapi/types.py +27 -0
- flask_openapi/utils.py +633 -0
- flask_openapi/view.py +230 -0
- flask_openapi-4.3.1.dist-info/METADATA +258 -0
- flask_openapi-4.3.1.dist-info/RECORD +53 -0
- flask_openapi-4.3.1.dist-info/WHEEL +4 -0
- flask_openapi-4.3.1.dist-info/licenses/LICENSE.rst +21 -0
flask_openapi/request.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Author : llc
|
|
3
|
+
# @Time : 2022/4/1 16:54
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from json import JSONDecodeError
|
|
8
|
+
from typing import Any, Type
|
|
9
|
+
|
|
10
|
+
from flask import abort, current_app, request
|
|
11
|
+
from pydantic import BaseModel, ValidationError
|
|
12
|
+
from pydantic.fields import FieldInfo
|
|
13
|
+
from werkzeug.datastructures.structures import MultiDict
|
|
14
|
+
|
|
15
|
+
from .utils import parse_parameters
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_list_value(model: Type[BaseModel], args: MultiDict, model_field_key: str, model_field_value: FieldInfo):
|
|
19
|
+
if model_field_value.alias and model.model_config.get("populate_by_name"):
|
|
20
|
+
key = model_field_value.alias
|
|
21
|
+
value = args.getlist(model_field_value.alias) or args.getlist(model_field_key)
|
|
22
|
+
elif model_field_value.alias:
|
|
23
|
+
key = model_field_value.alias
|
|
24
|
+
value = args.getlist(model_field_value.alias)
|
|
25
|
+
else:
|
|
26
|
+
key = model_field_key
|
|
27
|
+
value = args.getlist(model_field_key)
|
|
28
|
+
|
|
29
|
+
return key, value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_value(model: Type[BaseModel], args: MultiDict, model_field_key: str, model_field_value: FieldInfo):
|
|
33
|
+
if model_field_value.alias and model.model_config.get("populate_by_name"):
|
|
34
|
+
key = model_field_value.alias
|
|
35
|
+
value = args.get(model_field_value.alias) or args.get(model_field_key)
|
|
36
|
+
elif model_field_value.alias:
|
|
37
|
+
key = model_field_value.alias
|
|
38
|
+
value = args.get(model_field_value.alias)
|
|
39
|
+
else:
|
|
40
|
+
key = model_field_key
|
|
41
|
+
value = args.get(model_field_key)
|
|
42
|
+
|
|
43
|
+
return key, value
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _validate_header(header: Type[BaseModel], func_kwargs: dict):
|
|
47
|
+
request_headers = dict(request.headers)
|
|
48
|
+
header_dict = {}
|
|
49
|
+
model_properties = header.model_json_schema().get("properties", {})
|
|
50
|
+
for model_field_key, model_field_value in header.model_fields.items():
|
|
51
|
+
key_title = model_field_key.replace("_", "-").title()
|
|
52
|
+
model_field_schema = model_properties.get(model_field_value.alias or model_field_key)
|
|
53
|
+
if model_field_value.alias and header.model_config.get("populate_by_name"):
|
|
54
|
+
key = model_field_value.alias
|
|
55
|
+
key_alias_title = model_field_value.alias.replace("_", "-").title()
|
|
56
|
+
value = request_headers.get(key_alias_title) or request_headers.get(key_title)
|
|
57
|
+
elif model_field_value.alias:
|
|
58
|
+
key = model_field_value.alias
|
|
59
|
+
key_alias_title = model_field_value.alias.replace("_", "-").title()
|
|
60
|
+
value = request_headers.get(key_alias_title)
|
|
61
|
+
else:
|
|
62
|
+
key = model_field_key
|
|
63
|
+
value = request_headers[key_title]
|
|
64
|
+
if value is not None:
|
|
65
|
+
header_dict[key] = value
|
|
66
|
+
if model_field_schema.get("type") == "null":
|
|
67
|
+
header_dict[key] = value # type:ignore
|
|
68
|
+
# extra keys
|
|
69
|
+
for key, value in request_headers.items():
|
|
70
|
+
if key not in header_dict.keys():
|
|
71
|
+
header_dict[key] = value
|
|
72
|
+
func_kwargs["header"] = header.model_validate(obj=header_dict)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _validate_cookie(cookie: Type[BaseModel], func_kwargs: dict):
|
|
76
|
+
request_cookies = dict(request.cookies)
|
|
77
|
+
func_kwargs["cookie"] = cookie.model_validate(obj=request_cookies)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _validate_path(path: Type[BaseModel], path_kwargs: dict, func_kwargs: dict):
|
|
81
|
+
path_obj = path.model_validate(obj=path_kwargs)
|
|
82
|
+
func_kwargs["path"] = path_obj
|
|
83
|
+
# Consume path parameters to prevent from being passed to the function
|
|
84
|
+
for field_name, _ in path_obj:
|
|
85
|
+
path_kwargs.pop(field_name, None)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _validate_query(query: Type[BaseModel], func_kwargs: dict):
|
|
89
|
+
request_args = request.args
|
|
90
|
+
query_dict = {}
|
|
91
|
+
model_properties = query.model_json_schema().get("properties", {})
|
|
92
|
+
for model_field_key, model_field_value in query.model_fields.items():
|
|
93
|
+
model_field_schema = model_properties.get(model_field_value.alias or model_field_key)
|
|
94
|
+
if model_field_schema.get("type") == "array":
|
|
95
|
+
key, value = _get_list_value(query, request_args, model_field_key, model_field_value)
|
|
96
|
+
# To handle Optional[list]
|
|
97
|
+
elif any(m.get("type") == "array" for m in model_field_schema.get("anyOf", [])):
|
|
98
|
+
key, value = _get_list_value(query, request_args, model_field_key, model_field_value)
|
|
99
|
+
else:
|
|
100
|
+
key, value = _get_value(query, request_args, model_field_key, model_field_value)
|
|
101
|
+
if value is not None and value != []:
|
|
102
|
+
query_dict[key] = value
|
|
103
|
+
if model_field_schema.get("type") == "null":
|
|
104
|
+
query_dict[key] = value
|
|
105
|
+
# extra keys
|
|
106
|
+
for key, value in request_args.items():
|
|
107
|
+
if key not in query_dict.keys():
|
|
108
|
+
query_dict[key] = value
|
|
109
|
+
func_kwargs["query"] = query.model_validate(obj=query_dict)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _validate_form(form: Type[BaseModel], func_kwargs: dict):
|
|
113
|
+
request_form = request.form
|
|
114
|
+
request_files = request.files
|
|
115
|
+
form_dict = {}
|
|
116
|
+
model_properties = form.model_json_schema().get("properties", {})
|
|
117
|
+
for model_field_key, model_field_value in form.model_fields.items():
|
|
118
|
+
model_field_schema = model_properties.get(model_field_value.alias or model_field_key)
|
|
119
|
+
if model_field_schema.get("type") == "array":
|
|
120
|
+
if model_field_schema.get("items") == {"format": "binary", "type": "string"}:
|
|
121
|
+
# list[FileStorage]
|
|
122
|
+
key, value = _get_list_value(form, request_files, model_field_key, model_field_value)
|
|
123
|
+
else:
|
|
124
|
+
value = []
|
|
125
|
+
key, value_list = _get_list_value(form, request_form, model_field_key, model_field_value)
|
|
126
|
+
for _value in value_list:
|
|
127
|
+
try:
|
|
128
|
+
value.append(json.loads(_value))
|
|
129
|
+
except (JSONDecodeError, TypeError):
|
|
130
|
+
value.append(_value)
|
|
131
|
+
elif model_field_schema.get("type") == "string" and model_field_schema.get("format") == "binary":
|
|
132
|
+
# FileStorage
|
|
133
|
+
key, value = _get_value(form, request_files, model_field_key, model_field_value)
|
|
134
|
+
else:
|
|
135
|
+
key, _value = _get_value(form, request_form, model_field_key, model_field_value)
|
|
136
|
+
try:
|
|
137
|
+
value = json.loads(_value)
|
|
138
|
+
except (JSONDecodeError, TypeError):
|
|
139
|
+
value = _value
|
|
140
|
+
if value is not None and value != []:
|
|
141
|
+
form_dict[key] = value
|
|
142
|
+
if model_field_schema.get("type") == "null":
|
|
143
|
+
form_dict[key] = value
|
|
144
|
+
# extra keys
|
|
145
|
+
for key, value in {**dict(request_form), **dict(request_files)}.items():
|
|
146
|
+
if key not in form_dict.keys():
|
|
147
|
+
form_dict[key] = value
|
|
148
|
+
func_kwargs["form"] = form.model_validate(obj=form_dict)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _validate_body(body: Type[BaseModel], func_kwargs: dict):
|
|
152
|
+
obj = request.get_json(silent=True)
|
|
153
|
+
if isinstance(obj, str):
|
|
154
|
+
body_model = body.model_validate_json(json_data=obj)
|
|
155
|
+
else:
|
|
156
|
+
body_model = body.model_validate(obj=obj)
|
|
157
|
+
func_kwargs["body"] = body_model
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _validate_request(
|
|
161
|
+
header: Type[BaseModel] | None = None,
|
|
162
|
+
cookie: Type[BaseModel] | None = None,
|
|
163
|
+
path: Type[BaseModel] | None = None,
|
|
164
|
+
query: Type[BaseModel] | None = None,
|
|
165
|
+
form: Type[BaseModel] | None = None,
|
|
166
|
+
body: Type[BaseModel] | None = None,
|
|
167
|
+
raw: Type[BaseModel] | None = None,
|
|
168
|
+
path_kwargs: dict[Any, Any] | None = None,
|
|
169
|
+
) -> dict:
|
|
170
|
+
"""
|
|
171
|
+
Validate requests and responses.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
header: Header model.
|
|
175
|
+
cookie: Cookie model.
|
|
176
|
+
path: Path model.
|
|
177
|
+
query: Query model.
|
|
178
|
+
form: Form model.
|
|
179
|
+
body: Body model.
|
|
180
|
+
path_kwargs: Path parameters.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
dict: Request kwargs.
|
|
184
|
+
|
|
185
|
+
Raises:
|
|
186
|
+
ValidationError: If validation fails.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
# Dictionary to store func kwargs
|
|
190
|
+
func_kwargs: dict = {}
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
# Validate header, cookie, path, and query parameters
|
|
194
|
+
if header:
|
|
195
|
+
_validate_header(header, func_kwargs)
|
|
196
|
+
if cookie:
|
|
197
|
+
_validate_cookie(cookie, func_kwargs)
|
|
198
|
+
if path:
|
|
199
|
+
_validate_path(path, path_kwargs or {}, func_kwargs)
|
|
200
|
+
if query:
|
|
201
|
+
_validate_query(query, func_kwargs)
|
|
202
|
+
if form:
|
|
203
|
+
_validate_form(form, func_kwargs)
|
|
204
|
+
if body:
|
|
205
|
+
_validate_body(body, func_kwargs)
|
|
206
|
+
if raw:
|
|
207
|
+
func_kwargs["raw"] = request
|
|
208
|
+
except ValidationError as e:
|
|
209
|
+
# Create a response with validation error details
|
|
210
|
+
validation_error_callback = getattr(current_app, "validation_error_callback")
|
|
211
|
+
abort(validation_error_callback(e))
|
|
212
|
+
|
|
213
|
+
return func_kwargs
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def validate_request():
|
|
217
|
+
"""
|
|
218
|
+
Decorator to validate the annotated parts of the function and throw and error if applicable.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
def decorator(func):
|
|
222
|
+
setattr(func, "__delay_validate_request__", True)
|
|
223
|
+
|
|
224
|
+
is_coroutine_function = inspect.iscoroutinefunction(func)
|
|
225
|
+
|
|
226
|
+
if is_coroutine_function:
|
|
227
|
+
|
|
228
|
+
@wraps(func)
|
|
229
|
+
async def wrapper(*args, **kwargs):
|
|
230
|
+
header, cookie, path, query, form, body, raw = parse_parameters(func)
|
|
231
|
+
func_kwargs = _validate_request(header, cookie, path, query, form, body, raw, path_kwargs=kwargs)
|
|
232
|
+
# Update func_kwargs with any additional keyword arguments passed from other decorators or calls.
|
|
233
|
+
func_kwargs.update(kwargs)
|
|
234
|
+
|
|
235
|
+
return await func(*args, **func_kwargs)
|
|
236
|
+
|
|
237
|
+
return wrapper
|
|
238
|
+
else:
|
|
239
|
+
|
|
240
|
+
@wraps(func)
|
|
241
|
+
def wrapper(*args, **kwargs):
|
|
242
|
+
header, cookie, path, query, form, body, raw = parse_parameters(func)
|
|
243
|
+
func_kwargs = _validate_request(header, cookie, path, query, form, body, raw, path_kwargs=kwargs)
|
|
244
|
+
# Update func_kwargs with any additional keyword arguments passed from other decorators or calls.
|
|
245
|
+
func_kwargs.update(kwargs)
|
|
246
|
+
return func(*args, **func_kwargs)
|
|
247
|
+
|
|
248
|
+
return wrapper
|
|
249
|
+
|
|
250
|
+
return decorator
|