Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(66)

Side by Side Diff: third_party/google-endpoints/apitools/base/protorpclite/util.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 #
3 # Copyright 2010 Google Inc.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17 """Common utility library."""
18 from __future__ import with_statement
19
20 import datetime
21 import functools
22 import inspect
23 import os
24 import re
25 import sys
26
27 import six
28
29 __all__ = [
30 'Error',
31 'decode_datetime',
32 'get_package_for_module',
33 'positional',
34 'TimeZoneOffset',
35 'total_seconds',
36 ]
37
38
39 class Error(Exception):
40 """Base class for protorpc exceptions."""
41
42
43 _TIME_ZONE_RE_STRING = r"""
44 # Examples:
45 # +01:00
46 # -05:30
47 # Z12:00
48 ((?P<z>Z) | (?P<sign>[-+])
49 (?P<hours>\d\d) :
50 (?P<minutes>\d\d))$
51 """
52 _TIME_ZONE_RE = re.compile(_TIME_ZONE_RE_STRING, re.IGNORECASE | re.VERBOSE)
53
54
55 def positional(max_positional_args):
56 """A decorator to declare that only the first N arguments may be positional.
57
58 This decorator makes it easy to support Python 3 style keyword-only
59 parameters. For example, in Python 3 it is possible to write:
60
61 def fn(pos1, *, kwonly1=None, kwonly1=None):
62 ...
63
64 All named parameters after * must be a keyword:
65
66 fn(10, 'kw1', 'kw2') # Raises exception.
67 fn(10, kwonly1='kw1') # Ok.
68
69 Example:
70 To define a function like above, do:
71
72 @positional(1)
73 def fn(pos1, kwonly1=None, kwonly2=None):
74 ...
75
76 If no default value is provided to a keyword argument, it
77 becomes a required keyword argument:
78
79 @positional(0)
80 def fn(required_kw):
81 ...
82
83 This must be called with the keyword parameter:
84
85 fn() # Raises exception.
86 fn(10) # Raises exception.
87 fn(required_kw=10) # Ok.
88
89 When defining instance or class methods always remember to account for
90 'self' and 'cls':
91
92 class MyClass(object):
93
94 @positional(2)
95 def my_method(self, pos1, kwonly1=None):
96 ...
97
98 @classmethod
99 @positional(2)
100 def my_method(cls, pos1, kwonly1=None):
101 ...
102
103 One can omit the argument to 'positional' altogether, and then no
104 arguments with default values may be passed positionally. This
105 would be equivalent to placing a '*' before the first argument
106 with a default value in Python 3. If there are no arguments with
107 default values, and no argument is given to 'positional', an error
108 is raised.
109
110 @positional
111 def fn(arg1, arg2, required_kw1=None, required_kw2=0):
112 ...
113
114 fn(1, 3, 5) # Raises exception.
115 fn(1, 3) # Ok.
116 fn(1, 3, required_kw1=5) # Ok.
117
118 Args:
119 max_positional_arguments: Maximum number of positional arguments. All
120 parameters after the this index must be keyword only.
121
122 Returns:
123 A decorator that prevents using arguments after max_positional_args from
124 being used as positional parameters.
125
126 Raises:
127 TypeError if a keyword-only argument is provided as a positional
128 parameter.
129 ValueError if no maximum number of arguments is provided and the function
130 has no arguments with default values.
131 """
132 def positional_decorator(wrapped):
133 @functools.wraps(wrapped)
134 def positional_wrapper(*args, **kwargs):
135 if len(args) > max_positional_args:
136 plural_s = ''
137 if max_positional_args != 1:
138 plural_s = 's'
139 raise TypeError('%s() takes at most %d positional argument%s '
140 '(%d given)' % (wrapped.__name__,
141 max_positional_args,
142 plural_s, len(args)))
143 return wrapped(*args, **kwargs)
144 return positional_wrapper
145
146 if isinstance(max_positional_args, six.integer_types):
147 return positional_decorator
148 else:
149 args, _, _, defaults = inspect.getargspec(max_positional_args)
150 if defaults is None:
151 raise ValueError(
152 'Functions with no keyword arguments must specify '
153 'max_positional_args')
154 return positional(len(args) - len(defaults))(max_positional_args)
155
156
157 @positional(1)
158 def get_package_for_module(module):
159 """Get package name for a module.
160
161 Helper calculates the package name of a module.
162
163 Args:
164 module: Module to get name for. If module is a string, try to find
165 module in sys.modules.
166
167 Returns:
168 If module contains 'package' attribute, uses that as package name.
169 Else, if module is not the '__main__' module, the module __name__.
170 Else, the base name of the module file name. Else None.
171 """
172 if isinstance(module, six.string_types):
173 try:
174 module = sys.modules[module]
175 except KeyError:
176 return None
177
178 try:
179 return six.text_type(module.package)
180 except AttributeError:
181 if module.__name__ == '__main__':
182 try:
183 file_name = module.__file__
184 except AttributeError:
185 pass
186 else:
187 base_name = os.path.basename(file_name)
188 split_name = os.path.splitext(base_name)
189 if len(split_name) == 1:
190 return six.text_type(base_name)
191 else:
192 return u'.'.join(split_name[:-1])
193
194 return six.text_type(module.__name__)
195
196
197 def total_seconds(offset):
198 """Backport of offset.total_seconds() from python 2.7+."""
199 seconds = offset.days * 24 * 60 * 60 + offset.seconds
200 microseconds = seconds * 10**6 + offset.microseconds
201 return microseconds / (10**6 * 1.0)
202
203
204 class TimeZoneOffset(datetime.tzinfo):
205 """Time zone information as encoded/decoded for DateTimeFields."""
206
207 def __init__(self, offset):
208 """Initialize a time zone offset.
209
210 Args:
211 offset: Integer or timedelta time zone offset, in minutes from UTC.
212 This can be negative.
213 """
214 super(TimeZoneOffset, self).__init__()
215 if isinstance(offset, datetime.timedelta):
216 offset = total_seconds(offset) / 60
217 self.__offset = offset
218
219 def utcoffset(self, _):
220 """Get the a timedelta with the time zone's offset from UTC.
221
222 Returns:
223 The time zone offset from UTC, as a timedelta.
224 """
225 return datetime.timedelta(minutes=self.__offset)
226
227 def dst(self, _):
228 """Get the daylight savings time offset.
229
230 The formats that ProtoRPC uses to encode/decode time zone
231 information don't contain any information about daylight
232 savings time. So this always returns a timedelta of 0.
233
234 Returns:
235 A timedelta of 0.
236
237 """
238 return datetime.timedelta(0)
239
240
241 def decode_datetime(encoded_datetime):
242 """Decode a DateTimeField parameter from a string to a python datetime.
243
244 Args:
245 encoded_datetime: A string in RFC 3339 format.
246
247 Returns:
248 A datetime object with the date and time specified in encoded_datetime.
249
250 Raises:
251 ValueError: If the string is not in a recognized format.
252 """
253 # Check if the string includes a time zone offset. Break out the
254 # part that doesn't include time zone info. Convert to uppercase
255 # because all our comparisons should be case-insensitive.
256 time_zone_match = _TIME_ZONE_RE.search(encoded_datetime)
257 if time_zone_match:
258 time_string = encoded_datetime[:time_zone_match.start(1)].upper()
259 else:
260 time_string = encoded_datetime.upper()
261
262 if '.' in time_string:
263 format_string = '%Y-%m-%dT%H:%M:%S.%f'
264 else:
265 format_string = '%Y-%m-%dT%H:%M:%S'
266
267 decoded_datetime = datetime.datetime.strptime(time_string, format_string)
268
269 if not time_zone_match:
270 return decoded_datetime
271
272 # Time zone info was included in the parameter. Add a tzinfo
273 # object to the datetime. Datetimes can't be changed after they're
274 # created, so we'll need to create a new one.
275 if time_zone_match.group('z'):
276 offset_minutes = 0
277 else:
278 sign = time_zone_match.group('sign')
279 hours, minutes = [int(value) for value in
280 time_zone_match.group('hours', 'minutes')]
281 offset_minutes = hours * 60 + minutes
282 if sign == '-':
283 offset_minutes *= -1
284
285 return datetime.datetime(decoded_datetime.year,
286 decoded_datetime.month,
287 decoded_datetime.day,
288 decoded_datetime.hour,
289 decoded_datetime.minute,
290 decoded_datetime.second,
291 decoded_datetime.microsecond,
292 TimeZoneOffset(offset_minutes))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698