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 20Contains message definitions and functions for converting 21service classes into transmittable message format. 22 23Describing an Enum instance, Enum class, Field class or Message class will 24generate an appropriate descriptor object that describes that class. 25This message can itself be used to transmit information to clients wishing 26to know the description of an enum value, enum, field or message without 27needing to download the source code. This format is also compatible with 28other, non-Python languages. 29 30The descriptors are modeled to be binary compatible with 31 https://github.com/google/protobuf 32 33NOTE: The names of types and fields are not always the same between these 34descriptors and the ones defined in descriptor.proto. This was done in order 35to make source code files that use these descriptors easier to read. For 36example, it is not necessary to prefix TYPE to all the values in 37FieldDescriptor.Variant as is done in descriptor.proto 38FieldDescriptorProto.Type. 39 40Example: 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 80Public 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 88Public 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""" 96import codecs 97import types 98 99import six 100 101from apitools.base.protorpclite import messages 102from 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 151class 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 167class 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 179class 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 221class 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 240class 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 257class 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 267def 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 282def 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 305def 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 341def 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 386def 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 425def 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 445def 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) 473def 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 540class 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] 616