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 """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] |
OLD | NEW |