OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # | 2 # |
3 # Copyright 2010 Google Inc. | 3 # Copyright 2014 Google Inc. All rights reserved. |
4 # | 4 # |
5 # Licensed under the Apache License, Version 2.0 (the "License"); | 5 # Licensed under the Apache License, Version 2.0 (the "License"); |
6 # you may not use this file except in compliance with 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 | 7 # You may obtain a copy of the License at |
8 # | 8 # |
9 # http://www.apache.org/licenses/LICENSE-2.0 | 9 # http://www.apache.org/licenses/LICENSE-2.0 |
10 # | 10 # |
11 # Unless required by applicable law or agreed to in writing, software | 11 # Unless required by applicable law or agreed to in writing, software |
12 # distributed under the License is distributed on an "AS IS" BASIS, | 12 # distributed under the License is distributed on an "AS IS" BASIS, |
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
14 # See the License for the specific language governing permissions and | 14 # See the License for the specific language governing permissions and |
15 # limitations under the License. | 15 # limitations under the License. |
16 # | 16 # |
17 | 17 |
18 """Common utility library.""" | 18 """Common utility library.""" |
19 | 19 |
20 __author__ = ['rafek@google.com (Rafe Kaplan)', | 20 __author__ = [ |
21 'guido@google.com (Guido van Rossum)', | 21 'rafek@google.com (Rafe Kaplan)', |
22 ] | 22 'guido@google.com (Guido van Rossum)', |
23 __all__ = [ | |
24 'positional', | |
25 'POSITIONAL_WARNING', | |
26 'POSITIONAL_EXCEPTION', | |
27 'POSITIONAL_IGNORE', | |
28 ] | 23 ] |
29 | 24 |
| 25 __all__ = [ |
| 26 'positional', |
| 27 'POSITIONAL_WARNING', |
| 28 'POSITIONAL_EXCEPTION', |
| 29 'POSITIONAL_IGNORE', |
| 30 ] |
| 31 |
| 32 import functools |
30 import inspect | 33 import inspect |
31 import logging | 34 import logging |
| 35 import sys |
32 import types | 36 import types |
33 import urllib | |
34 import urlparse | |
35 | 37 |
36 try: | 38 from third_party import six |
37 from urlparse import parse_qsl | 39 from third_party.six.moves import urllib |
38 except ImportError: | 40 |
39 from cgi import parse_qsl | |
40 | 41 |
41 logger = logging.getLogger(__name__) | 42 logger = logging.getLogger(__name__) |
42 | 43 |
43 POSITIONAL_WARNING = 'WARNING' | 44 POSITIONAL_WARNING = 'WARNING' |
44 POSITIONAL_EXCEPTION = 'EXCEPTION' | 45 POSITIONAL_EXCEPTION = 'EXCEPTION' |
45 POSITIONAL_IGNORE = 'IGNORE' | 46 POSITIONAL_IGNORE = 'IGNORE' |
46 POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION, | 47 POSITIONAL_SET = frozenset([POSITIONAL_WARNING, POSITIONAL_EXCEPTION, |
47 POSITIONAL_IGNORE]) | 48 POSITIONAL_IGNORE]) |
48 | 49 |
49 positional_parameters_enforcement = POSITIONAL_WARNING | 50 positional_parameters_enforcement = POSITIONAL_WARNING |
50 | 51 |
51 def positional(max_positional_args): | 52 def positional(max_positional_args): |
52 """A decorator to declare that only the first N arguments my be positional. | 53 """A decorator to declare that only the first N arguments my be positional. |
53 | 54 |
54 This decorator makes it easy to support Python 3 style key-word only | 55 This decorator makes it easy to support Python 3 style keyword-only |
55 parameters. For example, in Python 3 it is possible to write: | 56 parameters. For example, in Python 3 it is possible to write:: |
56 | 57 |
57 def fn(pos1, *, kwonly1=None, kwonly1=None): | 58 def fn(pos1, *, kwonly1=None, kwonly1=None): |
58 ... | 59 ... |
59 | 60 |
60 All named parameters after * must be a keyword: | 61 All named parameters after ``*`` must be a keyword:: |
61 | 62 |
62 fn(10, 'kw1', 'kw2') # Raises exception. | 63 fn(10, 'kw1', 'kw2') # Raises exception. |
63 fn(10, kwonly1='kw1') # Ok. | 64 fn(10, kwonly1='kw1') # Ok. |
64 | 65 |
65 Example: | 66 Example |
66 To define a function like above, do: | 67 ^^^^^^^ |
67 | 68 |
68 @positional(1) | 69 To define a function like above, do:: |
69 def fn(pos1, kwonly1=None, kwonly2=None): | 70 |
| 71 @positional(1) |
| 72 def fn(pos1, kwonly1=None, kwonly2=None): |
| 73 ... |
| 74 |
| 75 If no default value is provided to a keyword argument, it becomes a required |
| 76 keyword argument:: |
| 77 |
| 78 @positional(0) |
| 79 def fn(required_kw): |
| 80 ... |
| 81 |
| 82 This must be called with the keyword parameter:: |
| 83 |
| 84 fn() # Raises exception. |
| 85 fn(10) # Raises exception. |
| 86 fn(required_kw=10) # Ok. |
| 87 |
| 88 When defining instance or class methods always remember to account for |
| 89 ``self`` and ``cls``:: |
| 90 |
| 91 class MyClass(object): |
| 92 |
| 93 @positional(2) |
| 94 def my_method(self, pos1, kwonly1=None): |
70 ... | 95 ... |
71 | 96 |
72 If no default value is provided to a keyword argument, it becomes a required | 97 @classmethod |
73 keyword argument: | 98 @positional(2) |
74 | 99 def my_method(cls, pos1, kwonly1=None): |
75 @positional(0) | |
76 def fn(required_kw): | |
77 ... | 100 ... |
78 | 101 |
79 This must be called with the keyword parameter: | |
80 | |
81 fn() # Raises exception. | |
82 fn(10) # Raises exception. | |
83 fn(required_kw=10) # Ok. | |
84 | |
85 When defining instance or class methods always remember to account for | |
86 'self' and 'cls': | |
87 | |
88 class MyClass(object): | |
89 | |
90 @positional(2) | |
91 def my_method(self, pos1, kwonly1=None): | |
92 ... | |
93 | |
94 @classmethod | |
95 @positional(2) | |
96 def my_method(cls, pos1, kwonly1=None): | |
97 ... | |
98 | |
99 The positional decorator behavior is controlled by | 102 The positional decorator behavior is controlled by |
100 util.positional_parameters_enforcement, which may be set to | 103 ``util.positional_parameters_enforcement``, which may be set to |
101 POSITIONAL_EXCEPTION, POSITIONAL_WARNING or POSITIONAL_IGNORE to raise an | 104 ``POSITIONAL_EXCEPTION``, ``POSITIONAL_WARNING`` or |
102 exception, log a warning, or do nothing, respectively, if a declaration is | 105 ``POSITIONAL_IGNORE`` to raise an exception, log a warning, or do |
103 violated. | 106 nothing, respectively, if a declaration is violated. |
104 | 107 |
105 Args: | 108 Args: |
106 max_positional_arguments: Maximum number of positional arguments. All | 109 max_positional_arguments: Maximum number of positional arguments. All |
107 parameters after the this index must be keyword only. | 110 parameters after the this index must be keyword only. |
108 | 111 |
109 Returns: | 112 Returns: |
110 A decorator that prevents using arguments after max_positional_args from | 113 A decorator that prevents using arguments after max_positional_args from |
111 being used as positional parameters. | 114 being used as positional parameters. |
112 | 115 |
113 Raises: | 116 Raises: |
114 TypeError if a key-word only argument is provided as a positional | 117 TypeError if a key-word only argument is provided as a positional |
115 parameter, but only if util.positional_parameters_enforcement is set to | 118 parameter, but only if util.positional_parameters_enforcement is set to |
116 POSITIONAL_EXCEPTION. | 119 POSITIONAL_EXCEPTION. |
| 120 |
117 """ | 121 """ |
118 def positional_decorator(wrapped): | 122 def positional_decorator(wrapped): |
| 123 @functools.wraps(wrapped) |
119 def positional_wrapper(*args, **kwargs): | 124 def positional_wrapper(*args, **kwargs): |
120 if len(args) > max_positional_args: | 125 if len(args) > max_positional_args: |
121 plural_s = '' | 126 plural_s = '' |
122 if max_positional_args != 1: | 127 if max_positional_args != 1: |
123 plural_s = 's' | 128 plural_s = 's' |
124 message = '%s() takes at most %d positional argument%s (%d given)' % ( | 129 message = '%s() takes at most %d positional argument%s (%d given)' % ( |
125 wrapped.__name__, max_positional_args, plural_s, len(args)) | 130 wrapped.__name__, max_positional_args, plural_s, len(args)) |
126 if positional_parameters_enforcement == POSITIONAL_EXCEPTION: | 131 if positional_parameters_enforcement == POSITIONAL_EXCEPTION: |
127 raise TypeError(message) | 132 raise TypeError(message) |
128 elif positional_parameters_enforcement == POSITIONAL_WARNING: | 133 elif positional_parameters_enforcement == POSITIONAL_WARNING: |
129 logger.warning(message) | 134 logger.warning(message) |
130 else: # IGNORE | 135 else: # IGNORE |
131 pass | 136 pass |
132 return wrapped(*args, **kwargs) | 137 return wrapped(*args, **kwargs) |
133 return positional_wrapper | 138 return positional_wrapper |
134 | 139 |
135 if isinstance(max_positional_args, (int, long)): | 140 if isinstance(max_positional_args, six.integer_types): |
136 return positional_decorator | 141 return positional_decorator |
137 else: | 142 else: |
138 args, _, _, defaults = inspect.getargspec(max_positional_args) | 143 args, _, _, defaults = inspect.getargspec(max_positional_args) |
139 return positional(len(args) - len(defaults))(max_positional_args) | 144 return positional(len(args) - len(defaults))(max_positional_args) |
140 | 145 |
141 | 146 |
142 def scopes_to_string(scopes): | 147 def scopes_to_string(scopes): |
143 """Converts scope value to a string. | 148 """Converts scope value to a string. |
144 | 149 |
145 If scopes is a string then it is simply passed through. If scopes is an | 150 If scopes is a string then it is simply passed through. If scopes is an |
146 iterable then a string is returned that is all the individual scopes | 151 iterable then a string is returned that is all the individual scopes |
147 concatenated with spaces. | 152 concatenated with spaces. |
148 | 153 |
149 Args: | 154 Args: |
150 scopes: string or iterable of strings, the scopes. | 155 scopes: string or iterable of strings, the scopes. |
151 | 156 |
152 Returns: | 157 Returns: |
153 The scopes formatted as a single string. | 158 The scopes formatted as a single string. |
154 """ | 159 """ |
155 if isinstance(scopes, types.StringTypes): | 160 if isinstance(scopes, six.string_types): |
156 return scopes | 161 return scopes |
157 else: | 162 else: |
158 return ' '.join(scopes) | 163 return ' '.join(scopes) |
159 | 164 |
160 | 165 |
161 def dict_to_tuple_key(dictionary): | 166 def dict_to_tuple_key(dictionary): |
162 """Converts a dictionary to a tuple that can be used as an immutable key. | 167 """Converts a dictionary to a tuple that can be used as an immutable key. |
163 | 168 |
164 The resulting key is always sorted so that logically equivalent dictionaries | 169 The resulting key is always sorted so that logically equivalent dictionaries |
165 always produce an identical tuple for a key. | 170 always produce an identical tuple for a key. |
(...skipping 16 matching lines...) Expand all Loading... |
182 url: string, url to add the query parameter to. | 187 url: string, url to add the query parameter to. |
183 name: string, query parameter name. | 188 name: string, query parameter name. |
184 value: string, query parameter value. | 189 value: string, query parameter value. |
185 | 190 |
186 Returns: | 191 Returns: |
187 Updated query parameter. Does not update the url if value is None. | 192 Updated query parameter. Does not update the url if value is None. |
188 """ | 193 """ |
189 if value is None: | 194 if value is None: |
190 return url | 195 return url |
191 else: | 196 else: |
192 parsed = list(urlparse.urlparse(url)) | 197 parsed = list(urllib.parse.urlparse(url)) |
193 q = dict(parse_qsl(parsed[4])) | 198 q = dict(urllib.parse.parse_qsl(parsed[4])) |
194 q[name] = value | 199 q[name] = value |
195 parsed[4] = urllib.urlencode(q) | 200 parsed[4] = urllib.parse.urlencode(q) |
196 return urlparse.urlunparse(parsed) | 201 return urllib.parse.urlunparse(parsed) |
OLD | NEW |