Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/flask_restx/utils.py: 49%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import re
2import warnings
3import typing
5from collections import OrderedDict
6from copy import deepcopy
8from ._http import HTTPStatus
10FIRST_CAP_RE = re.compile("(.)([A-Z][a-z]+)")
11ALL_CAP_RE = re.compile("([a-z0-9])([A-Z])")
14__all__ = (
15 "merge",
16 "camel_to_dash",
17 "default_id",
18 "not_none",
19 "not_none_sorted",
20 "unpack",
21 "BaseResponse",
22 "import_check_view_func",
23)
26def import_werkzeug_response():
27 """Resolve `werkzeug` `Response` class import because
28 `BaseResponse` was renamed in version 2.* to `Response`"""
29 import importlib.metadata
31 werkzeug_major = int(importlib.metadata.version("werkzeug").split(".")[0])
32 if werkzeug_major < 2:
33 from werkzeug.wrappers import BaseResponse
35 return BaseResponse
37 from werkzeug.wrappers import Response
39 return Response
42BaseResponse = import_werkzeug_response()
45class FlaskCompatibilityWarning(DeprecationWarning):
46 pass
49def merge(first, second):
50 """
51 Recursively merges two dictionaries.
53 Second dictionary values will take precedence over those from the first one.
54 Nested dictionaries are merged too.
56 :param dict first: The first dictionary
57 :param dict second: The second dictionary
58 :return: the resulting merged dictionary
59 :rtype: dict
60 """
61 if not isinstance(second, dict):
62 return second
63 result = deepcopy(first)
64 for key, value in second.items():
65 if key in result and isinstance(result[key], dict):
66 result[key] = merge(result[key], value)
67 else:
68 result[key] = deepcopy(value)
69 return result
72def camel_to_dash(value):
73 """
74 Transform a CamelCase string into a low_dashed one
76 :param str value: a CamelCase string to transform
77 :return: the low_dashed string
78 :rtype: str
79 """
80 first_cap = FIRST_CAP_RE.sub(r"\1_\2", value)
81 return ALL_CAP_RE.sub(r"\1_\2", first_cap).lower()
84def default_id(resource, method):
85 """Default operation ID generator"""
86 return "{0}_{1}".format(method, camel_to_dash(resource))
89def not_none(data):
90 """
91 Remove all keys where value is None
93 :param dict data: A dictionary with potentially some values set to None
94 :return: The same dictionary without the keys with values to ``None``
95 :rtype: dict
96 """
97 return dict((k, v) for k, v in data.items() if v is not None)
100def not_none_sorted(data):
101 """
102 Remove all keys where value is None
104 :param OrderedDict data: A dictionary with potentially some values set to None
105 :return: The same dictionary without the keys with values to ``None``
106 :rtype: OrderedDict
107 """
108 return OrderedDict((k, v) for k, v in sorted(data.items()) if v is not None)
111def unpack(response, default_code=HTTPStatus.OK):
112 """
113 Unpack a Flask standard response.
115 Flask response can be:
116 - a single value
117 - a 2-tuple ``(value, code)``
118 - a 3-tuple ``(value, code, headers)``
120 .. warning::
122 When using this function, you must ensure that the tuple is not the response data.
123 To do so, prefer returning list instead of tuple for listings.
125 :param response: A Flask style response
126 :param int default_code: The HTTP code to use as default if none is provided
127 :return: a 3-tuple ``(data, code, headers)``
128 :rtype: tuple
129 :raise ValueError: if the response does not have one of the expected format
130 """
131 if not isinstance(response, tuple):
132 # data only
133 return response, default_code, {}
134 elif len(response) == 1:
135 # data only as tuple
136 return response[0], default_code, {}
137 elif len(response) == 2:
138 # data and code
139 data, code = response
140 return data, code, {}
141 elif len(response) == 3:
142 # data, code and headers
143 data, code, headers = response
144 return data, code or default_code, headers
145 else:
146 raise ValueError("Too many response values")
149def to_view_name(view_func: typing.Callable) -> str:
150 """Helper that returns the default endpoint for a given
151 function. This always is the function name.
153 Note: copy of simple flask internal helper
154 """
155 assert view_func is not None, "expected view func if endpoint is not provided."
156 return view_func.__name__
159def import_check_view_func():
160 """
161 Resolve import flask _endpoint_from_view_func.
163 Show warning if function cannot be found and provide copy of last known implementation.
165 Note: This helper method exists because reoccurring problem with flask function, but
166 actual method body remaining the same in each flask version.
167 """
168 import importlib.metadata
170 flask_version = importlib.metadata.version("flask").split(".")
171 try:
172 if flask_version[0] == "1":
173 from flask.helpers import _endpoint_from_view_func
174 elif flask_version[0] == "2":
175 from flask.scaffold import _endpoint_from_view_func
176 elif flask_version[0] == "3":
177 from flask.sansio.scaffold import _endpoint_from_view_func
178 else:
179 warnings.simplefilter("once", FlaskCompatibilityWarning)
180 _endpoint_from_view_func = None
181 except ImportError:
182 warnings.simplefilter("once", FlaskCompatibilityWarning)
183 _endpoint_from_view_func = None
184 if _endpoint_from_view_func is None:
185 _endpoint_from_view_func = to_view_name
186 return _endpoint_from_view_func