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

Side by Side Diff: third_party/google-endpoints/apitools/gen/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 2015 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 """Assorted utilities shared between parts of apitools."""
18 from __future__ import print_function
19
20 import collections
21 import contextlib
22 import json
23 import keyword
24 import logging
25 import os
26 import re
27
28 import six
29 import six.moves.urllib.error as urllib_error
30 import six.moves.urllib.request as urllib_request
31
32
33 class Error(Exception):
34
35 """Base error for apitools generation."""
36
37
38 class CommunicationError(Error):
39
40 """Error in network communication."""
41
42
43 def _SortLengthFirstKey(a):
44 return -len(a), a
45
46
47 class Names(object):
48
49 """Utility class for cleaning and normalizing names in a fixed style."""
50 DEFAULT_NAME_CONVENTION = 'LOWER_CAMEL'
51 NAME_CONVENTIONS = ['LOWER_CAMEL', 'LOWER_WITH_UNDER', 'NONE']
52
53 def __init__(self, strip_prefixes,
54 name_convention=None,
55 capitalize_enums=False):
56 self.__strip_prefixes = sorted(strip_prefixes, key=_SortLengthFirstKey)
57 self.__name_convention = (
58 name_convention or self.DEFAULT_NAME_CONVENTION)
59 self.__capitalize_enums = capitalize_enums
60
61 @staticmethod
62 def __FromCamel(name, separator='_'):
63 name = re.sub(r'([a-z0-9])([A-Z])', r'\1%s\2' % separator, name)
64 return name.lower()
65
66 @staticmethod
67 def __ToCamel(name, separator='_'):
68 # TODO(craigcitro): Consider what to do about leading or trailing
69 # underscores (such as `_refValue` in discovery).
70 return ''.join(s[0:1].upper() + s[1:] for s in name.split(separator))
71
72 @staticmethod
73 def __ToLowerCamel(name, separator='_'):
74 name = Names.__ToCamel(name, separator=separator)
75 return name[0].lower() + name[1:]
76
77 def __StripName(self, name):
78 """Strip strip_prefix entries from name."""
79 if not name:
80 return name
81 for prefix in self.__strip_prefixes:
82 if name.startswith(prefix):
83 return name[len(prefix):]
84 return name
85
86 @staticmethod
87 def CleanName(name):
88 """Perform generic name cleaning."""
89 name = re.sub('[^_A-Za-z0-9]', '_', name)
90 if name[0].isdigit():
91 name = '_%s' % name
92 while keyword.iskeyword(name):
93 name = '%s_' % name
94 # If we end up with __ as a prefix, we'll run afoul of python
95 # field renaming, so we manually correct for it.
96 if name.startswith('__'):
97 name = 'f%s' % name
98 return name
99
100 @staticmethod
101 def NormalizeRelativePath(path):
102 """Normalize camelCase entries in path."""
103 path_components = path.split('/')
104 normalized_components = []
105 for component in path_components:
106 if re.match(r'{[A-Za-z0-9_]+}$', component):
107 normalized_components.append(
108 '{%s}' % Names.CleanName(component[1:-1]))
109 else:
110 normalized_components.append(component)
111 return '/'.join(normalized_components)
112
113 def NormalizeEnumName(self, enum_name):
114 if self.__capitalize_enums:
115 enum_name = enum_name.upper()
116 return self.CleanName(enum_name)
117
118 def ClassName(self, name, separator='_'):
119 """Generate a valid class name from name."""
120 # TODO(craigcitro): Get rid of this case here and in MethodName.
121 if name is None:
122 return name
123 # TODO(craigcitro): This is a hack to handle the case of specific
124 # protorpc class names; clean this up.
125 if name.startswith(('protorpc.', 'message_types.',
126 'apitools.base.protorpclite.',
127 'apitools.base.protorpclite.message_types.')):
128 return name
129 name = self.__StripName(name)
130 name = self.__ToCamel(name, separator=separator)
131 return self.CleanName(name)
132
133 def MethodName(self, name, separator='_'):
134 """Generate a valid method name from name."""
135 if name is None:
136 return None
137 name = Names.__ToCamel(name, separator=separator)
138 return Names.CleanName(name)
139
140 def FieldName(self, name):
141 """Generate a valid field name from name."""
142 # TODO(craigcitro): We shouldn't need to strip this name, but some
143 # of the service names here are excessive. Fix the API and then
144 # remove this.
145 name = self.__StripName(name)
146 if self.__name_convention == 'LOWER_CAMEL':
147 name = Names.__ToLowerCamel(name)
148 elif self.__name_convention == 'LOWER_WITH_UNDER':
149 name = Names.__FromCamel(name)
150 return Names.CleanName(name)
151
152
153 @contextlib.contextmanager
154 def Chdir(dirname, create=True):
155 if not os.path.exists(dirname):
156 if not create:
157 raise OSError('Cannot find directory %s' % dirname)
158 else:
159 os.mkdir(dirname)
160 previous_directory = os.getcwd()
161 try:
162 os.chdir(dirname)
163 yield
164 finally:
165 os.chdir(previous_directory)
166
167
168 def NormalizeVersion(version):
169 # Currently, '.' is the only character that might cause us trouble.
170 return version.replace('.', '_')
171
172
173 class ClientInfo(collections.namedtuple('ClientInfo', (
174 'package', 'scopes', 'version', 'client_id', 'client_secret',
175 'user_agent', 'client_class_name', 'url_version', 'api_key'))):
176
177 """Container for client-related info and names."""
178
179 @classmethod
180 def Create(cls, discovery_doc,
181 scope_ls, client_id, client_secret, user_agent, names, api_key):
182 """Create a new ClientInfo object from a discovery document."""
183 scopes = set(
184 discovery_doc.get('auth', {}).get('oauth2', {}).get('scopes', {}))
185 scopes.update(scope_ls)
186 client_info = {
187 'package': discovery_doc['name'],
188 'version': NormalizeVersion(discovery_doc['version']),
189 'url_version': discovery_doc['version'],
190 'scopes': sorted(list(scopes)),
191 'client_id': client_id,
192 'client_secret': client_secret,
193 'user_agent': user_agent,
194 'api_key': api_key,
195 }
196 client_class_name = '%s%s' % (
197 names.ClassName(client_info['package']),
198 names.ClassName(client_info['version']))
199 client_info['client_class_name'] = client_class_name
200 return cls(**client_info)
201
202 @property
203 def default_directory(self):
204 return self.package
205
206 @property
207 def cli_rule_name(self):
208 return '%s_%s' % (self.package, self.version)
209
210 @property
211 def cli_file_name(self):
212 return '%s.py' % self.cli_rule_name
213
214 @property
215 def client_rule_name(self):
216 return '%s_%s_client' % (self.package, self.version)
217
218 @property
219 def client_file_name(self):
220 return '%s.py' % self.client_rule_name
221
222 @property
223 def messages_rule_name(self):
224 return '%s_%s_messages' % (self.package, self.version)
225
226 @property
227 def services_rule_name(self):
228 return '%s_%s_services' % (self.package, self.version)
229
230 @property
231 def messages_file_name(self):
232 return '%s.py' % self.messages_rule_name
233
234 @property
235 def messages_proto_file_name(self):
236 return '%s.proto' % self.messages_rule_name
237
238 @property
239 def services_proto_file_name(self):
240 return '%s.proto' % self.services_rule_name
241
242
243 def CleanDescription(description):
244 """Return a version of description safe for printing in a docstring."""
245 if not isinstance(description, six.string_types):
246 return description
247 return description.replace('"""', '" " "')
248
249
250 class SimplePrettyPrinter(object):
251
252 """Simple pretty-printer that supports an indent contextmanager."""
253
254 def __init__(self, out):
255 self.__out = out
256 self.__indent = ''
257 self.__skip = False
258 self.__comment_context = False
259
260 @property
261 def indent(self):
262 return self.__indent
263
264 def CalculateWidth(self, max_width=78):
265 return max_width - len(self.indent)
266
267 @contextlib.contextmanager
268 def Indent(self, indent=' '):
269 previous_indent = self.__indent
270 self.__indent = '%s%s' % (previous_indent, indent)
271 yield
272 self.__indent = previous_indent
273
274 @contextlib.contextmanager
275 def CommentContext(self):
276 """Print without any argument formatting."""
277 old_context = self.__comment_context
278 self.__comment_context = True
279 yield
280 self.__comment_context = old_context
281
282 def __call__(self, *args):
283 if self.__comment_context and args[1:]:
284 raise Error('Cannot do string interpolation in comment context')
285 if args and args[0]:
286 if not self.__comment_context:
287 line = (args[0] % args[1:]).rstrip()
288 else:
289 line = args[0].rstrip()
290 line = line.encode('ascii', 'backslashreplace')
291 print('%s%s' % (self.__indent, line), file=self.__out)
292 else:
293 print('', file=self.__out)
294
295
296 def NormalizeDiscoveryUrl(discovery_url):
297 """Expands a few abbreviations into full discovery urls."""
298 if discovery_url.startswith('http'):
299 return discovery_url
300 elif '.' not in discovery_url:
301 raise ValueError('Unrecognized value "%s" for discovery url')
302 api_name, _, api_version = discovery_url.partition('.')
303 return 'https://www.googleapis.com/discovery/v1/apis/%s/%s/rest' % (
304 api_name, api_version)
305
306
307 def FetchDiscoveryDoc(discovery_url, retries=5):
308 """Fetch the discovery document at the given url."""
309 discovery_url = NormalizeDiscoveryUrl(discovery_url)
310 discovery_doc = None
311 last_exception = None
312 for _ in range(retries):
313 try:
314 discovery_doc = json.loads(
315 urllib_request.urlopen(discovery_url).read())
316 break
317 except (urllib_error.HTTPError,
318 urllib_error.URLError) as last_exception:
319 logging.warning(
320 'Attempting to fetch discovery doc again after "%s"',
321 last_exception)
322 if discovery_doc is None:
323 raise CommunicationError(
324 'Could not find discovery doc at url "%s": %s' % (
325 discovery_url, last_exception))
326 return discovery_doc
OLDNEW
« no previous file with comments | « third_party/google-endpoints/apitools/gen/test_utils.py ('k') | third_party/google-endpoints/apitools/gen/util_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698