Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/googleapiclient/_helpers.py: 49%

53 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1# Copyright 2015 Google Inc. All rights reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Helper functions for commonly used utilities.""" 

16 

17import functools 

18import inspect 

19import logging 

20import urllib 

21 

22logger = logging.getLogger(__name__) 

23 

24POSITIONAL_WARNING = "WARNING" 

25POSITIONAL_EXCEPTION = "EXCEPTION" 

26POSITIONAL_IGNORE = "IGNORE" 

27POSITIONAL_SET = frozenset( 

28 [POSITIONAL_WARNING, POSITIONAL_EXCEPTION, POSITIONAL_IGNORE] 

29) 

30 

31positional_parameters_enforcement = POSITIONAL_WARNING 

32 

33_SYM_LINK_MESSAGE = "File: {0}: Is a symbolic link." 

34_IS_DIR_MESSAGE = "{0}: Is a directory" 

35_MISSING_FILE_MESSAGE = "Cannot access {0}: No such file or directory" 

36 

37 

38def positional(max_positional_args): 

39 """A decorator to declare that only the first N arguments may be positional. 

40 

41 This decorator makes it easy to support Python 3 style keyword-only 

42 parameters. For example, in Python 3 it is possible to write:: 

43 

44 def fn(pos1, *, kwonly1=None, kwonly2=None): 

45 ... 

46 

47 All named parameters after ``*`` must be a keyword:: 

48 

49 fn(10, 'kw1', 'kw2') # Raises exception. 

50 fn(10, kwonly1='kw1') # Ok. 

51 

52 Example 

53 ^^^^^^^ 

54 

55 To define a function like above, do:: 

56 

57 @positional(1) 

58 def fn(pos1, kwonly1=None, kwonly2=None): 

59 ... 

60 

61 If no default value is provided to a keyword argument, it becomes a 

62 required keyword argument:: 

63 

64 @positional(0) 

65 def fn(required_kw): 

66 ... 

67 

68 This must be called with the keyword parameter:: 

69 

70 fn() # Raises exception. 

71 fn(10) # Raises exception. 

72 fn(required_kw=10) # Ok. 

73 

74 When defining instance or class methods always remember to account for 

75 ``self`` and ``cls``:: 

76 

77 class MyClass(object): 

78 

79 @positional(2) 

80 def my_method(self, pos1, kwonly1=None): 

81 ... 

82 

83 @classmethod 

84 @positional(2) 

85 def my_method(cls, pos1, kwonly1=None): 

86 ... 

87 

88 The positional decorator behavior is controlled by 

89 ``_helpers.positional_parameters_enforcement``, which may be set to 

90 ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or 

91 ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do 

92 nothing, respectively, if a declaration is violated. 

93 

94 Args: 

95 max_positional_arguments: Maximum number of positional arguments. All 

96 parameters after this index must be 

97 keyword only. 

98 

99 Returns: 

100 A decorator that prevents using arguments after max_positional_args 

101 from being used as positional parameters. 

102 

103 Raises: 

104 TypeError: if a keyword-only argument is provided as a positional 

105 parameter, but only if 

106 _helpers.positional_parameters_enforcement is set to 

107 POSITIONAL_EXCEPTION. 

108 """ 

109 

110 def positional_decorator(wrapped): 

111 @functools.wraps(wrapped) 

112 def positional_wrapper(*args, **kwargs): 

113 if len(args) > max_positional_args: 

114 plural_s = "" 

115 if max_positional_args != 1: 

116 plural_s = "s" 

117 message = ( 

118 "{function}() takes at most {args_max} positional " 

119 "argument{plural} ({args_given} given)".format( 

120 function=wrapped.__name__, 

121 args_max=max_positional_args, 

122 args_given=len(args), 

123 plural=plural_s, 

124 ) 

125 ) 

126 if positional_parameters_enforcement == POSITIONAL_EXCEPTION: 

127 raise TypeError(message) 

128 elif positional_parameters_enforcement == POSITIONAL_WARNING: 

129 logger.warning(message) 

130 return wrapped(*args, **kwargs) 

131 

132 return positional_wrapper 

133 

134 if isinstance(max_positional_args, int): 

135 return positional_decorator 

136 else: 

137 args, _, _, defaults, _, _, _ = inspect.getfullargspec(max_positional_args) 

138 return positional(len(args) - len(defaults))(max_positional_args) 

139 

140 

141def parse_unique_urlencoded(content): 

142 """Parses unique key-value parameters from urlencoded content. 

143 

144 Args: 

145 content: string, URL-encoded key-value pairs. 

146 

147 Returns: 

148 dict, The key-value pairs from ``content``. 

149 

150 Raises: 

151 ValueError: if one of the keys is repeated. 

152 """ 

153 urlencoded_params = urllib.parse.parse_qs(content) 

154 params = {} 

155 for key, value in urlencoded_params.items(): 

156 if len(value) != 1: 

157 msg = "URL-encoded content contains a repeated value:" "%s -> %s" % ( 

158 key, 

159 ", ".join(value), 

160 ) 

161 raise ValueError(msg) 

162 params[key] = value[0] 

163 return params 

164 

165 

166def update_query_params(uri, params): 

167 """Updates a URI with new query parameters. 

168 

169 If a given key from ``params`` is repeated in the ``uri``, then 

170 the URI will be considered invalid and an error will occur. 

171 

172 If the URI is valid, then each value from ``params`` will 

173 replace the corresponding value in the query parameters (if 

174 it exists). 

175 

176 Args: 

177 uri: string, A valid URI, with potential existing query parameters. 

178 params: dict, A dictionary of query parameters. 

179 

180 Returns: 

181 The same URI but with the new query parameters added. 

182 """ 

183 parts = urllib.parse.urlparse(uri) 

184 query_params = parse_unique_urlencoded(parts.query) 

185 query_params.update(params) 

186 new_query = urllib.parse.urlencode(query_params) 

187 new_parts = parts._replace(query=new_query) 

188 return urllib.parse.urlunparse(new_parts) 

189 

190 

191def _add_query_parameter(url, name, value): 

192 """Adds a query parameter to a url. 

193 

194 Replaces the current value if it already exists in the URL. 

195 

196 Args: 

197 url: string, url to add the query parameter to. 

198 name: string, query parameter name. 

199 value: string, query parameter value. 

200 

201 Returns: 

202 Updated query parameter. Does not update the url if value is None. 

203 """ 

204 if value is None: 

205 return url 

206 else: 

207 return update_query_params(url, {name: value})