• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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