| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- # Copyright 2015 Google Inc. All rights reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- """Helper functions for commonly used utilities."""
- import functools
- import inspect
- import logging
- import urllib
- logger = logging.getLogger(__name__)
- POSITIONAL_WARNING = "WARNING"
- POSITIONAL_EXCEPTION = "EXCEPTION"
- POSITIONAL_IGNORE = "IGNORE"
- POSITIONAL_SET = frozenset(
- [POSITIONAL_WARNING, POSITIONAL_EXCEPTION, POSITIONAL_IGNORE]
- )
- positional_parameters_enforcement = POSITIONAL_WARNING
- _SYM_LINK_MESSAGE = "File: {0}: Is a symbolic link."
- _IS_DIR_MESSAGE = "{0}: Is a directory"
- _MISSING_FILE_MESSAGE = "Cannot access {0}: No such file or directory"
- def positional(max_positional_args):
- """A decorator to declare that only the first N arguments may be positional.
- This decorator makes it easy to support Python 3 style keyword-only
- parameters. For example, in Python 3 it is possible to write::
- def fn(pos1, *, kwonly1=None, kwonly2=None):
- ...
- All named parameters after ``*`` must be a keyword::
- fn(10, 'kw1', 'kw2') # Raises exception.
- fn(10, kwonly1='kw1') # Ok.
- Example
- ^^^^^^^
- To define a function like above, do::
- @positional(1)
- def fn(pos1, kwonly1=None, kwonly2=None):
- ...
- If no default value is provided to a keyword argument, it becomes a
- required keyword argument::
- @positional(0)
- def fn(required_kw):
- ...
- This must be called with the keyword parameter::
- fn() # Raises exception.
- fn(10) # Raises exception.
- fn(required_kw=10) # Ok.
- When defining instance or class methods always remember to account for
- ``self`` and ``cls``::
- class MyClass(object):
- @positional(2)
- def my_method(self, pos1, kwonly1=None):
- ...
- @classmethod
- @positional(2)
- def my_method(cls, pos1, kwonly1=None):
- ...
- The positional decorator behavior is controlled by
- ``_helpers.positional_parameters_enforcement``, which may be set to
- ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or
- ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do
- nothing, respectively, if a declaration is violated.
- Args:
- max_positional_arguments: Maximum number of positional arguments. All
- parameters after this index must be
- keyword only.
- Returns:
- A decorator that prevents using arguments after max_positional_args
- from being used as positional parameters.
- Raises:
- TypeError: if a keyword-only argument is provided as a positional
- parameter, but only if
- _helpers.positional_parameters_enforcement is set to
- POSITIONAL_EXCEPTION.
- """
- def positional_decorator(wrapped):
- @functools.wraps(wrapped)
- def positional_wrapper(*args, **kwargs):
- if len(args) > max_positional_args:
- plural_s = ""
- if max_positional_args != 1:
- plural_s = "s"
- message = (
- "{function}() takes at most {args_max} positional "
- "argument{plural} ({args_given} given)".format(
- function=wrapped.__name__,
- args_max=max_positional_args,
- args_given=len(args),
- plural=plural_s,
- )
- )
- if positional_parameters_enforcement == POSITIONAL_EXCEPTION:
- raise TypeError(message)
- elif positional_parameters_enforcement == POSITIONAL_WARNING:
- logger.warning(message)
- return wrapped(*args, **kwargs)
- return positional_wrapper
- if isinstance(max_positional_args, int):
- return positional_decorator
- else:
- args, _, _, defaults, _, _, _ = inspect.getfullargspec(max_positional_args)
- return positional(len(args) - len(defaults))(max_positional_args)
- def parse_unique_urlencoded(content):
- """Parses unique key-value parameters from urlencoded content.
- Args:
- content: string, URL-encoded key-value pairs.
- Returns:
- dict, The key-value pairs from ``content``.
- Raises:
- ValueError: if one of the keys is repeated.
- """
- urlencoded_params = urllib.parse.parse_qs(content)
- params = {}
- for key, value in urlencoded_params.items():
- if len(value) != 1:
- msg = "URL-encoded content contains a repeated value:" "%s -> %s" % (
- key,
- ", ".join(value),
- )
- raise ValueError(msg)
- params[key] = value[0]
- return params
- def update_query_params(uri, params):
- """Updates a URI with new query parameters.
- If a given key from ``params`` is repeated in the ``uri``, then
- the URI will be considered invalid and an error will occur.
- If the URI is valid, then each value from ``params`` will
- replace the corresponding value in the query parameters (if
- it exists).
- Args:
- uri: string, A valid URI, with potential existing query parameters.
- params: dict, A dictionary of query parameters.
- Returns:
- The same URI but with the new query parameters added.
- """
- parts = urllib.parse.urlparse(uri)
- query_params = parse_unique_urlencoded(parts.query)
- query_params.update(params)
- new_query = urllib.parse.urlencode(query_params)
- new_parts = parts._replace(query=new_query)
- return urllib.parse.urlunparse(new_parts)
- def _add_query_parameter(url, name, value):
- """Adds a query parameter to a url.
- Replaces the current value if it already exists in the URL.
- Args:
- url: string, url to add the query parameter to.
- name: string, query parameter name.
- value: string, query parameter value.
- Returns:
- Updated query parameter. Does not update the url if value is None.
- """
- if value is None:
- return url
- else:
- return update_query_params(url, {name: value})
|