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
« 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.
15"""Helper functions for commonly used utilities."""
17import functools
18import inspect
19import logging
20import urllib
22logger = logging.getLogger(__name__)
24POSITIONAL_WARNING = "WARNING"
25POSITIONAL_EXCEPTION = "EXCEPTION"
26POSITIONAL_IGNORE = "IGNORE"
27POSITIONAL_SET = frozenset(
28 [POSITIONAL_WARNING, POSITIONAL_EXCEPTION, POSITIONAL_IGNORE]
29)
31positional_parameters_enforcement = POSITIONAL_WARNING
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"
38def positional(max_positional_args):
39 """A decorator to declare that only the first N arguments may be positional.
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::
44 def fn(pos1, *, kwonly1=None, kwonly2=None):
45 ...
47 All named parameters after ``*`` must be a keyword::
49 fn(10, 'kw1', 'kw2') # Raises exception.
50 fn(10, kwonly1='kw1') # Ok.
52 Example
53 ^^^^^^^
55 To define a function like above, do::
57 @positional(1)
58 def fn(pos1, kwonly1=None, kwonly2=None):
59 ...
61 If no default value is provided to a keyword argument, it becomes a
62 required keyword argument::
64 @positional(0)
65 def fn(required_kw):
66 ...
68 This must be called with the keyword parameter::
70 fn() # Raises exception.
71 fn(10) # Raises exception.
72 fn(required_kw=10) # Ok.
74 When defining instance or class methods always remember to account for
75 ``self`` and ``cls``::
77 class MyClass(object):
79 @positional(2)
80 def my_method(self, pos1, kwonly1=None):
81 ...
83 @classmethod
84 @positional(2)
85 def my_method(cls, pos1, kwonly1=None):
86 ...
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.
94 Args:
95 max_positional_arguments: Maximum number of positional arguments. All
96 parameters after this index must be
97 keyword only.
99 Returns:
100 A decorator that prevents using arguments after max_positional_args
101 from being used as positional parameters.
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 """
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)
132 return positional_wrapper
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)
141def parse_unique_urlencoded(content):
142 """Parses unique key-value parameters from urlencoded content.
144 Args:
145 content: string, URL-encoded key-value pairs.
147 Returns:
148 dict, The key-value pairs from ``content``.
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
166def update_query_params(uri, params):
167 """Updates a URI with new query parameters.
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.
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).
176 Args:
177 uri: string, A valid URI, with potential existing query parameters.
178 params: dict, A dictionary of query parameters.
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)
191def _add_query_parameter(url, name, value):
192 """Adds a query parameter to a url.
194 Replaces the current value if it already exists in the URL.
196 Args:
197 url: string, url to add the query parameter to.
198 name: string, query parameter name.
199 value: string, query parameter value.
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})