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

Unified Diff: third_party/gsutil/third_party/protorpc/protorpc/messages.py

Issue 1377933002: [catapult] - Copy Telemetry's gsutilz over to third_party. (Closed) Base URL: https://github.com/catapult-project/catapult.git@master
Patch Set: Rename to gsutil. Created 5 years, 3 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 side-by-side diff with in-line comments
Download patch
Index: third_party/gsutil/third_party/protorpc/protorpc/messages.py
diff --git a/third_party/gsutil/third_party/protorpc/protorpc/messages.py b/third_party/gsutil/third_party/protorpc/protorpc/messages.py
new file mode 100755
index 0000000000000000000000000000000000000000..ca918aadd1654824fa2b6ddd76fa76439531989d
--- /dev/null
+++ b/third_party/gsutil/third_party/protorpc/protorpc/messages.py
@@ -0,0 +1,1929 @@
+#!/usr/bin/env python
+#
+# Copyright 2010 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""Stand-alone implementation of in memory protocol messages.
+
+Public Classes:
+ Enum: Represents an enumerated type.
+ Variant: Hint for wire format to determine how to serialize.
+ Message: Base class for user defined messages.
+ IntegerField: Field for integer values.
+ FloatField: Field for float values.
+ BooleanField: Field for boolean values.
+ BytesField: Field for binary string values.
+ StringField: Field for UTF-8 string values.
+ MessageField: Field for other message type values.
+ EnumField: Field for enumerated type values.
+
+Public Exceptions (indentation indications class hierarchy):
+ EnumDefinitionError: Raised when enumeration is incorrectly defined.
+ FieldDefinitionError: Raised when field is incorrectly defined.
+ InvalidVariantError: Raised when variant is not compatible with field type.
+ InvalidDefaultError: Raised when default is not compatiable with field.
+ InvalidNumberError: Raised when field number is out of range or reserved.
+ MessageDefinitionError: Raised when message is incorrectly defined.
+ DuplicateNumberError: Raised when field has duplicate number with another.
+ ValidationError: Raised when a message or field is not valid.
+ DefinitionNotFoundError: Raised when definition not found.
+"""
+import six
+
+__author__ = 'rafek@google.com (Rafe Kaplan)'
+
+
+import types
+import weakref
+
+from . import util
+
+__all__ = ['MAX_ENUM_VALUE',
+ 'MAX_FIELD_NUMBER',
+ 'FIRST_RESERVED_FIELD_NUMBER',
+ 'LAST_RESERVED_FIELD_NUMBER',
+
+ 'Enum',
+ 'Field',
+ 'FieldList',
+ 'Variant',
+ 'Message',
+ 'IntegerField',
+ 'FloatField',
+ 'BooleanField',
+ 'BytesField',
+ 'StringField',
+ 'MessageField',
+ 'EnumField',
+ 'find_definition',
+
+ 'Error',
+ 'DecodeError',
+ 'EncodeError',
+ 'EnumDefinitionError',
+ 'FieldDefinitionError',
+ 'InvalidVariantError',
+ 'InvalidDefaultError',
+ 'InvalidNumberError',
+ 'MessageDefinitionError',
+ 'DuplicateNumberError',
+ 'ValidationError',
+ 'DefinitionNotFoundError',
+ ]
+
+
+# TODO(rafek): Add extended module test to ensure all exceptions
+# in services extends Error.
+Error = util.Error
+
+
+class EnumDefinitionError(Error):
+ """Enumeration definition error."""
+
+
+class FieldDefinitionError(Error):
+ """Field definition error."""
+
+
+class InvalidVariantError(FieldDefinitionError):
+ """Invalid variant provided to field."""
+
+
+class InvalidDefaultError(FieldDefinitionError):
+ """Invalid default provided to field."""
+
+
+class InvalidNumberError(FieldDefinitionError):
+ """Invalid number provided to field."""
+
+
+class MessageDefinitionError(Error):
+ """Message definition error."""
+
+
+class DuplicateNumberError(Error):
+ """Duplicate number assigned to field."""
+
+
+class DefinitionNotFoundError(Error):
+ """Raised when definition is not found."""
+
+
+class DecodeError(Error):
+ """Error found decoding message from encoded form."""
+
+
+class EncodeError(Error):
+ """Error found when encoding message."""
+
+
+class ValidationError(Error):
+ """Invalid value for message error."""
+
+ def __str__(self):
+ """Prints string with field name if present on exception."""
+ message = Error.__str__(self)
+ try:
+ field_name = self.field_name
+ except AttributeError:
+ return message
+ else:
+ return message
+
+
+# Attributes that are reserved by a class definition that
+# may not be used by either Enum or Message class definitions.
+_RESERVED_ATTRIBUTE_NAMES = frozenset(
+ ['__module__', '__doc__', '__qualname__'])
+
+_POST_INIT_FIELD_ATTRIBUTE_NAMES = frozenset(
+ ['name',
+ '_message_definition',
+ '_MessageField__type',
+ '_EnumField__type',
+ '_EnumField__resolved_default'])
+
+_POST_INIT_ATTRIBUTE_NAMES = frozenset(
+ ['_message_definition'])
+
+# Maximum enumeration value as defined by the protocol buffers standard.
+# All enum values must be less than or equal to this value.
+MAX_ENUM_VALUE = (2 ** 29) - 1
+
+# Maximum field number as defined by the protocol buffers standard.
+# All field numbers must be less than or equal to this value.
+MAX_FIELD_NUMBER = (2 ** 29) - 1
+
+# Field numbers between 19000 and 19999 inclusive are reserved by the
+# protobuf protocol and may not be used by fields.
+FIRST_RESERVED_FIELD_NUMBER = 19000
+LAST_RESERVED_FIELD_NUMBER = 19999
+
+
+class _DefinitionClass(type):
+ """Base meta-class used for definition meta-classes.
+
+ The Enum and Message definition classes share some basic functionality.
+ Both of these classes may be contained by a Message definition. After
+ initialization, neither class may have attributes changed
+ except for the protected _message_definition attribute, and that attribute
+ may change only once.
+ """
+
+ __initialized = False
+
+ def __init__(cls, name, bases, dct):
+ """Constructor."""
+ type.__init__(cls, name, bases, dct)
+ # Base classes may never be initialized.
+ if cls.__bases__ != (object,):
+ cls.__initialized = True
+
+ def message_definition(cls):
+ """Get outer Message definition that contains this definition.
+
+ Returns:
+ Containing Message definition if definition is contained within one,
+ else None.
+ """
+ try:
+ return cls._message_definition()
+ except AttributeError:
+ return None
+
+ def __setattr__(cls, name, value):
+ """Overridden so that cannot set variables on definition classes after init.
+
+ Setting attributes on a class must work during the period of initialization
+ to set the enumation value class variables and build the name/number maps.
+ Once __init__ has set the __initialized flag to True prohibits setting any
+ more values on the class. The class is in effect frozen.
+
+ Args:
+ name: Name of value to set.
+ value: Value to set.
+ """
+ if cls.__initialized and name not in _POST_INIT_ATTRIBUTE_NAMES:
+ raise AttributeError('May not change values: %s' % name)
+ else:
+ type.__setattr__(cls, name, value)
+
+ def __delattr__(cls, name):
+ """Overridden so that cannot delete varaibles on definition classes."""
+ raise TypeError('May not delete attributes on definition class')
+
+ def definition_name(cls):
+ """Helper method for creating definition name.
+
+ Names will be generated to include the classes package name, scope (if the
+ class is nested in another definition) and class name.
+
+ By default, the package name for a definition is derived from its module
+ name. However, this value can be overriden by placing a 'package' attribute
+ in the module that contains the definition class. For example:
+
+ package = 'some.alternate.package'
+
+ class MyMessage(Message):
+ ...
+
+ >>> MyMessage.definition_name()
+ some.alternate.package.MyMessage
+
+ Returns:
+ Dot-separated fully qualified name of definition.
+ """
+ outer_definition_name = cls.outer_definition_name()
+ if outer_definition_name is None:
+ return six.text_type(cls.__name__)
+ else:
+ return u'%s.%s' % (outer_definition_name, cls.__name__)
+
+ def outer_definition_name(cls):
+ """Helper method for creating outer definition name.
+
+ Returns:
+ If definition is nested, will return the outer definitions name, else the
+ package name.
+ """
+ outer_definition = cls.message_definition()
+ if not outer_definition:
+ return util.get_package_for_module(cls.__module__)
+ else:
+ return outer_definition.definition_name()
+
+ def definition_package(cls):
+ """Helper method for creating creating the package of a definition.
+
+ Returns:
+ Name of package that definition belongs to.
+ """
+ outer_definition = cls.message_definition()
+ if not outer_definition:
+ return util.get_package_for_module(cls.__module__)
+ else:
+ return outer_definition.definition_package()
+
+
+class _EnumClass(_DefinitionClass):
+ """Meta-class used for defining the Enum base class.
+
+ Meta-class enables very specific behavior for any defined Enum
+ class. All attributes defined on an Enum sub-class must be integers.
+ Each attribute defined on an Enum sub-class is translated
+ into an instance of that sub-class, with the name of the attribute
+ as its name, and the number provided as its value. It also ensures
+ that only one level of Enum class hierarchy is possible. In other
+ words it is not possible to delcare sub-classes of sub-classes of
+ Enum.
+
+ This class also defines some functions in order to restrict the
+ behavior of the Enum class and its sub-classes. It is not possible
+ to change the behavior of the Enum class in later classes since
+ any new classes may be defined with only integer values, and no methods.
+ """
+
+ def __init__(cls, name, bases, dct):
+ # Can only define one level of sub-classes below Enum.
+ if not (bases == (object,) or bases == (Enum,)):
+ raise EnumDefinitionError('Enum type %s may only inherit from Enum' %
+ (name,))
+
+ cls.__by_number = {}
+ cls.__by_name = {}
+
+ # Enum base class does not need to be initialized or locked.
+ if bases != (object,):
+ # Replace integer with number.
+ for attribute, value in dct.items():
+
+ # Module will be in every enum class.
+ if attribute in _RESERVED_ATTRIBUTE_NAMES:
+ continue
+
+ # Reject anything that is not an int.
+ if not isinstance(value, six.integer_types):
+ raise EnumDefinitionError(
+ 'May only use integers in Enum definitions. Found: %s = %s' %
+ (attribute, value))
+
+ # Protocol buffer standard recommends non-negative values.
+ # Reject negative values.
+ if value < 0:
+ raise EnumDefinitionError(
+ 'Must use non-negative enum values. Found: %s = %d' %
+ (attribute, value))
+
+ if value > MAX_ENUM_VALUE:
+ raise EnumDefinitionError(
+ 'Must use enum values less than or equal %d. Found: %s = %d' %
+ (MAX_ENUM_VALUE, attribute, value))
+
+ if value in cls.__by_number:
+ raise EnumDefinitionError(
+ 'Value for %s = %d is already defined: %s' %
+ (attribute, value, cls.__by_number[value].name))
+
+ # Create enum instance and list in new Enum type.
+ instance = object.__new__(cls)
+ cls.__init__(instance, attribute, value)
+ cls.__by_name[instance.name] = instance
+ cls.__by_number[instance.number] = instance
+ setattr(cls, attribute, instance)
+
+ _DefinitionClass.__init__(cls, name, bases, dct)
+
+ def __iter__(cls):
+ """Iterate over all values of enum.
+
+ Yields:
+ Enumeration instances of the Enum class in arbitrary order.
+ """
+ return iter(cls.__by_number.values())
+
+ def names(cls):
+ """Get all names for Enum.
+
+ Returns:
+ An iterator for names of the enumeration in arbitrary order.
+ """
+ return cls.__by_name.keys()
+
+ def numbers(cls):
+ """Get all numbers for Enum.
+
+ Returns:
+ An iterator for all numbers of the enumeration in arbitrary order.
+ """
+ return cls.__by_number.keys()
+
+ def lookup_by_name(cls, name):
+ """Look up Enum by name.
+
+ Args:
+ name: Name of enum to find.
+
+ Returns:
+ Enum sub-class instance of that value.
+ """
+ return cls.__by_name[name]
+
+ def lookup_by_number(cls, number):
+ """Look up Enum by number.
+
+ Args:
+ number: Number of enum to find.
+
+ Returns:
+ Enum sub-class instance of that value.
+ """
+ return cls.__by_number[number]
+
+ def __len__(cls):
+ return len(cls.__by_name)
+
+
+class Enum(six.with_metaclass(_EnumClass, object)):
+ """Base class for all enumerated types."""
+
+ __slots__ = set(('name', 'number'))
+
+ def __new__(cls, index):
+ """Acts as look-up routine after class is initialized.
+
+ The purpose of overriding __new__ is to provide a way to treat
+ Enum subclasses as casting types, similar to how the int type
+ functions. A program can pass a string or an integer and this
+ method with "convert" that value in to an appropriate Enum instance.
+
+ Args:
+ index: Name or number to look up. During initialization
+ this is always the name of the new enum value.
+
+ Raises:
+ TypeError: When an inappropriate index value is passed provided.
+ """
+ # If is enum type of this class, return it.
+ if isinstance(index, cls):
+ return index
+
+ # If number, look up by number.
+ if isinstance(index, six.integer_types):
+ try:
+ return cls.lookup_by_number(index)
+ except KeyError:
+ pass
+
+ # If name, look up by name.
+ if isinstance(index, six.string_types):
+ try:
+ return cls.lookup_by_name(index)
+ except KeyError:
+ pass
+
+ raise TypeError('No such value for %s in Enum %s' %
+ (index, cls.__name__))
+
+ def __init__(self, name, number=None):
+ """Initialize new Enum instance.
+
+ Since this should only be called during class initialization any
+ calls that happen after the class is frozen raises an exception.
+ """
+ # Immediately return if __init__ was called after _Enum.__init__().
+ # It means that casting operator version of the class constructor
+ # is being used.
+ if getattr(type(self), '_DefinitionClass__initialized'):
+ return
+ object.__setattr__(self, 'name', name)
+ object.__setattr__(self, 'number', number)
+
+ def __setattr__(self, name, value):
+ raise TypeError('May not change enum values')
+
+ def __str__(self):
+ return self.name
+
+ def __int__(self):
+ return self.number
+
+ def __repr__(self):
+ return '%s(%s, %d)' % (type(self).__name__, self.name, self.number)
+
+ def __reduce__(self):
+ """Enable pickling.
+
+ Returns:
+ A 2-tuple containing the class and __new__ args to be used for restoring
+ a pickled instance.
+ """
+ return self.__class__, (self.number,)
+
+ def __cmp__(self, other):
+ """Order is by number."""
+ if isinstance(other, type(self)):
+ return cmp(self.number, other.number)
+ return NotImplemented
+
+ def __lt__(self, other):
+ """Order is by number."""
+ if isinstance(other, type(self)):
+ return self.number < other.number
+ return NotImplemented
+
+ def __le__(self, other):
+ """Order is by number."""
+ if isinstance(other, type(self)):
+ return self.number <= other.number
+ return NotImplemented
+
+ def __eq__(self, other):
+ """Order is by number."""
+ if isinstance(other, type(self)):
+ return self.number == other.number
+ return NotImplemented
+
+ def __ne__(self, other):
+ """Order is by number."""
+ if isinstance(other, type(self)):
+ return self.number != other.number
+ return NotImplemented
+
+ def __ge__(self, other):
+ """Order is by number."""
+ if isinstance(other, type(self)):
+ return self.number >= other.number
+ return NotImplemented
+
+ def __gt__(self, other):
+ """Order is by number."""
+ if isinstance(other, type(self)):
+ return self.number > other.number
+ return NotImplemented
+
+ def __hash__(self):
+ """Hash by number."""
+ return hash(self.number)
+
+ @classmethod
+ def to_dict(cls):
+ """Make dictionary version of enumerated class.
+
+ Dictionary created this way can be used with def_num.
+
+ Returns:
+ A dict (name) -> number
+ """
+ return dict((item.name, item.number) for item in iter(cls))
+
+ @staticmethod
+ def def_enum(dct, name):
+ """Define enum class from dictionary.
+
+ Args:
+ dct: Dictionary of enumerated values for type.
+ name: Name of enum.
+ """
+ return type(name, (Enum,), dct)
+
+
+# TODO(rafek): Determine to what degree this enumeration should be compatible
+# with FieldDescriptor.Type in:
+#
+# http://code.google.com/p/protobuf/source/browse/trunk/src/google/protobuf/descriptor.proto
+class Variant(Enum):
+ """Wire format variant.
+
+ Used by the 'protobuf' wire format to determine how to transmit
+ a single piece of data. May be used by other formats.
+
+ See: http://code.google.com/apis/protocolbuffers/docs/encoding.html
+
+ Values:
+ DOUBLE: 64-bit floating point number.
+ FLOAT: 32-bit floating point number.
+ INT64: 64-bit signed integer.
+ UINT64: 64-bit unsigned integer.
+ INT32: 32-bit signed integer.
+ BOOL: Boolean value (True or False).
+ STRING: String of UTF-8 encoded text.
+ MESSAGE: Embedded message as byte string.
+ BYTES: String of 8-bit bytes.
+ UINT32: 32-bit unsigned integer.
+ ENUM: Enum value as integer.
+ SINT32: 32-bit signed integer. Uses "zig-zag" encoding.
+ SINT64: 64-bit signed integer. Uses "zig-zag" encoding.
+ """
+ DOUBLE = 1
+ FLOAT = 2
+ INT64 = 3
+ UINT64 = 4
+ INT32 = 5
+ BOOL = 8
+ STRING = 9
+ MESSAGE = 11
+ BYTES = 12
+ UINT32 = 13
+ ENUM = 14
+ SINT32 = 17
+ SINT64 = 18
+
+
+class _MessageClass(_DefinitionClass):
+ """Meta-class used for defining the Message base class.
+
+ For more details about Message classes, see the Message class docstring.
+ Information contained there may help understanding this class.
+
+ Meta-class enables very specific behavior for any defined Message
+ class. All attributes defined on an Message sub-class must be field
+ instances, Enum class definitions or other Message class definitions. Each
+ field attribute defined on an Message sub-class is added to the set of
+ field definitions and the attribute is translated in to a slot. It also
+ ensures that only one level of Message class hierarchy is possible. In other
+ words it is not possible to declare sub-classes of sub-classes of
+ Message.
+
+ This class also defines some functions in order to restrict the
+ behavior of the Message class and its sub-classes. It is not possible
+ to change the behavior of the Message class in later classes since
+ any new classes may be defined with only field, Enums and Messages, and
+ no methods.
+ """
+
+ def __new__(cls, name, bases, dct):
+ """Create new Message class instance.
+
+ The __new__ method of the _MessageClass type is overridden so as to
+ allow the translation of Field instances to slots.
+ """
+ by_number = {}
+ by_name = {}
+
+ variant_map = {}
+
+ if bases != (object,):
+ # Can only define one level of sub-classes below Message.
+ if bases != (Message,):
+ raise MessageDefinitionError(
+ 'Message types may only inherit from Message')
+
+ enums = []
+ messages = []
+ # Must not use iteritems because this loop will change the state of dct.
+ for key, field in dct.items():
+
+ if key in _RESERVED_ATTRIBUTE_NAMES:
+ continue
+
+ if isinstance(field, type) and issubclass(field, Enum):
+ enums.append(key)
+ continue
+
+ if (isinstance(field, type) and
+ issubclass(field, Message) and
+ field is not Message):
+ messages.append(key)
+ continue
+
+ # Reject anything that is not a field.
+ if type(field) is Field or not isinstance(field, Field):
+ raise MessageDefinitionError(
+ 'May only use fields in message definitions. Found: %s = %s' %
+ (key, field))
+
+ if field.number in by_number:
+ raise DuplicateNumberError(
+ 'Field with number %d declared more than once in %s' %
+ (field.number, name))
+
+ field.name = key
+
+ # Place in name and number maps.
+ by_name[key] = field
+ by_number[field.number] = field
+
+ # Add enums if any exist.
+ if enums:
+ dct['__enums__'] = sorted(enums)
+
+ # Add messages if any exist.
+ if messages:
+ dct['__messages__'] = sorted(messages)
+
+ dct['_Message__by_number'] = by_number
+ dct['_Message__by_name'] = by_name
+
+ return _DefinitionClass.__new__(cls, name, bases, dct)
+
+ def __init__(cls, name, bases, dct):
+ """Initializer required to assign references to new class."""
+ if bases != (object,):
+ for value in dct.values():
+ if isinstance(value, _DefinitionClass) and not value is Message:
+ value._message_definition = weakref.ref(cls)
+
+ for field in cls.all_fields():
+ field._message_definition = weakref.ref(cls)
+
+ _DefinitionClass.__init__(cls, name, bases, dct)
+
+
+class Message(six.with_metaclass(_MessageClass, object)):
+ """Base class for user defined message objects.
+
+ Used to define messages for efficient transmission across network or
+ process space. Messages are defined using the field classes (IntegerField,
+ FloatField, EnumField, etc.).
+
+ Messages are more restricted than normal classes in that they may only
+ contain field attributes and other Message and Enum definitions. These
+ restrictions are in place because the structure of the Message class is
+ intentended to itself be transmitted across network or process space and
+ used directly by clients or even other servers. As such methods and
+ non-field attributes could not be transmitted with the structural information
+ causing discrepancies between different languages and implementations.
+
+ Initialization and validation:
+
+ A Message object is considered to be initialized if it has all required
+ fields and any nested messages are also initialized.
+
+ Calling 'check_initialized' will raise a ValidationException if it is not
+ initialized; 'is_initialized' returns a boolean value indicating if it is
+ valid.
+
+ Validation automatically occurs when Message objects are created
+ and populated. Validation that a given value will be compatible with
+ a field that it is assigned to can be done through the Field instances
+ validate() method. The validate method used on a message will check that
+ all values of a message and its sub-messages are valid. Assingning an
+ invalid value to a field will raise a ValidationException.
+
+ Example:
+
+ # Trade type.
+ class TradeType(Enum):
+ BUY = 1
+ SELL = 2
+ SHORT = 3
+ CALL = 4
+
+ class Lot(Message):
+ price = IntegerField(1, required=True)
+ quantity = IntegerField(2, required=True)
+
+ class Order(Message):
+ symbol = StringField(1, required=True)
+ total_quantity = IntegerField(2, required=True)
+ trade_type = EnumField(TradeType, 3, required=True)
+ lots = MessageField(Lot, 4, repeated=True)
+ limit = IntegerField(5)
+
+ order = Order(symbol='GOOG',
+ total_quantity=10,
+ trade_type=TradeType.BUY)
+
+ lot1 = Lot(price=304,
+ quantity=7)
+
+ lot2 = Lot(price = 305,
+ quantity=3)
+
+ order.lots = [lot1, lot2]
+
+ # Now object is initialized!
+ order.check_initialized()
+ """
+
+ def __init__(self, **kwargs):
+ """Initialize internal messages state.
+
+ Args:
+ A message can be initialized via the constructor by passing in keyword
+ arguments corresponding to fields. For example:
+
+ class Date(Message):
+ day = IntegerField(1)
+ month = IntegerField(2)
+ year = IntegerField(3)
+
+ Invoking:
+
+ date = Date(day=6, month=6, year=1911)
+
+ is the same as doing:
+
+ date = Date()
+ date.day = 6
+ date.month = 6
+ date.year = 1911
+ """
+ # Tag being an essential implementation detail must be private.
+ self.__tags = {}
+ self.__unrecognized_fields = {}
+
+ assigned = set()
+ for name, value in kwargs.items():
+ setattr(self, name, value)
+ assigned.add(name)
+
+ # initialize repeated fields.
+ for field in self.all_fields():
+ if field.repeated and field.name not in assigned:
+ setattr(self, field.name, [])
+
+
+ def check_initialized(self):
+ """Check class for initialization status.
+
+ Check that all required fields are initialized
+
+ Raises:
+ ValidationError: If message is not initialized.
+ """
+ for name, field in self.__by_name.items():
+ value = getattr(self, name)
+ if value is None:
+ if field.required:
+ raise ValidationError("Message %s is missing required field %s" %
+ (type(self).__name__, name))
+ else:
+ try:
+ if (isinstance(field, MessageField) and
+ issubclass(field.message_type, Message)):
+ if field.repeated:
+ for item in value:
+ item_message_value = field.value_to_message(item)
+ item_message_value.check_initialized()
+ else:
+ message_value = field.value_to_message(value)
+ message_value.check_initialized()
+ except ValidationError as err:
+ if not hasattr(err, 'message_name'):
+ err.message_name = type(self).__name__
+ raise
+
+ def is_initialized(self):
+ """Get initialization status.
+
+ Returns:
+ True if message is valid, else False.
+ """
+ try:
+ self.check_initialized()
+ except ValidationError:
+ return False
+ else:
+ return True
+
+ @classmethod
+ def all_fields(cls):
+ """Get all field definition objects.
+
+ Ordering is arbitrary.
+
+ Returns:
+ Iterator over all values in arbitrary order.
+ """
+ return cls.__by_name.values()
+
+ @classmethod
+ def field_by_name(cls, name):
+ """Get field by name.
+
+ Returns:
+ Field object associated with name.
+
+ Raises:
+ KeyError if no field found by that name.
+ """
+ return cls.__by_name[name]
+
+ @classmethod
+ def field_by_number(cls, number):
+ """Get field by number.
+
+ Returns:
+ Field object associated with number.
+
+ Raises:
+ KeyError if no field found by that number.
+ """
+ return cls.__by_number[number]
+
+ def get_assigned_value(self, name):
+ """Get the assigned value of an attribute.
+
+ Get the underlying value of an attribute. If value has not been set, will
+ not return the default for the field.
+
+ Args:
+ name: Name of attribute to get.
+
+ Returns:
+ Value of attribute, None if it has not been set.
+ """
+ message_type = type(self)
+ try:
+ field = message_type.field_by_name(name)
+ except KeyError:
+ raise AttributeError('Message %s has no field %s' % (
+ message_type.__name__, name))
+ return self.__tags.get(field.number)
+
+ def reset(self, name):
+ """Reset assigned value for field.
+
+ Resetting a field will return it to its default value or None.
+
+ Args:
+ name: Name of field to reset.
+ """
+ message_type = type(self)
+ try:
+ field = message_type.field_by_name(name)
+ except KeyError:
+ if name not in message_type.__by_name:
+ raise AttributeError('Message %s has no field %s' % (
+ message_type.__name__, name))
+ if field.repeated:
+ self.__tags[field.number] = FieldList(field, [])
+ else:
+ self.__tags.pop(field.number, None)
+
+ def all_unrecognized_fields(self):
+ """Get the names of all unrecognized fields in this message."""
+ return list(self.__unrecognized_fields.keys())
+
+ def get_unrecognized_field_info(self, key, value_default=None,
+ variant_default=None):
+ """Get the value and variant of an unknown field in this message.
+
+ Args:
+ key: The name or number of the field to retrieve.
+ value_default: Value to be returned if the key isn't found.
+ variant_default: Value to be returned as variant if the key isn't
+ found.
+
+ Returns:
+ (value, variant), where value and variant are whatever was passed
+ to set_unrecognized_field.
+ """
+ value, variant = self.__unrecognized_fields.get(key, (value_default,
+ variant_default))
+ return value, variant
+
+ def set_unrecognized_field(self, key, value, variant):
+ """Set an unrecognized field, used when decoding a message.
+
+ Args:
+ key: The name or number used to refer to this unknown value.
+ value: The value of the field.
+ variant: Type information needed to interpret the value or re-encode it.
+
+ Raises:
+ TypeError: If the variant is not an instance of messages.Variant.
+ """
+ if not isinstance(variant, Variant):
+ raise TypeError('Variant type %s is not valid.' % variant)
+ self.__unrecognized_fields[key] = value, variant
+
+ def __setattr__(self, name, value):
+ """Change set behavior for messages.
+
+ Messages may only be assigned values that are fields.
+
+ Does not try to validate field when set.
+
+ Args:
+ name: Name of field to assign to.
+ vlaue: Value to assign to field.
+
+ Raises:
+ AttributeError when trying to assign value that is not a field.
+ """
+ if name in self.__by_name or name.startswith('_Message__'):
+ object.__setattr__(self, name, value)
+ else:
+ raise AttributeError("May not assign arbitrary value %s "
+ "to message %s" % (name, type(self).__name__))
+
+ def __repr__(self):
+ """Make string representation of message.
+
+ Example:
+
+ class MyMessage(messages.Message):
+ integer_value = messages.IntegerField(1)
+ string_value = messages.StringField(2)
+
+ my_message = MyMessage()
+ my_message.integer_value = 42
+ my_message.string_value = u'A string'
+
+ print my_message
+ >>> <MyMessage
+ ... integer_value: 42
+ ... string_value: u'A string'>
+
+ Returns:
+ String representation of message, including the values
+ of all fields and repr of all sub-messages.
+ """
+ body = ['<', type(self).__name__]
+ for field in sorted(self.all_fields(),
+ key=lambda f: f.number):
+ attribute = field.name
+ value = self.get_assigned_value(field.name)
+ if value is not None:
+ body.append('\n %s: %s' % (attribute, repr(value)))
+ body.append('>')
+ return ''.join(body)
+
+ def __eq__(self, other):
+ """Equality operator.
+
+ Does field by field comparison with other message. For
+ equality, must be same type and values of all fields must be
+ equal.
+
+ Messages not required to be initialized for comparison.
+
+ Does not attempt to determine equality for values that have
+ default values that are not set. In other words:
+
+ class HasDefault(Message):
+
+ attr1 = StringField(1, default='default value')
+
+ message1 = HasDefault()
+ message2 = HasDefault()
+ message2.attr1 = 'default value'
+
+ message1 != message2
+
+ Does not compare unknown values.
+
+ Args:
+ other: Other message to compare with.
+ """
+ # TODO(rafek): Implement "equivalent" which does comparisons
+ # taking default values in to consideration.
+ if self is other:
+ return True
+
+ if type(self) is not type(other):
+ return False
+
+ return self.__tags == other.__tags
+
+ def __ne__(self, other):
+ """Not equals operator.
+
+ Does field by field comparison with other message. For
+ non-equality, must be different type or any value of a field must be
+ non-equal to the same field in the other instance.
+
+ Messages not required to be initialized for comparison.
+
+ Args:
+ other: Other message to compare with.
+ """
+ return not self.__eq__(other)
+
+
+class FieldList(list):
+ """List implementation that validates field values.
+
+ This list implementation overrides all methods that add values in to a list
+ in order to validate those new elements. Attempting to add or set list
+ values that are not of the correct type will raise ValidationError.
+ """
+
+ def __init__(self, field_instance, sequence):
+ """Constructor.
+
+ Args:
+ field_instance: Instance of field that validates the list.
+ sequence: List or tuple to construct list from.
+ """
+ if not field_instance.repeated:
+ raise FieldDefinitionError('FieldList may only accept repeated fields')
+ self.__field = field_instance
+ self.__field.validate(sequence)
+ list.__init__(self, sequence)
+
+ def __getstate__(self):
+ """Enable pickling.
+
+ The assigned field instance can't be pickled if it belongs to a Message
+ definition (message_definition uses a weakref), so the Message class and
+ field number are returned in that case.
+
+ Returns:
+ A 3-tuple containing:
+ - The field instance, or None if it belongs to a Message class.
+ - The Message class that the field instance belongs to, or None.
+ - The field instance number of the Message class it belongs to, or None.
+ """
+ message_class = self.__field.message_definition()
+ if message_class is None:
+ return self.__field, None, None
+ else:
+ return None, message_class, self.__field.number
+
+ def __setstate__(self, state):
+ """Enable unpickling.
+
+ Args:
+ state: A 3-tuple containing:
+ - The field instance, or None if it belongs to a Message class.
+ - The Message class that the field instance belongs to, or None.
+ - The field instance number of the Message class it belongs to, or None.
+ """
+ field_instance, message_class, number = state
+ if field_instance is None:
+ self.__field = message_class.field_by_number(number)
+ else:
+ self.__field = field_instance
+
+ @property
+ def field(self):
+ """Field that validates list."""
+ return self.__field
+
+ def __setslice__(self, i, j, sequence):
+ """Validate slice assignment to list."""
+ self.__field.validate(sequence)
+ list.__setslice__(self, i, j, sequence)
+
+ def __setitem__(self, index, value):
+ """Validate item assignment to list."""
+ if isinstance(index, slice):
+ self.__field.validate(value)
+ else:
+ self.__field.validate_element(value)
+ list.__setitem__(self, index, value)
+
+ def append(self, value):
+ """Validate item appending to list."""
+ self.__field.validate_element(value)
+ return list.append(self, value)
+
+ def extend(self, sequence):
+ """Validate extension of list."""
+ self.__field.validate(sequence)
+ return list.extend(self, sequence)
+
+ def insert(self, index, value):
+ """Validate item insertion to list."""
+ self.__field.validate_element(value)
+ return list.insert(self, index, value)
+
+
+# TODO(rafek): Prevent additional field subclasses.
+class Field(object):
+
+ __variant_to_type = {}
+
+ class __metaclass__(type):
+
+ def __init__(cls, name, bases, dct):
+ getattr(cls, '_Field__variant_to_type').update(
+ (variant, cls) for variant in dct.get('VARIANTS', []))
+ type.__init__(cls, name, bases, dct)
+
+ __initialized = False
+
+ @util.positional(2)
+ def __init__(self,
+ number,
+ required=False,
+ repeated=False,
+ variant=None,
+ default=None):
+ """Constructor.
+
+ The required and repeated parameters are mutually exclusive. Setting both
+ to True will raise a FieldDefinitionError.
+
+ Sub-class Attributes:
+ Each sub-class of Field must define the following:
+ VARIANTS: Set of variant types accepted by that field.
+ DEFAULT_VARIANT: Default variant type if not specified in constructor.
+
+ Args:
+ number: Number of field. Must be unique per message class.
+ required: Whether or not field is required. Mutually exclusive with
+ 'repeated'.
+ repeated: Whether or not field is repeated. Mutually exclusive with
+ 'required'.
+ variant: Wire-format variant hint.
+ default: Default value for field if not found in stream.
+
+ Raises:
+ InvalidVariantError when invalid variant for field is provided.
+ InvalidDefaultError when invalid default for field is provided.
+ FieldDefinitionError when invalid number provided or mutually exclusive
+ fields are used.
+ InvalidNumberError when the field number is out of range or reserved.
+ """
+ if not isinstance(number, int) or not 1 <= number <= MAX_FIELD_NUMBER:
+ raise InvalidNumberError('Invalid number for field: %s\n'
+ 'Number must be 1 or greater and %d or less' %
+ (number, MAX_FIELD_NUMBER))
+
+ if FIRST_RESERVED_FIELD_NUMBER <= number <= LAST_RESERVED_FIELD_NUMBER:
+ raise InvalidNumberError('Tag number %d is a reserved number.\n'
+ 'Numbers %d to %d are reserved' %
+ (number, FIRST_RESERVED_FIELD_NUMBER,
+ LAST_RESERVED_FIELD_NUMBER))
+
+ if repeated and required:
+ raise FieldDefinitionError('Cannot set both repeated and required')
+
+ if variant is None:
+ variant = self.DEFAULT_VARIANT
+
+ if repeated and default is not None:
+ raise FieldDefinitionError('Repeated fields may not have defaults')
+
+ if variant not in self.VARIANTS:
+ raise InvalidVariantError(
+ 'Invalid variant: %s\nValid variants for %s are %r' %
+ (variant, type(self).__name__, sorted(self.VARIANTS)))
+
+ self.number = number
+ self.required = required
+ self.repeated = repeated
+ self.variant = variant
+
+ if default is not None:
+ try:
+ self.validate_default(default)
+ except ValidationError as err:
+ try:
+ name = self.name
+ except AttributeError:
+ # For when raising error before name initialization.
+ raise InvalidDefaultError('Invalid default value for %s: %r: %s' %
+ (self.__class__.__name__, default, err))
+ else:
+ raise InvalidDefaultError('Invalid default value for field %s: '
+ '%r: %s' % (name, default, err))
+
+ self.__default = default
+ self.__initialized = True
+
+ def __setattr__(self, name, value):
+ """Setter overidden to prevent assignment to fields after creation.
+
+ Args:
+ name: Name of attribute to set.
+ value: Value to assign.
+ """
+ # Special case post-init names. They need to be set after constructor.
+ if name in _POST_INIT_FIELD_ATTRIBUTE_NAMES:
+ object.__setattr__(self, name, value)
+ return
+
+ # All other attributes must be set before __initialized.
+ if not self.__initialized:
+ # Not initialized yet, allow assignment.
+ object.__setattr__(self, name, value)
+ else:
+ raise AttributeError('Field objects are read-only')
+
+ def __set__(self, message_instance, value):
+ """Set value on message.
+
+ Args:
+ message_instance: Message instance to set value on.
+ value: Value to set on message.
+ """
+ # Reaches in to message instance directly to assign to private tags.
+ if value is None:
+ if self.repeated:
+ raise ValidationError(
+ 'May not assign None to repeated field %s' % self.name)
+ else:
+ message_instance._Message__tags.pop(self.number, None)
+ else:
+ if self.repeated:
+ value = FieldList(self, value)
+ else:
+ self.validate(value)
+ message_instance._Message__tags[self.number] = value
+
+ def __get__(self, message_instance, message_class):
+ if message_instance is None:
+ return self
+
+ result = message_instance._Message__tags.get(self.number)
+ if result is None:
+ return self.default
+ else:
+ return result
+
+ def validate_element(self, value):
+ """Validate single element of field.
+
+ This is different from validate in that it is used on individual
+ values of repeated fields.
+
+ Args:
+ value: Value to validate.
+
+ Raises:
+ ValidationError if value is not expected type.
+ """
+ if not isinstance(value, self.type):
+ if value is None:
+ if self.required:
+ raise ValidationError('Required field is missing')
+ else:
+ try:
+ name = self.name
+ except AttributeError:
+ raise ValidationError('Expected type %s for %s, '
+ 'found %s (type %s)' %
+ (self.type, self.__class__.__name__,
+ value, type(value)))
+ else:
+ raise ValidationError('Expected type %s for field %s, '
+ 'found %s (type %s)' %
+ (self.type, name, value, type(value)))
+
+ def __validate(self, value, validate_element):
+ """Internal validation function.
+
+ Validate an internal value using a function to validate individual elements.
+
+ Args:
+ value: Value to validate.
+ validate_element: Function to use to validate individual elements.
+
+ Raises:
+ ValidationError if value is not expected type.
+ """
+ if not self.repeated:
+ validate_element(value)
+ else:
+ # Must be a list or tuple, may not be a string.
+ if isinstance(value, (list, tuple)):
+ for element in value:
+ if element is None:
+ try:
+ name = self.name
+ except AttributeError:
+ raise ValidationError('Repeated values for %s '
+ 'may not be None' % self.__class__.__name__)
+ else:
+ raise ValidationError('Repeated values for field %s '
+ 'may not be None' % name)
+ validate_element(element)
+ elif value is not None:
+ try:
+ name = self.name
+ except AttributeError:
+ raise ValidationError('%s is repeated. Found: %s' % (
+ self.__class__.__name__, value))
+ else:
+ raise ValidationError('Field %s is repeated. Found: %s' % (name,
+ value))
+
+ def validate(self, value):
+ """Validate value assigned to field.
+
+ Args:
+ value: Value to validate.
+
+ Raises:
+ ValidationError if value is not expected type.
+ """
+ self.__validate(value, self.validate_element)
+
+ def validate_default_element(self, value):
+ """Validate value as assigned to field default field.
+
+ Some fields may allow for delayed resolution of default types necessary
+ in the case of circular definition references. In this case, the default
+ value might be a place holder that is resolved when needed after all the
+ message classes are defined.
+
+ Args:
+ value: Default value to validate.
+
+ Raises:
+ ValidationError if value is not expected type.
+ """
+ self.validate_element(value)
+
+ def validate_default(self, value):
+ """Validate default value assigned to field.
+
+ Args:
+ value: Value to validate.
+
+ Raises:
+ ValidationError if value is not expected type.
+ """
+ self.__validate(value, self.validate_default_element)
+
+ def message_definition(self):
+ """Get Message definition that contains this Field definition.
+
+ Returns:
+ Containing Message definition for Field. Will return None if for
+ some reason Field is defined outside of a Message class.
+ """
+ try:
+ return self._message_definition()
+ except AttributeError:
+ return None
+
+ @property
+ def default(self):
+ """Get default value for field."""
+ return self.__default
+
+ @classmethod
+ def lookup_field_type_by_variant(cls, variant):
+ return cls.__variant_to_type[variant]
+
+
+class IntegerField(Field):
+ """Field definition for integer values."""
+
+ VARIANTS = frozenset([Variant.INT32,
+ Variant.INT64,
+ Variant.UINT32,
+ Variant.UINT64,
+ Variant.SINT32,
+ Variant.SINT64,
+ ])
+
+ DEFAULT_VARIANT = Variant.INT64
+
+ type = six.integer_types
+
+
+class FloatField(Field):
+ """Field definition for float values."""
+
+ VARIANTS = frozenset([Variant.FLOAT,
+ Variant.DOUBLE,
+ ])
+
+ DEFAULT_VARIANT = Variant.DOUBLE
+
+ type = float
+
+
+class BooleanField(Field):
+ """Field definition for boolean values."""
+
+ VARIANTS = frozenset([Variant.BOOL])
+
+ DEFAULT_VARIANT = Variant.BOOL
+
+ type = bool
+
+
+class BytesField(Field):
+ """Field definition for byte string values."""
+
+ VARIANTS = frozenset([Variant.BYTES])
+
+ DEFAULT_VARIANT = Variant.BYTES
+
+ type = bytes
+
+
+class StringField(Field):
+ """Field definition for unicode string values."""
+
+ VARIANTS = frozenset([Variant.STRING])
+
+ DEFAULT_VARIANT = Variant.STRING
+
+ type = six.text_type
+
+ def validate_element(self, value):
+ """Validate StringField allowing for str and unicode.
+
+ Raises:
+ ValidationError if a str value is not 7-bit ascii.
+ """
+ # If value is str is it considered valid. Satisfies "required=True".
+ if isinstance(value, bytes):
+ try:
+ six.text_type(value, 'ascii')
+ except UnicodeDecodeError as err:
+ try:
+ name = self.name
+ except AttributeError:
+ validation_error = ValidationError(
+ 'Field encountered non-ASCII string %r: %s' % (value,
+ err))
+ else:
+ validation_error = ValidationError(
+ 'Field %s encountered non-ASCII string %r: %s' % (self.name,
+ value,
+ err))
+ validation_error.field_name = self.name
+ raise validation_error
+ else:
+ super(StringField, self).validate_element(value)
+
+
+class MessageField(Field):
+ """Field definition for sub-message values.
+
+ Message fields contain instance of other messages. Instances stored
+ on messages stored on message fields are considered to be owned by
+ the containing message instance and should not be shared between
+ owning instances.
+
+ Message fields must be defined to reference a single type of message.
+ Normally message field are defined by passing the referenced message
+ class in to the constructor.
+
+ It is possible to define a message field for a type that does not yet
+ exist by passing the name of the message in to the constructor instead
+ of a message class. Resolution of the actual type of the message is
+ deferred until it is needed, for example, during message verification.
+ Names provided to the constructor must refer to a class within the same
+ python module as the class that is using it. Names refer to messages
+ relative to the containing messages scope. For example, the two fields
+ of OuterMessage refer to the same message type:
+
+ class Outer(Message):
+
+ inner_relative = MessageField('Inner', 1)
+ inner_absolute = MessageField('Outer.Inner', 2)
+
+ class Inner(Message):
+ ...
+
+ When resolving an actual type, MessageField will traverse the entire
+ scope of nested messages to match a message name. This makes it easy
+ for siblings to reference siblings:
+
+ class Outer(Message):
+
+ class Inner(Message):
+
+ sibling = MessageField('Sibling', 1)
+
+ class Sibling(Message):
+ ...
+ """
+
+ VARIANTS = frozenset([Variant.MESSAGE])
+
+ DEFAULT_VARIANT = Variant.MESSAGE
+
+ @util.positional(3)
+ def __init__(self,
+ message_type,
+ number,
+ required=False,
+ repeated=False,
+ variant=None):
+ """Constructor.
+
+ Args:
+ message_type: Message type for field. Must be subclass of Message.
+ number: Number of field. Must be unique per message class.
+ required: Whether or not field is required. Mutually exclusive to
+ 'repeated'.
+ repeated: Whether or not field is repeated. Mutually exclusive to
+ 'required'.
+ variant: Wire-format variant hint.
+
+ Raises:
+ FieldDefinitionError when invalid message_type is provided.
+ """
+ valid_type = (isinstance(message_type, six.string_types) or
+ (message_type is not Message and
+ isinstance(message_type, type) and
+ issubclass(message_type, Message)))
+
+ if not valid_type:
+ raise FieldDefinitionError('Invalid message class: %s' % message_type)
+
+ if isinstance(message_type, six.string_types):
+ self.__type_name = message_type
+ self.__type = None
+ else:
+ self.__type = message_type
+
+ super(MessageField, self).__init__(number,
+ required=required,
+ repeated=repeated,
+ variant=variant)
+
+ def __set__(self, message_instance, value):
+ """Set value on message.
+
+ Args:
+ message_instance: Message instance to set value on.
+ value: Value to set on message.
+ """
+ message_type = self.type
+ if isinstance(message_type, type) and issubclass(message_type, Message):
+ if self.repeated:
+ if value and isinstance(value, (list, tuple)):
+ value = [(message_type(**v) if isinstance(v, dict) else v)
+ for v in value]
+ elif isinstance(value, dict):
+ value = message_type(**value)
+ super(MessageField, self).__set__(message_instance, value)
+
+ @property
+ def type(self):
+ """Message type used for field."""
+ if self.__type is None:
+ message_type = find_definition(self.__type_name, self.message_definition())
+ if not (message_type is not Message and
+ isinstance(message_type, type) and
+ issubclass(message_type, Message)):
+ raise FieldDefinitionError('Invalid message class: %s' % message_type)
+ self.__type = message_type
+ return self.__type
+
+ @property
+ def message_type(self):
+ """Underlying message type used for serialization.
+
+ Will always be a sub-class of Message. This is different from type
+ which represents the python value that message_type is mapped to for
+ use by the user.
+ """
+ return self.type
+
+ def value_from_message(self, message):
+ """Convert a message to a value instance.
+
+ Used by deserializers to convert from underlying messages to
+ value of expected user type.
+
+ Args:
+ message: A message instance of type self.message_type.
+
+ Returns:
+ Value of self.message_type.
+ """
+ if not isinstance(message, self.message_type):
+ raise DecodeError('Expected type %s, got %s: %r' %
+ (self.message_type.__name__,
+ type(message).__name__,
+ message))
+ return message
+
+ def value_to_message(self, value):
+ """Convert a value instance to a message.
+
+ Used by serializers to convert Python user types to underlying
+ messages for transmission.
+
+ Args:
+ value: A value of type self.type.
+
+ Returns:
+ An instance of type self.message_type.
+ """
+ if not isinstance(value, self.type):
+ raise EncodeError('Expected type %s, got %s: %r' %
+ (self.type.__name__,
+ type(value).__name__,
+ value))
+ return value
+
+
+class EnumField(Field):
+ """Field definition for enum values.
+
+ Enum fields may have default values that are delayed until the associated enum
+ type is resolved. This is necessary to support certain circular references.
+
+ For example:
+
+ class Message1(Message):
+
+ class Color(Enum):
+
+ RED = 1
+ GREEN = 2
+ BLUE = 3
+
+ # This field default value will be validated when default is accessed.
+ animal = EnumField('Message2.Animal', 1, default='HORSE')
+
+ class Message2(Message):
+
+ class Animal(Enum):
+
+ DOG = 1
+ CAT = 2
+ HORSE = 3
+
+ # This fields default value will be validated right away since Color is
+ # already fully resolved.
+ color = EnumField(Message1.Color, 1, default='RED')
+ """
+
+ VARIANTS = frozenset([Variant.ENUM])
+
+ DEFAULT_VARIANT = Variant.ENUM
+
+ def __init__(self, enum_type, number, **kwargs):
+ """Constructor.
+
+ Args:
+ enum_type: Enum type for field. Must be subclass of Enum.
+ number: Number of field. Must be unique per message class.
+ required: Whether or not field is required. Mutually exclusive to
+ 'repeated'.
+ repeated: Whether or not field is repeated. Mutually exclusive to
+ 'required'.
+ variant: Wire-format variant hint.
+ default: Default value for field if not found in stream.
+
+ Raises:
+ FieldDefinitionError when invalid enum_type is provided.
+ """
+ valid_type = (isinstance(enum_type, six.string_types) or
+ (enum_type is not Enum and
+ isinstance(enum_type, type) and
+ issubclass(enum_type, Enum)))
+
+ if not valid_type:
+ raise FieldDefinitionError('Invalid enum type: %s' % enum_type)
+
+ if isinstance(enum_type, six.string_types):
+ self.__type_name = enum_type
+ self.__type = None
+ else:
+ self.__type = enum_type
+
+ super(EnumField, self).__init__(number, **kwargs)
+
+ def validate_default_element(self, value):
+ """Validate default element of Enum field.
+
+ Enum fields allow for delayed resolution of default values when the type
+ of the field has not been resolved. The default value of a field may be
+ a string or an integer. If the Enum type of the field has been resolved,
+ the default value is validated against that type.
+
+ Args:
+ value: Value to validate.
+
+ Raises:
+ ValidationError if value is not expected message type.
+ """
+ if isinstance(value, (six.string_types, six.integer_types)):
+ # Validation of the value does not happen for delayed resolution
+ # enumerated types. Ignore if type is not yet resolved.
+ if self.__type:
+ self.__type(value)
+ return
+
+ super(EnumField, self).validate_default_element(value)
+
+ @property
+ def type(self):
+ """Enum type used for field."""
+ if self.__type is None:
+ found_type = find_definition(self.__type_name, self.message_definition())
+ if not (found_type is not Enum and
+ isinstance(found_type, type) and
+ issubclass(found_type, Enum)):
+ raise FieldDefinitionError('Invalid enum type: %s' % found_type)
+
+ self.__type = found_type
+ return self.__type
+
+ @property
+ def default(self):
+ """Default for enum field.
+
+ Will cause resolution of Enum type and unresolved default value.
+ """
+ try:
+ return self.__resolved_default
+ except AttributeError:
+ resolved_default = super(EnumField, self).default
+ if isinstance(resolved_default, (six.string_types, six.integer_types)):
+ resolved_default = self.type(resolved_default)
+ self.__resolved_default = resolved_default
+ return self.__resolved_default
+
+
+@util.positional(2)
+def find_definition(name, relative_to=None, importer=__import__):
+ """Find definition by name in module-space.
+
+ The find algorthm will look for definitions by name relative to a message
+ definition or by fully qualfied name. If no definition is found relative
+ to the relative_to parameter it will do the same search against the container
+ of relative_to. If relative_to is a nested Message, it will search its
+ message_definition(). If that message has no message_definition() it will
+ search its module. If relative_to is a module, it will attempt to look for
+ the containing module and search relative to it. If the module is a top-level
+ module, it will look for the a message using a fully qualified name. If
+ no message is found then, the search fails and DefinitionNotFoundError is
+ raised.
+
+ For example, when looking for any definition 'foo.bar.ADefinition' relative to
+ an actual message definition abc.xyz.SomeMessage:
+
+ find_definition('foo.bar.ADefinition', SomeMessage)
+
+ It is like looking for the following fully qualified names:
+
+ abc.xyz.SomeMessage. foo.bar.ADefinition
+ abc.xyz. foo.bar.ADefinition
+ abc. foo.bar.ADefinition
+ foo.bar.ADefinition
+
+ When resolving the name relative to Message definitions and modules, the
+ algorithm searches any Messages or sub-modules found in its path.
+ Non-Message values are not searched.
+
+ A name that begins with '.' is considered to be a fully qualified name. The
+ name is always searched for from the topmost package. For example, assume
+ two message types:
+
+ abc.xyz.SomeMessage
+ xyz.SomeMessage
+
+ Searching for '.xyz.SomeMessage' relative to 'abc' will resolve to
+ 'xyz.SomeMessage' and not 'abc.xyz.SomeMessage'. For this kind of name,
+ the relative_to parameter is effectively ignored and always set to None.
+
+ For more information about package name resolution, please see:
+
+ http://code.google.com/apis/protocolbuffers/docs/proto.html#packages
+
+ Args:
+ name: Name of definition to find. May be fully qualified or relative name.
+ relative_to: Search for definition relative to message definition or module.
+ None will cause a fully qualified name search.
+ importer: Import function to use for resolving modules.
+
+ Returns:
+ Enum or Message class definition associated with name.
+
+ Raises:
+ DefinitionNotFoundError if no definition is found in any search path.
+ """
+ # Check parameters.
+ if not (relative_to is None or
+ isinstance(relative_to, types.ModuleType) or
+ isinstance(relative_to, type) and issubclass(relative_to, Message)):
+ raise TypeError('relative_to must be None, Message definition or module. '
+ 'Found: %s' % relative_to)
+
+ name_path = name.split('.')
+
+ # Handle absolute path reference.
+ if not name_path[0]:
+ relative_to = None
+ name_path = name_path[1:]
+
+ def search_path():
+ """Performs a single iteration searching the path from relative_to.
+
+ This is the function that searches up the path from a relative object.
+
+ fully.qualified.object . relative.or.nested.Definition
+ ---------------------------->
+ ^
+ |
+ this part of search --+
+
+ Returns:
+ Message or Enum at the end of name_path, else None.
+ """
+ next = relative_to
+ for node in name_path:
+ # Look for attribute first.
+ attribute = getattr(next, node, None)
+
+ if attribute is not None:
+ next = attribute
+ else:
+ # If module, look for sub-module.
+ if next is None or isinstance(next, types.ModuleType):
+ if next is None:
+ module_name = node
+ else:
+ module_name = '%s.%s' % (next.__name__, node)
+
+ try:
+ fromitem = module_name.split('.')[-1]
+ next = importer(module_name, '', '', [str(fromitem)])
+ except ImportError:
+ return None
+ else:
+ return None
+
+ if (not isinstance(next, types.ModuleType) and
+ not (isinstance(next, type) and
+ issubclass(next, (Message, Enum)))):
+ return None
+
+ return next
+
+ while True:
+ found = search_path()
+ if isinstance(found, type) and issubclass(found, (Enum, Message)):
+ return found
+ else:
+ # Find next relative_to to search against.
+ #
+ # fully.qualified.object . relative.or.nested.Definition
+ # <---------------------
+ # ^
+ # |
+ # does this part of search
+ if relative_to is None:
+ # Fully qualified search was done. Nothing found. Fail.
+ raise DefinitionNotFoundError('Could not find definition for %s'
+ % (name,))
+ else:
+ if isinstance(relative_to, types.ModuleType):
+ # Find parent module.
+ module_path = relative_to.__name__.split('.')[:-1]
+ if not module_path:
+ relative_to = None
+ else:
+ # Should not raise ImportError. If it does... weird and
+ # unexepected. Propagate.
+ relative_to = importer(
+ '.'.join(module_path), '', '', [module_path[-1]])
+ elif (isinstance(relative_to, type) and
+ issubclass(relative_to, Message)):
+ parent = relative_to.message_definition()
+ if parent is None:
+ last_module_name = relative_to.__module__.split('.')[-1]
+ relative_to = importer(
+ relative_to.__module__, '', '', [last_module_name])
+ else:
+ relative_to = parent

Powered by Google App Engine
This is Rietveld 408576698