Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/oauth2client/_helpers.py: 72%

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

99 statements  

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 base64 

18import functools 

19import inspect 

20import json 

21import logging 

22import os 

23import warnings 

24 

25import six 

26from six.moves import urllib 

27 

28 

29logger = logging.getLogger(__name__) 

30 

31POSITIONAL_WARNING = 'WARNING' 

32POSITIONAL_EXCEPTION = 'EXCEPTION' 

33POSITIONAL_IGNORE = 'IGNORE' 

34POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION, 

35 POSITIONAL_IGNORE]) 

36 

37positional_parameters_enforcement = POSITIONAL_WARNING 

38 

39_SYM_LINK_MESSAGE = 'File: {0}: Is a symbolic link.' 

40_IS_DIR_MESSAGE = '{0}: Is a directory' 

41_MISSING_FILE_MESSAGE = 'Cannot access {0}: No such file or directory' 

42 

43 

44def positional(max_positional_args): 

45 """A decorator to declare that only the first N arguments my be positional. 

46 

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

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

49 

50 def fn(pos1, *, kwonly1=None, kwonly1=None): 

51 ... 

52 

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

54 

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

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

57 

58 Example 

59 ^^^^^^^ 

60 

61 To define a function like above, do:: 

62 

63 @positional(1) 

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

65 ... 

66 

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

68 required keyword argument:: 

69 

70 @positional(0) 

71 def fn(required_kw): 

72 ... 

73 

74 This must be called with the keyword parameter:: 

75 

76 fn() # Raises exception. 

77 fn(10) # Raises exception. 

78 fn(required_kw=10) # Ok. 

79 

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

81 ``self`` and ``cls``:: 

82 

83 class MyClass(object): 

84 

85 @positional(2) 

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

87 ... 

88 

89 @classmethod 

90 @positional(2) 

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

92 ... 

93 

94 The positional decorator behavior is controlled by 

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

96 ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or 

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

98 nothing, respectively, if a declaration is violated. 

99 

100 Args: 

101 max_positional_arguments: Maximum number of positional arguments. All 

102 parameters after the this index must be 

103 keyword only. 

104 

105 Returns: 

106 A decorator that prevents using arguments after max_positional_args 

107 from being used as positional parameters. 

108 

109 Raises: 

110 TypeError: if a key-word only argument is provided as a positional 

111 parameter, but only if 

112 _helpers.positional_parameters_enforcement is set to 

113 POSITIONAL_EXCEPTION. 

114 """ 

115 

116 def positional_decorator(wrapped): 

117 @functools.wraps(wrapped) 

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

119 if len(args) > max_positional_args: 

120 plural_s = '' 

121 if max_positional_args != 1: 

122 plural_s = 's' 

123 message = ('{function}() takes at most {args_max} positional ' 

124 'argument{plural} ({args_given} given)'.format( 

125 function=wrapped.__name__, 

126 args_max=max_positional_args, 

127 args_given=len(args), 

128 plural=plural_s)) 

129 if positional_parameters_enforcement == POSITIONAL_EXCEPTION: 

130 raise TypeError(message) 

131 elif positional_parameters_enforcement == POSITIONAL_WARNING: 

132 logger.warning(message) 

133 return wrapped(*args, **kwargs) 

134 return positional_wrapper 

135 

136 if isinstance(max_positional_args, six.integer_types): 

137 return positional_decorator 

138 else: 

139 args, _, _, defaults = inspect.getargspec(max_positional_args) 

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

141 

142 

143def scopes_to_string(scopes): 

144 """Converts scope value to a string. 

145 

146 If scopes is a string then it is simply passed through. If scopes is an 

147 iterable then a string is returned that is all the individual scopes 

148 concatenated with spaces. 

149 

150 Args: 

151 scopes: string or iterable of strings, the scopes. 

152 

153 Returns: 

154 The scopes formatted as a single string. 

155 """ 

156 if isinstance(scopes, six.string_types): 

157 return scopes 

158 else: 

159 return ' '.join(scopes) 

160 

161 

162def string_to_scopes(scopes): 

163 """Converts stringifed scope value to a list. 

164 

165 If scopes is a list then it is simply passed through. If scopes is an 

166 string then a list of each individual scope is returned. 

167 

168 Args: 

169 scopes: a string or iterable of strings, the scopes. 

170 

171 Returns: 

172 The scopes in a list. 

173 """ 

174 if not scopes: 

175 return [] 

176 elif isinstance(scopes, six.string_types): 

177 return scopes.split(' ') 

178 else: 

179 return scopes 

180 

181 

182def parse_unique_urlencoded(content): 

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

184 

185 Args: 

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

187 

188 Returns: 

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

190 

191 Raises: 

192 ValueError: if one of the keys is repeated. 

193 """ 

194 urlencoded_params = urllib.parse.parse_qs(content) 

195 params = {} 

196 for key, value in six.iteritems(urlencoded_params): 

197 if len(value) != 1: 

198 msg = ('URL-encoded content contains a repeated value:' 

199 '%s -> %s' % (key, ', '.join(value))) 

200 raise ValueError(msg) 

201 params[key] = value[0] 

202 return params 

203 

204 

205def update_query_params(uri, params): 

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

207 

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

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

210 

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

212 replace the corresponding value in the query parameters (if 

213 it exists). 

214 

215 Args: 

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

217 params: dict, A dictionary of query parameters. 

218 

219 Returns: 

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

221 """ 

222 parts = urllib.parse.urlparse(uri) 

223 query_params = parse_unique_urlencoded(parts.query) 

224 query_params.update(params) 

225 new_query = urllib.parse.urlencode(query_params) 

226 new_parts = parts._replace(query=new_query) 

227 return urllib.parse.urlunparse(new_parts) 

228 

229 

230def _add_query_parameter(url, name, value): 

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

232 

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

234 

235 Args: 

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

237 name: string, query parameter name. 

238 value: string, query parameter value. 

239 

240 Returns: 

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

242 """ 

243 if value is None: 

244 return url 

245 else: 

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

247 

248 

249def validate_file(filename): 

250 if os.path.islink(filename): 

251 raise IOError(_SYM_LINK_MESSAGE.format(filename)) 

252 elif os.path.isdir(filename): 

253 raise IOError(_IS_DIR_MESSAGE.format(filename)) 

254 elif not os.path.isfile(filename): 

255 warnings.warn(_MISSING_FILE_MESSAGE.format(filename)) 

256 

257 

258def _parse_pem_key(raw_key_input): 

259 """Identify and extract PEM keys. 

260 

261 Determines whether the given key is in the format of PEM key, and extracts 

262 the relevant part of the key if it is. 

263 

264 Args: 

265 raw_key_input: The contents of a private key file (either PEM or 

266 PKCS12). 

267 

268 Returns: 

269 string, The actual key if the contents are from a PEM file, or 

270 else None. 

271 """ 

272 offset = raw_key_input.find(b'-----BEGIN ') 

273 if offset != -1: 

274 return raw_key_input[offset:] 

275 

276 

277def _json_encode(data): 

278 return json.dumps(data, separators=(',', ':')) 

279 

280 

281def _to_bytes(value, encoding='ascii'): 

282 """Converts a string value to bytes, if necessary. 

283 

284 Unfortunately, ``six.b`` is insufficient for this task since in 

285 Python2 it does not modify ``unicode`` objects. 

286 

287 Args: 

288 value: The string/bytes value to be converted. 

289 encoding: The encoding to use to convert unicode to bytes. Defaults 

290 to "ascii", which will not allow any characters from ordinals 

291 larger than 127. Other useful values are "latin-1", which 

292 which will only allows byte ordinals (up to 255) and "utf-8", 

293 which will encode any unicode that needs to be. 

294 

295 Returns: 

296 The original value converted to bytes (if unicode) or as passed in 

297 if it started out as bytes. 

298 

299 Raises: 

300 ValueError if the value could not be converted to bytes. 

301 """ 

302 result = (value.encode(encoding) 

303 if isinstance(value, six.text_type) else value) 

304 if isinstance(result, six.binary_type): 

305 return result 

306 else: 

307 raise ValueError('{0!r} could not be converted to bytes'.format(value)) 

308 

309 

310def _from_bytes(value): 

311 """Converts bytes to a string value, if necessary. 

312 

313 Args: 

314 value: The string/bytes value to be converted. 

315 

316 Returns: 

317 The original value converted to unicode (if bytes) or as passed in 

318 if it started out as unicode. 

319 

320 Raises: 

321 ValueError if the value could not be converted to unicode. 

322 """ 

323 result = (value.decode('utf-8') 

324 if isinstance(value, six.binary_type) else value) 

325 if isinstance(result, six.text_type): 

326 return result 

327 else: 

328 raise ValueError( 

329 '{0!r} could not be converted to unicode'.format(value)) 

330 

331 

332def _urlsafe_b64encode(raw_bytes): 

333 raw_bytes = _to_bytes(raw_bytes, encoding='utf-8') 

334 return base64.urlsafe_b64encode(raw_bytes).rstrip(b'=') 

335 

336 

337def _urlsafe_b64decode(b64string): 

338 # Guard against unicode strings, which base64 can't handle. 

339 b64string = _to_bytes(b64string) 

340 padded = b64string + b'=' * (4 - len(b64string) % 4) 

341 return base64.urlsafe_b64decode(padded)