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)) |