Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/oauth2client/_helpers.py: 72%
98 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:28 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:28 +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.
15"""Helper functions for commonly used utilities."""
17import base64
18import functools
19import inspect
20import json
21import logging
22import os
23import warnings
25import six
26from six.moves import urllib
29logger = logging.getLogger(__name__)
31POSITIONAL_WARNING = 'WARNING'
32POSITIONAL_EXCEPTION = 'EXCEPTION'
33POSITIONAL_IGNORE = 'IGNORE'
34POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION,
35 POSITIONAL_IGNORE])
37positional_parameters_enforcement = POSITIONAL_WARNING
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'
44def positional(max_positional_args):
45 """A decorator to declare that only the first N arguments my be positional.
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::
50 def fn(pos1, *, kwonly1=None, kwonly1=None):
51 ...
53 All named parameters after ``*`` must be a keyword::
55 fn(10, 'kw1', 'kw2') # Raises exception.
56 fn(10, kwonly1='kw1') # Ok.
58 Example
59 ^^^^^^^
61 To define a function like above, do::
63 @positional(1)
64 def fn(pos1, kwonly1=None, kwonly2=None):
65 ...
67 If no default value is provided to a keyword argument, it becomes a
68 required keyword argument::
70 @positional(0)
71 def fn(required_kw):
72 ...
74 This must be called with the keyword parameter::
76 fn() # Raises exception.
77 fn(10) # Raises exception.
78 fn(required_kw=10) # Ok.
80 When defining instance or class methods always remember to account for
81 ``self`` and ``cls``::
83 class MyClass(object):
85 @positional(2)
86 def my_method(self, pos1, kwonly1=None):
87 ...
89 @classmethod
90 @positional(2)
91 def my_method(cls, pos1, kwonly1=None):
92 ...
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.
100 Args:
101 max_positional_arguments: Maximum number of positional arguments. All
102 parameters after the this index must be
103 keyword only.
105 Returns:
106 A decorator that prevents using arguments after max_positional_args
107 from being used as positional parameters.
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 """
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
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)
143def scopes_to_string(scopes):
144 """Converts scope value to a string.
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.
150 Args:
151 scopes: string or iterable of strings, the scopes.
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)
162def string_to_scopes(scopes):
163 """Converts stringifed scope value to a list.
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.
168 Args:
169 scopes: a string or iterable of strings, the scopes.
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
182def parse_unique_urlencoded(content):
183 """Parses unique key-value parameters from urlencoded content.
185 Args:
186 content: string, URL-encoded key-value pairs.
188 Returns:
189 dict, The key-value pairs from ``content``.
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
205def update_query_params(uri, params):
206 """Updates a URI with new query parameters.
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.
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).
215 Args:
216 uri: string, A valid URI, with potential existing query parameters.
217 params: dict, A dictionary of query parameters.
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)
230def _add_query_parameter(url, name, value):
231 """Adds a query parameter to a url.
233 Replaces the current value if it already exists in the URL.
235 Args:
236 url: string, url to add the query parameter to.
237 name: string, query parameter name.
238 value: string, query parameter value.
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})
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))
258def _parse_pem_key(raw_key_input):
259 """Identify and extract PEM keys.
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.
264 Args:
265 raw_key_input: The contents of a private key file (either PEM or
266 PKCS12).
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:]
277def _json_encode(data):
278 return json.dumps(data, separators=(',', ':'))
281def _to_bytes(value, encoding='ascii'):
282 """Converts a string value to bytes, if necessary.
284 Unfortunately, ``six.b`` is insufficient for this task since in
285 Python2 it does not modify ``unicode`` objects.
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.
295 Returns:
296 The original value converted to bytes (if unicode) or as passed in
297 if it started out as bytes.
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))
310def _from_bytes(value):
311 """Converts bytes to a string value, if necessary.
313 Args:
314 value: The string/bytes value to be converted.
316 Returns:
317 The original value converted to unicode (if bytes) or as passed in
318 if it started out as unicode.
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))
332def _urlsafe_b64encode(raw_bytes):
333 raw_bytes = _to_bytes(raw_bytes, encoding='utf-8')
334 return base64.urlsafe_b64encode(raw_bytes).rstrip(b'=')
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)