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

71 statements  

1import re 

2import warnings 

3import typing 

4 

5from collections import OrderedDict 

6from copy import deepcopy 

7 

8from ._http import HTTPStatus 

9 

10 

11FIRST_CAP_RE = re.compile("(.)([A-Z][a-z]+)") 

12ALL_CAP_RE = re.compile("([a-z0-9])([A-Z])") 

13 

14 

15__all__ = ( 

16 "merge", 

17 "camel_to_dash", 

18 "default_id", 

19 "not_none", 

20 "not_none_sorted", 

21 "unpack", 

22 "BaseResponse", 

23 "import_check_view_func", 

24) 

25 

26 

27def import_werkzeug_response(): 

28 """Resolve `werkzeug` `Response` class import because 

29 `BaseResponse` was renamed in version 2.* to `Response`""" 

30 import importlib.metadata 

31 

32 werkzeug_major = int(importlib.metadata.version("werkzeug").split(".")[0]) 

33 if werkzeug_major < 2: 

34 from werkzeug.wrappers import BaseResponse 

35 

36 return BaseResponse 

37 

38 from werkzeug.wrappers import Response 

39 

40 return Response 

41 

42 

43BaseResponse = import_werkzeug_response() 

44 

45 

46class FlaskCompatibilityWarning(DeprecationWarning): 

47 pass 

48 

49 

50def merge(first, second): 

51 """ 

52 Recursively merges two dictionaries. 

53 

54 Second dictionary values will take precedence over those from the first one. 

55 Nested dictionaries are merged too. 

56 

57 :param dict first: The first dictionary 

58 :param dict second: The second dictionary 

59 :return: the resulting merged dictionary 

60 :rtype: dict 

61 """ 

62 if not isinstance(second, dict): 

63 return second 

64 result = deepcopy(first) 

65 for key, value in second.items(): 

66 if key in result and isinstance(result[key], dict): 

67 result[key] = merge(result[key], value) 

68 else: 

69 result[key] = deepcopy(value) 

70 return result 

71 

72 

73def camel_to_dash(value): 

74 """ 

75 Transform a CamelCase string into a low_dashed one 

76 

77 :param str value: a CamelCase string to transform 

78 :return: the low_dashed string 

79 :rtype: str 

80 """ 

81 first_cap = FIRST_CAP_RE.sub(r"\1_\2", value) 

82 return ALL_CAP_RE.sub(r"\1_\2", first_cap).lower() 

83 

84 

85def default_id(resource, method): 

86 """Default operation ID generator""" 

87 return "{0}_{1}".format(method, camel_to_dash(resource)) 

88 

89 

90def not_none(data): 

91 """ 

92 Remove all keys where value is None 

93 

94 :param dict data: A dictionary with potentially some values set to None 

95 :return: The same dictionary without the keys with values to ``None`` 

96 :rtype: dict 

97 """ 

98 return dict((k, v) for k, v in data.items() if v is not None) 

99 

100 

101def not_none_sorted(data): 

102 """ 

103 Remove all keys where value is None 

104 

105 :param OrderedDict data: A dictionary with potentially some values set to None 

106 :return: The same dictionary without the keys with values to ``None`` 

107 :rtype: OrderedDict 

108 """ 

109 return OrderedDict((k, v) for k, v in sorted(data.items()) if v is not None) 

110 

111 

112def unpack(response, default_code=HTTPStatus.OK): 

113 """ 

114 Unpack a Flask standard response. 

115 

116 Flask response can be: 

117 - a single value 

118 - a 2-tuple ``(value, code)`` 

119 - a 3-tuple ``(value, code, headers)`` 

120 

121 .. warning:: 

122 

123 When using this function, you must ensure that the tuple is not the response data. 

124 To do so, prefer returning list instead of tuple for listings. 

125 

126 :param response: A Flask style response 

127 :param int default_code: The HTTP code to use as default if none is provided 

128 :return: a 3-tuple ``(data, code, headers)`` 

129 :rtype: tuple 

130 :raise ValueError: if the response does not have one of the expected format 

131 """ 

132 if not isinstance(response, tuple): 

133 # data only 

134 return response, default_code, {} 

135 elif len(response) == 1: 

136 # data only as tuple 

137 return response[0], default_code, {} 

138 elif len(response) == 2: 

139 # data and code 

140 data, code = response 

141 return data, code, {} 

142 elif len(response) == 3: 

143 # data, code and headers 

144 data, code, headers = response 

145 return data, code or default_code, headers 

146 else: 

147 raise ValueError("Too many response values") 

148 

149 

150def to_view_name(view_func: typing.Callable) -> str: 

151 """Helper that returns the default endpoint for a given 

152 function. This always is the function name. 

153 

154 Note: copy of simple flask internal helper 

155 """ 

156 assert view_func is not None, "expected view func if endpoint is not provided." 

157 return view_func.__name__ 

158 

159 

160def import_check_view_func(): 

161 """ 

162 Resolve import flask _endpoint_from_view_func. 

163 

164 Show warning if function cannot be found and provide copy of last known implementation. 

165 

166 Note: This helper method exists because reoccurring problem with flask function, but 

167 actual method body remaining the same in each flask version. 

168 """ 

169 import importlib.metadata 

170 

171 flask_version = importlib.metadata.version("flask").split(".") 

172 try: 

173 if flask_version[0] == "1": 

174 from flask.helpers import _endpoint_from_view_func 

175 elif flask_version[0] == "2": 

176 from flask.scaffold import _endpoint_from_view_func 

177 elif flask_version[0] == "3": 

178 from flask.sansio.scaffold import _endpoint_from_view_func 

179 else: 

180 warnings.simplefilter("once", FlaskCompatibilityWarning) 

181 _endpoint_from_view_func = None 

182 except ImportError: 

183 warnings.simplefilter("once", FlaskCompatibilityWarning) 

184 _endpoint_from_view_func = None 

185 if _endpoint_from_view_func is None: 

186 _endpoint_from_view_func = to_view_name 

187 return _endpoint_from_view_func