OLD | NEW |
| (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 | |
18 """Common utility library.""" | |
19 | |
20 __author__ = ['rafek@google.com (Rafe Kaplan)', | |
21 'guido@google.com (Guido van Rossum)', | |
22 ] | |
23 __all__ = [ | |
24 'positional', | |
25 ] | |
26 | |
27 import gflags | |
28 import inspect | |
29 import logging | |
30 import types | |
31 import urllib | |
32 import urlparse | |
33 | |
34 try: | |
35 from urlparse import parse_qsl | |
36 except ImportError: | |
37 from cgi import parse_qsl | |
38 | |
39 logger = logging.getLogger(__name__) | |
40 | |
41 FLAGS = gflags.FLAGS | |
42 | |
43 gflags.DEFINE_enum('positional_parameters_enforcement', 'WARNING', | |
44 ['EXCEPTION', 'WARNING', 'IGNORE'], | |
45 'The action when an oauth2client.util.positional declaration is violated.') | |
46 | |
47 | |
48 def positional(max_positional_args): | |
49 """A decorator to declare that only the first N arguments my be positional. | |
50 | |
51 This decorator makes it easy to support Python 3 style key-word only | |
52 parameters. For example, in Python 3 it is possible to write: | |
53 | |
54 def fn(pos1, *, kwonly1=None, kwonly1=None): | |
55 ... | |
56 | |
57 All named parameters after * must be a keyword: | |
58 | |
59 fn(10, 'kw1', 'kw2') # Raises exception. | |
60 fn(10, kwonly1='kw1') # Ok. | |
61 | |
62 Example: | |
63 To define a function like above, do: | |
64 | |
65 @positional(1) | |
66 def fn(pos1, kwonly1=None, kwonly2=None): | |
67 ... | |
68 | |
69 If no default value is provided to a keyword argument, it becomes a required | |
70 keyword argument: | |
71 | |
72 @positional(0) | |
73 def fn(required_kw): | |
74 ... | |
75 | |
76 This must be called with the keyword parameter: | |
77 | |
78 fn() # Raises exception. | |
79 fn(10) # Raises exception. | |
80 fn(required_kw=10) # Ok. | |
81 | |
82 When defining instance or class methods always remember to account for | |
83 'self' and 'cls': | |
84 | |
85 class MyClass(object): | |
86 | |
87 @positional(2) | |
88 def my_method(self, pos1, kwonly1=None): | |
89 ... | |
90 | |
91 @classmethod | |
92 @positional(2) | |
93 def my_method(cls, pos1, kwonly1=None): | |
94 ... | |
95 | |
96 The positional decorator behavior is controlled by the | |
97 --positional_parameters_enforcement flag. The flag may be set to 'EXCEPTION', | |
98 'WARNING' or 'IGNORE' to raise an exception, log a warning, or do nothing, | |
99 respectively, if a declaration is violated. | |
100 | |
101 Args: | |
102 max_positional_arguments: Maximum number of positional arguments. All | |
103 parameters after the this index must be keyword only. | |
104 | |
105 Returns: | |
106 A decorator that prevents using arguments after max_positional_args from | |
107 being used as positional parameters. | |
108 | |
109 Raises: | |
110 TypeError if a key-word only argument is provided as a positional parameter, | |
111 but only if the --positional_parameters_enforcement flag is set to | |
112 'EXCEPTION'. | |
113 """ | |
114 def positional_decorator(wrapped): | |
115 def positional_wrapper(*args, **kwargs): | |
116 if len(args) > max_positional_args: | |
117 plural_s = '' | |
118 if max_positional_args != 1: | |
119 plural_s = 's' | |
120 message = '%s() takes at most %d positional argument%s (%d given)' % ( | |
121 wrapped.__name__, max_positional_args, plural_s, len(args)) | |
122 if FLAGS.positional_parameters_enforcement == 'EXCEPTION': | |
123 raise TypeError(message) | |
124 elif FLAGS.positional_parameters_enforcement == 'WARNING': | |
125 logger.warning(message) | |
126 else: # IGNORE | |
127 pass | |
128 return wrapped(*args, **kwargs) | |
129 return positional_wrapper | |
130 | |
131 if isinstance(max_positional_args, (int, long)): | |
132 return positional_decorator | |
133 else: | |
134 args, _, _, defaults = inspect.getargspec(max_positional_args) | |
135 return positional(len(args) - len(defaults))(max_positional_args) | |
136 | |
137 | |
138 def scopes_to_string(scopes): | |
139 """Converts scope value to a string. | |
140 | |
141 If scopes is a string then it is simply passed through. If scopes is an | |
142 iterable then a string is returned that is all the individual scopes | |
143 concatenated with spaces. | |
144 | |
145 Args: | |
146 scopes: string or iterable of strings, the scopes. | |
147 | |
148 Returns: | |
149 The scopes formatted as a single string. | |
150 """ | |
151 if isinstance(scopes, types.StringTypes): | |
152 return scopes | |
153 else: | |
154 return ' '.join(scopes) | |
155 | |
156 | |
157 def dict_to_tuple_key(dictionary): | |
158 """Converts a dictionary to a tuple that can be used as an immutable key. | |
159 | |
160 The resulting key is always sorted so that logically equivalent dictionaries | |
161 always produce an identical tuple for a key. | |
162 | |
163 Args: | |
164 dictionary: the dictionary to use as the key. | |
165 | |
166 Returns: | |
167 A tuple representing the dictionary in it's naturally sorted ordering. | |
168 """ | |
169 return tuple(sorted(dictionary.items())) | |
170 | |
171 | |
172 def _add_query_parameter(url, name, value): | |
173 """Adds a query parameter to a url. | |
174 | |
175 Replaces the current value if it already exists in the URL. | |
176 | |
177 Args: | |
178 url: string, url to add the query parameter to. | |
179 name: string, query parameter name. | |
180 value: string, query parameter value. | |
181 | |
182 Returns: | |
183 Updated query parameter. Does not update the url if value is None. | |
184 """ | |
185 if value is None: | |
186 return url | |
187 else: | |
188 parsed = list(urlparse.urlparse(url)) | |
189 q = dict(parse_qsl(parsed[4])) | |
190 q[name] = value | |
191 parsed[4] = urllib.urlencode(q) | |
192 return urlparse.urlunparse(parsed) | |
OLD | NEW |