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

Side by Side Diff: third_party/google-endpoints/apitools/base/protorpclite/descriptor.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
18 """Services descriptor definitions.
19
20 Contains message definitions and functions for converting
21 service classes into transmittable message format.
22
23 Describing an Enum instance, Enum class, Field class or Message class will
24 generate an appropriate descriptor object that describes that class.
25 This message can itself be used to transmit information to clients wishing
26 to know the description of an enum value, enum, field or message without
27 needing to download the source code. This format is also compatible with
28 other, non-Python languages.
29
30 The descriptors are modeled to be binary compatible with
31 https://github.com/google/protobuf
32
33 NOTE: The names of types and fields are not always the same between these
34 descriptors and the ones defined in descriptor.proto. This was done in order
35 to make source code files that use these descriptors easier to read. For
36 example, it is not necessary to prefix TYPE to all the values in
37 FieldDescriptor.Variant as is done in descriptor.proto
38 FieldDescriptorProto.Type.
39
40 Example:
41
42 class Pixel(messages.Message):
43
44 x = messages.IntegerField(1, required=True)
45 y = messages.IntegerField(2, required=True)
46
47 color = messages.BytesField(3)
48
49 # Describe Pixel class using message descriptor.
50 fields = []
51
52 field = FieldDescriptor()
53 field.name = 'x'
54 field.number = 1
55 field.label = FieldDescriptor.Label.REQUIRED
56 field.variant = FieldDescriptor.Variant.INT64
57 fields.append(field)
58
59 field = FieldDescriptor()
60 field.name = 'y'
61 field.number = 2
62 field.label = FieldDescriptor.Label.REQUIRED
63 field.variant = FieldDescriptor.Variant.INT64
64 fields.append(field)
65
66 field = FieldDescriptor()
67 field.name = 'color'
68 field.number = 3
69 field.label = FieldDescriptor.Label.OPTIONAL
70 field.variant = FieldDescriptor.Variant.BYTES
71 fields.append(field)
72
73 message = MessageDescriptor()
74 message.name = 'Pixel'
75 message.fields = fields
76
77 # Describing is the equivalent of building the above message.
78 message == describe_message(Pixel)
79
80 Public Classes:
81 EnumValueDescriptor: Describes Enum values.
82 EnumDescriptor: Describes Enum classes.
83 FieldDescriptor: Describes field instances.
84 FileDescriptor: Describes a single 'file' unit.
85 FileSet: Describes a collection of file descriptors.
86 MessageDescriptor: Describes Message classes.
87
88 Public Functions:
89 describe_enum_value: Describe an individual enum-value.
90 describe_enum: Describe an Enum class.
91 describe_field: Describe a Field definition.
92 describe_file: Describe a 'file' unit from a Python module or object.
93 describe_file_set: Describe a file set from a list of modules or objects.
94 describe_message: Describe a Message definition.
95 """
96 import codecs
97 import types
98
99 import six
100
101 from apitools.base.protorpclite import messages
102 from apitools.base.protorpclite import util
103
104
105 __all__ = [
106 'EnumDescriptor',
107 'EnumValueDescriptor',
108 'FieldDescriptor',
109 'MessageDescriptor',
110 'FileDescriptor',
111 'FileSet',
112 'DescriptorLibrary',
113
114 'describe_enum',
115 'describe_enum_value',
116 'describe_field',
117 'describe_message',
118 'describe_file',
119 'describe_file_set',
120 'describe',
121 'import_descriptor_loader',
122 ]
123
124
125 # NOTE: MessageField is missing because message fields cannot have
126 # a default value at this time.
127 # TODO(rafek): Support default message values.
128 #
129 # Map to functions that convert default values of fields of a given type
130 # to a string. The function must return a value that is compatible with
131 # FieldDescriptor.default_value and therefore a unicode string.
132 _DEFAULT_TO_STRING_MAP = {
133 messages.IntegerField: six.text_type,
134 messages.FloatField: six.text_type,
135 messages.BooleanField: lambda value: value and u'true' or u'false',
136 messages.BytesField: lambda value: codecs.escape_encode(value)[0],
137 messages.StringField: lambda value: value,
138 messages.EnumField: lambda value: six.text_type(value.number),
139 }
140
141 _DEFAULT_FROM_STRING_MAP = {
142 messages.IntegerField: int,
143 messages.FloatField: float,
144 messages.BooleanField: lambda value: value == u'true',
145 messages.BytesField: lambda value: codecs.escape_decode(value)[0],
146 messages.StringField: lambda value: value,
147 messages.EnumField: int,
148 }
149
150
151 class EnumValueDescriptor(messages.Message):
152 """Enum value descriptor.
153
154 Fields:
155 name: Name of enumeration value.
156 number: Number of enumeration value.
157 """
158
159 # TODO(rafek): Why are these listed as optional in descriptor.proto.
160 # Harmonize?
161 name = messages.StringField(1, required=True)
162 number = messages.IntegerField(2,
163 required=True,
164 variant=messages.Variant.INT32)
165
166
167 class EnumDescriptor(messages.Message):
168 """Enum class descriptor.
169
170 Fields:
171 name: Name of Enum without any qualification.
172 values: Values defined by Enum class.
173 """
174
175 name = messages.StringField(1)
176 values = messages.MessageField(EnumValueDescriptor, 2, repeated=True)
177
178
179 class FieldDescriptor(messages.Message):
180 """Field definition descriptor.
181
182 Enums:
183 Variant: Wire format hint sub-types for field.
184 Label: Values for optional, required and repeated fields.
185
186 Fields:
187 name: Name of field.
188 number: Number of field.
189 variant: Variant of field.
190 type_name: Type name for message and enum fields.
191 default_value: String representation of default value.
192 """
193
194 Variant = messages.Variant # pylint:disable=invalid-name
195
196 class Label(messages.Enum):
197 """Field label."""
198
199 OPTIONAL = 1
200 REQUIRED = 2
201 REPEATED = 3
202
203 name = messages.StringField(1, required=True)
204 number = messages.IntegerField(3,
205 required=True,
206 variant=messages.Variant.INT32)
207 label = messages.EnumField(Label, 4, default=Label.OPTIONAL)
208 variant = messages.EnumField(Variant, 5)
209 type_name = messages.StringField(6)
210
211 # For numeric types, contains the original text representation of
212 # the value.
213 # For booleans, "true" or "false".
214 # For strings, contains the default text contents (not escaped in any
215 # way).
216 # For bytes, contains the C escaped value. All bytes < 128 are that are
217 # traditionally considered unprintable are also escaped.
218 default_value = messages.StringField(7)
219
220
221 class MessageDescriptor(messages.Message):
222 """Message definition descriptor.
223
224 Fields:
225 name: Name of Message without any qualification.
226 fields: Fields defined for message.
227 message_types: Nested Message classes defined on message.
228 enum_types: Nested Enum classes defined on message.
229 """
230
231 name = messages.StringField(1)
232 fields = messages.MessageField(FieldDescriptor, 2, repeated=True)
233
234 message_types = messages.MessageField(
235 'apitools.base.protorpclite.descriptor.MessageDescriptor', 3,
236 repeated=True)
237 enum_types = messages.MessageField(EnumDescriptor, 4, repeated=True)
238
239
240 class FileDescriptor(messages.Message):
241 """Description of file containing protobuf definitions.
242
243 Fields:
244 package: Fully qualified name of package that definitions belong to.
245 message_types: Message definitions contained in file.
246 enum_types: Enum definitions contained in file.
247 """
248
249 package = messages.StringField(2)
250
251 # TODO(rafek): Add dependency field
252
253 message_types = messages.MessageField(MessageDescriptor, 4, repeated=True)
254 enum_types = messages.MessageField(EnumDescriptor, 5, repeated=True)
255
256
257 class FileSet(messages.Message):
258 """A collection of FileDescriptors.
259
260 Fields:
261 files: Files in file-set.
262 """
263
264 files = messages.MessageField(FileDescriptor, 1, repeated=True)
265
266
267 def describe_enum_value(enum_value):
268 """Build descriptor for Enum instance.
269
270 Args:
271 enum_value: Enum value to provide descriptor for.
272
273 Returns:
274 Initialized EnumValueDescriptor instance describing the Enum instance.
275 """
276 enum_value_descriptor = EnumValueDescriptor()
277 enum_value_descriptor.name = six.text_type(enum_value.name)
278 enum_value_descriptor.number = enum_value.number
279 return enum_value_descriptor
280
281
282 def describe_enum(enum_definition):
283 """Build descriptor for Enum class.
284
285 Args:
286 enum_definition: Enum class to provide descriptor for.
287
288 Returns:
289 Initialized EnumDescriptor instance describing the Enum class.
290 """
291 enum_descriptor = EnumDescriptor()
292 enum_descriptor.name = enum_definition.definition_name().split('.')[-1]
293
294 values = []
295 for number in enum_definition.numbers():
296 value = enum_definition.lookup_by_number(number)
297 values.append(describe_enum_value(value))
298
299 if values:
300 enum_descriptor.values = values
301
302 return enum_descriptor
303
304
305 def describe_field(field_definition):
306 """Build descriptor for Field instance.
307
308 Args:
309 field_definition: Field instance to provide descriptor for.
310
311 Returns:
312 Initialized FieldDescriptor instance describing the Field instance.
313 """
314 field_descriptor = FieldDescriptor()
315 field_descriptor.name = field_definition.name
316 field_descriptor.number = field_definition.number
317 field_descriptor.variant = field_definition.variant
318
319 if isinstance(field_definition, messages.EnumField):
320 field_descriptor.type_name = field_definition.type.definition_name()
321
322 if isinstance(field_definition, messages.MessageField):
323 field_descriptor.type_name = (
324 field_definition.message_type.definition_name())
325
326 if field_definition.default is not None:
327 field_descriptor.default_value = _DEFAULT_TO_STRING_MAP[
328 type(field_definition)](field_definition.default)
329
330 # Set label.
331 if field_definition.repeated:
332 field_descriptor.label = FieldDescriptor.Label.REPEATED
333 elif field_definition.required:
334 field_descriptor.label = FieldDescriptor.Label.REQUIRED
335 else:
336 field_descriptor.label = FieldDescriptor.Label.OPTIONAL
337
338 return field_descriptor
339
340
341 def describe_message(message_definition):
342 """Build descriptor for Message class.
343
344 Args:
345 message_definition: Message class to provide descriptor for.
346
347 Returns:
348 Initialized MessageDescriptor instance describing the Message class.
349 """
350 message_descriptor = MessageDescriptor()
351 message_descriptor.name = message_definition.definition_name().split(
352 '.')[-1]
353
354 fields = sorted(message_definition.all_fields(),
355 key=lambda v: v.number)
356 if fields:
357 message_descriptor.fields = [describe_field(field) for field in fields]
358
359 try:
360 nested_messages = message_definition.__messages__
361 except AttributeError:
362 pass
363 else:
364 message_descriptors = []
365 for name in nested_messages:
366 value = getattr(message_definition, name)
367 message_descriptors.append(describe_message(value))
368
369 message_descriptor.message_types = message_descriptors
370
371 try:
372 nested_enums = message_definition.__enums__
373 except AttributeError:
374 pass
375 else:
376 enum_descriptors = []
377 for name in nested_enums:
378 value = getattr(message_definition, name)
379 enum_descriptors.append(describe_enum(value))
380
381 message_descriptor.enum_types = enum_descriptors
382
383 return message_descriptor
384
385
386 def describe_file(module):
387 """Build a file from a specified Python module.
388
389 Args:
390 module: Python module to describe.
391
392 Returns:
393 Initialized FileDescriptor instance describing the module.
394 """
395 descriptor = FileDescriptor()
396 descriptor.package = util.get_package_for_module(module)
397
398 if not descriptor.package:
399 descriptor.package = None
400
401 message_descriptors = []
402 enum_descriptors = []
403
404 # Need to iterate over all top level attributes of the module looking for
405 # message and enum definitions. Each definition must be itself described.
406 for name in sorted(dir(module)):
407 value = getattr(module, name)
408
409 if isinstance(value, type):
410 if issubclass(value, messages.Message):
411 message_descriptors.append(describe_message(value))
412
413 elif issubclass(value, messages.Enum):
414 enum_descriptors.append(describe_enum(value))
415
416 if message_descriptors:
417 descriptor.message_types = message_descriptors
418
419 if enum_descriptors:
420 descriptor.enum_types = enum_descriptors
421
422 return descriptor
423
424
425 def describe_file_set(modules):
426 """Build a file set from a specified Python modules.
427
428 Args:
429 modules: Iterable of Python module to describe.
430
431 Returns:
432 Initialized FileSet instance describing the modules.
433 """
434 descriptor = FileSet()
435 file_descriptors = []
436 for module in modules:
437 file_descriptors.append(describe_file(module))
438
439 if file_descriptors:
440 descriptor.files = file_descriptors
441
442 return descriptor
443
444
445 def describe(value):
446 """Describe any value as a descriptor.
447
448 Helper function for describing any object with an appropriate descriptor
449 object.
450
451 Args:
452 value: Value to describe as a descriptor.
453
454 Returns:
455 Descriptor message class if object is describable as a descriptor, else
456 None.
457 """
458 if isinstance(value, types.ModuleType):
459 return describe_file(value)
460 elif isinstance(value, messages.Field):
461 return describe_field(value)
462 elif isinstance(value, messages.Enum):
463 return describe_enum_value(value)
464 elif isinstance(value, type):
465 if issubclass(value, messages.Message):
466 return describe_message(value)
467 elif issubclass(value, messages.Enum):
468 return describe_enum(value)
469 return None
470
471
472 @util.positional(1)
473 def import_descriptor_loader(definition_name, importer=__import__):
474 """Find objects by importing modules as needed.
475
476 A definition loader is a function that resolves a definition name to a
477 descriptor.
478
479 The import finder resolves definitions to their names by importing modules
480 when necessary.
481
482 Args:
483 definition_name: Name of definition to find.
484 importer: Import function used for importing new modules.
485
486 Returns:
487 Appropriate descriptor for any describable type located by name.
488
489 Raises:
490 DefinitionNotFoundError when a name does not refer to either a definition
491 or a module.
492 """
493 # Attempt to import descriptor as a module.
494 if definition_name.startswith('.'):
495 definition_name = definition_name[1:]
496 if not definition_name.startswith('.'):
497 leaf = definition_name.split('.')[-1]
498 if definition_name:
499 try:
500 module = importer(definition_name, '', '', [leaf])
501 except ImportError:
502 pass
503 else:
504 return describe(module)
505
506 try:
507 # Attempt to use messages.find_definition to find item.
508 return describe(messages.find_definition(definition_name,
509 importer=__import__))
510 except messages.DefinitionNotFoundError as err:
511 # There are things that find_definition will not find, but if
512 # the parent is loaded, its children can be searched for a
513 # match.
514 split_name = definition_name.rsplit('.', 1)
515 if len(split_name) > 1:
516 parent, child = split_name
517 try:
518 parent_definition = import_descriptor_loader(
519 parent, importer=importer)
520 except messages.DefinitionNotFoundError:
521 # Fall through to original error.
522 pass
523 else:
524 # Check the parent definition for a matching descriptor.
525 if isinstance(parent_definition, EnumDescriptor):
526 search_list = parent_definition.values or []
527 elif isinstance(parent_definition, MessageDescriptor):
528 search_list = parent_definition.fields or []
529 else:
530 search_list = []
531
532 for definition in search_list:
533 if definition.name == child:
534 return definition
535
536 # Still didn't find. Reraise original exception.
537 raise err
538
539
540 class DescriptorLibrary(object):
541 """A descriptor library is an object that contains known definitions.
542
543 A descriptor library contains a cache of descriptor objects mapped by
544 definition name. It contains all types of descriptors except for
545 file sets.
546
547 When a definition name is requested that the library does not know about
548 it can be provided with a descriptor loader which attempt to resolve the
549 missing descriptor.
550 """
551
552 @util.positional(1)
553 def __init__(self,
554 descriptors=None,
555 descriptor_loader=import_descriptor_loader):
556 """Constructor.
557
558 Args:
559 descriptors: A dictionary or dictionary-like object that can be used
560 to store and cache descriptors by definition name.
561 definition_loader: A function used for resolving missing descriptors.
562 The function takes a definition name as its parameter and returns
563 an appropriate descriptor. It may raise DefinitionNotFoundError.
564 """
565 self.__descriptor_loader = descriptor_loader
566 self.__descriptors = descriptors or {}
567
568 def lookup_descriptor(self, definition_name):
569 """Lookup descriptor by name.
570
571 Get descriptor from library by name. If descriptor is not found will
572 attempt to find via descriptor loader if provided.
573
574 Args:
575 definition_name: Definition name to find.
576
577 Returns:
578 Descriptor that describes definition name.
579
580 Raises:
581 DefinitionNotFoundError if not descriptor exists for definition name.
582 """
583 try:
584 return self.__descriptors[definition_name]
585 except KeyError:
586 pass
587
588 if self.__descriptor_loader:
589 definition = self.__descriptor_loader(definition_name)
590 self.__descriptors[definition_name] = definition
591 return definition
592 else:
593 raise messages.DefinitionNotFoundError(
594 'Could not find definition for %s' % definition_name)
595
596 def lookup_package(self, definition_name):
597 """Determines the package name for any definition.
598
599 Determine the package that any definition name belongs to. May
600 check parent for package name and will resolve missing
601 descriptors if provided descriptor loader.
602
603 Args:
604 definition_name: Definition name to find package for.
605
606 """
607 while True:
608 descriptor = self.lookup_descriptor(definition_name)
609 if isinstance(descriptor, FileDescriptor):
610 return descriptor.package
611 else:
612 index = definition_name.rfind('.')
613 if index < 0:
614 return None
615 definition_name = definition_name[:index]
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698