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 

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

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

12 

13 

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) 

24 

25 

26def import_werkzeug_response(): 

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

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

29 import importlib.metadata 

30 

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

32 if werkzeug_major < 2: 

33 from werkzeug.wrappers import BaseResponse 

34 

35 return BaseResponse 

36 

37 from werkzeug.wrappers import Response 

38 

39 return Response 

40 

41 

42BaseResponse = import_werkzeug_response() 

43 

44 

45class FlaskCompatibilityWarning(DeprecationWarning): 

46 pass 

47 

48 

49def merge(first, second): 

50 """ 

51 Recursively merges two dictionaries. 

52 

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

54 Nested dictionaries are merged too. 

55 

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 

70 

71 

72def camel_to_dash(value): 

73 """ 

74 Transform a CamelCase string into a low_dashed one 

75 

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() 

82 

83 

84def default_id(resource, method): 

85 """Default operation ID generator""" 

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

87 

88 

89def not_none(data): 

90 """ 

91 Remove all keys where value is None 

92 

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) 

98 

99 

100def not_none_sorted(data): 

101 """ 

102 Remove all keys where value is None 

103 

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) 

109 

110 

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

112 """ 

113 Unpack a Flask standard response. 

114 

115 Flask response can be: 

116 - a single value 

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

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

119 

120 .. warning:: 

121 

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. 

124 

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") 

147 

148 

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. 

152 

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__ 

157 

158 

159def import_check_view_func(): 

160 """ 

161 Resolve import flask _endpoint_from_view_func. 

162 

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

164 

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 

169 

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