| Index: third_party/google-endpoints/apitools/base/protorpclite/util.py
|
| diff --git a/third_party/google-endpoints/apitools/base/protorpclite/util.py b/third_party/google-endpoints/apitools/base/protorpclite/util.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4df045869d7a8fccfa99de8002009ab6ef81866e
|
| --- /dev/null
|
| +++ b/third_party/google-endpoints/apitools/base/protorpclite/util.py
|
| @@ -0,0 +1,292 @@
|
| +#!/usr/bin/env python
|
| +#
|
| +# Copyright 2010 Google Inc.
|
| +#
|
| +# 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.
|
| +#
|
| +"""Common utility library."""
|
| +from __future__ import with_statement
|
| +
|
| +import datetime
|
| +import functools
|
| +import inspect
|
| +import os
|
| +import re
|
| +import sys
|
| +
|
| +import six
|
| +
|
| +__all__ = [
|
| + 'Error',
|
| + 'decode_datetime',
|
| + 'get_package_for_module',
|
| + 'positional',
|
| + 'TimeZoneOffset',
|
| + 'total_seconds',
|
| +]
|
| +
|
| +
|
| +class Error(Exception):
|
| + """Base class for protorpc exceptions."""
|
| +
|
| +
|
| +_TIME_ZONE_RE_STRING = r"""
|
| + # Examples:
|
| + # +01:00
|
| + # -05:30
|
| + # Z12:00
|
| + ((?P<z>Z) | (?P<sign>[-+])
|
| + (?P<hours>\d\d) :
|
| + (?P<minutes>\d\d))$
|
| +"""
|
| +_TIME_ZONE_RE = re.compile(_TIME_ZONE_RE_STRING, re.IGNORECASE | re.VERBOSE)
|
| +
|
| +
|
| +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, kwonly1=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):
|
| + ...
|
| +
|
| + One can omit the argument to 'positional' altogether, and then no
|
| + arguments with default values may be passed positionally. This
|
| + would be equivalent to placing a '*' before the first argument
|
| + with a default value in Python 3. If there are no arguments with
|
| + default values, and no argument is given to 'positional', an error
|
| + is raised.
|
| +
|
| + @positional
|
| + def fn(arg1, arg2, required_kw1=None, required_kw2=0):
|
| + ...
|
| +
|
| + fn(1, 3, 5) # Raises exception.
|
| + fn(1, 3) # Ok.
|
| + fn(1, 3, required_kw1=5) # Ok.
|
| +
|
| + Args:
|
| + max_positional_arguments: Maximum number of positional arguments. All
|
| + parameters after the 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.
|
| + ValueError if no maximum number of arguments is provided and the function
|
| + has no arguments with default values.
|
| + """
|
| + 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'
|
| + raise TypeError('%s() takes at most %d positional argument%s '
|
| + '(%d given)' % (wrapped.__name__,
|
| + max_positional_args,
|
| + plural_s, len(args)))
|
| + return wrapped(*args, **kwargs)
|
| + return positional_wrapper
|
| +
|
| + if isinstance(max_positional_args, six.integer_types):
|
| + return positional_decorator
|
| + else:
|
| + args, _, _, defaults = inspect.getargspec(max_positional_args)
|
| + if defaults is None:
|
| + raise ValueError(
|
| + 'Functions with no keyword arguments must specify '
|
| + 'max_positional_args')
|
| + return positional(len(args) - len(defaults))(max_positional_args)
|
| +
|
| +
|
| +@positional(1)
|
| +def get_package_for_module(module):
|
| + """Get package name for a module.
|
| +
|
| + Helper calculates the package name of a module.
|
| +
|
| + Args:
|
| + module: Module to get name for. If module is a string, try to find
|
| + module in sys.modules.
|
| +
|
| + Returns:
|
| + If module contains 'package' attribute, uses that as package name.
|
| + Else, if module is not the '__main__' module, the module __name__.
|
| + Else, the base name of the module file name. Else None.
|
| + """
|
| + if isinstance(module, six.string_types):
|
| + try:
|
| + module = sys.modules[module]
|
| + except KeyError:
|
| + return None
|
| +
|
| + try:
|
| + return six.text_type(module.package)
|
| + except AttributeError:
|
| + if module.__name__ == '__main__':
|
| + try:
|
| + file_name = module.__file__
|
| + except AttributeError:
|
| + pass
|
| + else:
|
| + base_name = os.path.basename(file_name)
|
| + split_name = os.path.splitext(base_name)
|
| + if len(split_name) == 1:
|
| + return six.text_type(base_name)
|
| + else:
|
| + return u'.'.join(split_name[:-1])
|
| +
|
| + return six.text_type(module.__name__)
|
| +
|
| +
|
| +def total_seconds(offset):
|
| + """Backport of offset.total_seconds() from python 2.7+."""
|
| + seconds = offset.days * 24 * 60 * 60 + offset.seconds
|
| + microseconds = seconds * 10**6 + offset.microseconds
|
| + return microseconds / (10**6 * 1.0)
|
| +
|
| +
|
| +class TimeZoneOffset(datetime.tzinfo):
|
| + """Time zone information as encoded/decoded for DateTimeFields."""
|
| +
|
| + def __init__(self, offset):
|
| + """Initialize a time zone offset.
|
| +
|
| + Args:
|
| + offset: Integer or timedelta time zone offset, in minutes from UTC.
|
| + This can be negative.
|
| + """
|
| + super(TimeZoneOffset, self).__init__()
|
| + if isinstance(offset, datetime.timedelta):
|
| + offset = total_seconds(offset) / 60
|
| + self.__offset = offset
|
| +
|
| + def utcoffset(self, _):
|
| + """Get the a timedelta with the time zone's offset from UTC.
|
| +
|
| + Returns:
|
| + The time zone offset from UTC, as a timedelta.
|
| + """
|
| + return datetime.timedelta(minutes=self.__offset)
|
| +
|
| + def dst(self, _):
|
| + """Get the daylight savings time offset.
|
| +
|
| + The formats that ProtoRPC uses to encode/decode time zone
|
| + information don't contain any information about daylight
|
| + savings time. So this always returns a timedelta of 0.
|
| +
|
| + Returns:
|
| + A timedelta of 0.
|
| +
|
| + """
|
| + return datetime.timedelta(0)
|
| +
|
| +
|
| +def decode_datetime(encoded_datetime):
|
| + """Decode a DateTimeField parameter from a string to a python datetime.
|
| +
|
| + Args:
|
| + encoded_datetime: A string in RFC 3339 format.
|
| +
|
| + Returns:
|
| + A datetime object with the date and time specified in encoded_datetime.
|
| +
|
| + Raises:
|
| + ValueError: If the string is not in a recognized format.
|
| + """
|
| + # Check if the string includes a time zone offset. Break out the
|
| + # part that doesn't include time zone info. Convert to uppercase
|
| + # because all our comparisons should be case-insensitive.
|
| + time_zone_match = _TIME_ZONE_RE.search(encoded_datetime)
|
| + if time_zone_match:
|
| + time_string = encoded_datetime[:time_zone_match.start(1)].upper()
|
| + else:
|
| + time_string = encoded_datetime.upper()
|
| +
|
| + if '.' in time_string:
|
| + format_string = '%Y-%m-%dT%H:%M:%S.%f'
|
| + else:
|
| + format_string = '%Y-%m-%dT%H:%M:%S'
|
| +
|
| + decoded_datetime = datetime.datetime.strptime(time_string, format_string)
|
| +
|
| + if not time_zone_match:
|
| + return decoded_datetime
|
| +
|
| + # Time zone info was included in the parameter. Add a tzinfo
|
| + # object to the datetime. Datetimes can't be changed after they're
|
| + # created, so we'll need to create a new one.
|
| + if time_zone_match.group('z'):
|
| + offset_minutes = 0
|
| + else:
|
| + sign = time_zone_match.group('sign')
|
| + hours, minutes = [int(value) for value in
|
| + time_zone_match.group('hours', 'minutes')]
|
| + offset_minutes = hours * 60 + minutes
|
| + if sign == '-':
|
| + offset_minutes *= -1
|
| +
|
| + return datetime.datetime(decoded_datetime.year,
|
| + decoded_datetime.month,
|
| + decoded_datetime.day,
|
| + decoded_datetime.hour,
|
| + decoded_datetime.minute,
|
| + decoded_datetime.second,
|
| + decoded_datetime.microsecond,
|
| + TimeZoneOffset(offset_minutes))
|
|
|