| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import collections |
| | import collections.abc |
| | import copy |
| | import re |
| | from typing import List, Optional, Type |
| | import warnings |
| |
|
| | import google.protobuf |
| | from google.protobuf import descriptor_pb2 |
| | from google.protobuf import message |
| | from google.protobuf.json_format import MessageToDict, MessageToJson, Parse |
| |
|
| | from proto import _file_info |
| | from proto import _package_info |
| | from proto.fields import Field |
| | from proto.fields import MapField |
| | from proto.fields import RepeatedField |
| | from proto.marshal import Marshal |
| | from proto.primitives import ProtoType |
| | from proto.utils import has_upb |
| |
|
| |
|
| | PROTOBUF_VERSION = google.protobuf.__version__ |
| |
|
| | _upb = has_upb() |
| |
|
| |
|
| | class MessageMeta(type): |
| | """A metaclass for building and registering Message subclasses.""" |
| |
|
| | def __new__(mcls, name, bases, attrs): |
| | |
| | if not bases: |
| | return super().__new__(mcls, name, bases, attrs) |
| |
|
| | |
| | |
| | package, marshal = _package_info.compile(name, attrs) |
| |
|
| | |
| | local_path = tuple(attrs.get("__qualname__", name).split(".")) |
| |
|
| | |
| | |
| | if "<locals>" in local_path: |
| | ix = local_path.index("<locals>") |
| | local_path = local_path[: ix - 1] + local_path[ix + 1 :] |
| |
|
| | |
| | full_name = ".".join((package,) + local_path).lstrip(".") |
| |
|
| | |
| | |
| | |
| | |
| | map_fields = {} |
| | for key, field in attrs.items(): |
| | if not isinstance(field, MapField): |
| | continue |
| |
|
| | |
| | msg_name = "{pascal_key}Entry".format( |
| | pascal_key=re.sub( |
| | r"_\w", |
| | lambda m: m.group()[1:].upper(), |
| | key, |
| | ).replace(key[0], key[0].upper(), 1), |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | entry_attrs = collections.OrderedDict( |
| | { |
| | "__module__": attrs.get("__module__", None), |
| | "__qualname__": "{prefix}.{name}".format( |
| | prefix=attrs.get("__qualname__", name), |
| | name=msg_name, |
| | ), |
| | "_pb_options": {"map_entry": True}, |
| | } |
| | ) |
| | entry_attrs["key"] = Field(field.map_key_type, number=1) |
| | entry_attrs["value"] = Field( |
| | field.proto_type, |
| | number=2, |
| | enum=field.enum, |
| | message=field.message, |
| | ) |
| | map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs) |
| |
|
| | |
| | map_fields[key] = RepeatedField( |
| | ProtoType.MESSAGE, |
| | number=field.number, |
| | message=map_fields[msg_name], |
| | ) |
| |
|
| | |
| | attrs.update(map_fields) |
| |
|
| | |
| | |
| | |
| | fields = [] |
| | new_attrs = {} |
| | oneofs = collections.OrderedDict() |
| | proto_imports = set() |
| | index = 0 |
| | for key, field in attrs.items(): |
| | |
| | if not isinstance(field, Field): |
| | |
| | new_attrs[key] = field |
| | continue |
| |
|
| | |
| | |
| | |
| | field.mcls_data = { |
| | "name": key, |
| | "parent_name": full_name, |
| | "index": index, |
| | "package": package, |
| | } |
| |
|
| | |
| | fields.append(field) |
| | |
| | |
| | if field.oneof: |
| | |
| | |
| | oneofs.setdefault(field.oneof, len(oneofs)) |
| | field.descriptor.oneof_index = oneofs[field.oneof] |
| |
|
| | |
| | |
| | |
| | if field.message and not isinstance(field.message, str): |
| | field_msg = field.message |
| | if hasattr(field_msg, "pb") and callable(field_msg.pb): |
| | field_msg = field_msg.pb() |
| | |
| | |
| | |
| | |
| | |
| | |
| | if field_msg: |
| | proto_imports.add(field_msg.DESCRIPTOR.file.name) |
| |
|
| | |
| | elif field.enum and not isinstance(field.enum, str): |
| | field_enum = ( |
| | field.enum._meta.pb |
| | if hasattr(field.enum, "_meta") |
| | else field.enum.DESCRIPTOR |
| | ) |
| |
|
| | if field_enum: |
| | proto_imports.add(field_enum.file.name) |
| |
|
| | |
| | index += 1 |
| |
|
| | |
| | |
| | opt_attrs = {} |
| | for field in fields: |
| | if field.optional: |
| | field.oneof = "_{}".format(field.name) |
| | field.descriptor.oneof_index = oneofs[field.oneof] = len(oneofs) |
| | opt_attrs[field.name] = field.name |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if opt_attrs: |
| | mcls = type("AttrsMeta", (mcls,), opt_attrs) |
| |
|
| | |
| | |
| | |
| | filename = _file_info._FileInfo.proto_file_name( |
| | new_attrs.get("__module__", name.lower()) |
| | ) |
| |
|
| | |
| | |
| | file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package) |
| |
|
| | |
| | |
| | for proto_import in proto_imports: |
| | if proto_import not in file_info.descriptor.dependency: |
| | file_info.descriptor.dependency.append(proto_import) |
| |
|
| | |
| | opts = descriptor_pb2.MessageOptions(**new_attrs.pop("_pb_options", {})) |
| |
|
| | |
| | desc = descriptor_pb2.DescriptorProto( |
| | name=name, |
| | field=[i.descriptor for i in fields], |
| | oneof_decl=[ |
| | descriptor_pb2.OneofDescriptorProto(name=i) for i in oneofs.keys() |
| | ], |
| | options=opts, |
| | ) |
| |
|
| | |
| | |
| | child_paths = [p for p in file_info.nested.keys() if local_path == p[:-1]] |
| | for child_path in child_paths: |
| | desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path)) |
| |
|
| | |
| | child_paths = [p for p in file_info.nested_enum.keys() if local_path == p[:-1]] |
| | for child_path in child_paths: |
| | desc.enum_type.add().MergeFrom(file_info.nested_enum.pop(child_path)) |
| |
|
| | |
| | |
| | if len(local_path) == 1: |
| | file_info.descriptor.message_type.add().MergeFrom(desc) |
| | else: |
| | file_info.nested[local_path] = desc |
| |
|
| | |
| | new_attrs["_meta"] = _MessageInfo( |
| | fields=fields, |
| | full_name=full_name, |
| | marshal=marshal, |
| | options=opts, |
| | package=package, |
| | ) |
| |
|
| | |
| | cls = super().__new__(mcls, name, bases, new_attrs) |
| |
|
| | |
| | cls._meta.parent = cls |
| | for field in cls._meta.fields.values(): |
| | field.parent = cls |
| |
|
| | |
| | |
| | |
| | file_info.messages[full_name] = cls |
| |
|
| | |
| | if file_info.ready(new_class=cls): |
| | file_info.generate_file_pb(new_class=cls, fallback_salt=full_name) |
| |
|
| | |
| | return cls |
| |
|
| | @classmethod |
| | def __prepare__(mcls, name, bases, **kwargs): |
| | return collections.OrderedDict() |
| |
|
| | @property |
| | def meta(cls): |
| | return cls._meta |
| |
|
| | def __dir__(self): |
| | try: |
| | names = set(dir(type)) |
| | names.update( |
| | ( |
| | "meta", |
| | "pb", |
| | "wrap", |
| | "serialize", |
| | "deserialize", |
| | "to_json", |
| | "from_json", |
| | "to_dict", |
| | "copy_from", |
| | ) |
| | ) |
| | desc = self.pb().DESCRIPTOR |
| | names.update(t.name for t in desc.nested_types) |
| | names.update(e.name for e in desc.enum_types) |
| |
|
| | return names |
| | except AttributeError: |
| | return dir(type) |
| |
|
| | def pb(cls, obj=None, *, coerce: bool = False): |
| | """Return the underlying protobuf Message class or instance. |
| | |
| | Args: |
| | obj: If provided, and an instance of ``cls``, return the |
| | underlying protobuf instance. |
| | coerce (bool): If provided, will attempt to coerce ``obj`` to |
| | ``cls`` if it is not already an instance. |
| | """ |
| | if obj is None: |
| | return cls.meta.pb |
| | if not isinstance(obj, cls): |
| | if coerce: |
| | obj = cls(obj) |
| | else: |
| | raise TypeError( |
| | "%r is not an instance of %s" |
| | % ( |
| | obj, |
| | cls.__name__, |
| | ) |
| | ) |
| | return obj._pb |
| |
|
| | def wrap(cls, pb): |
| | """Return a Message object that shallowly wraps the descriptor. |
| | |
| | Args: |
| | pb: A protocol buffer object, such as would be returned by |
| | :meth:`pb`. |
| | """ |
| | |
| | instance = cls.__new__(cls) |
| | super(cls, instance).__setattr__("_pb", pb) |
| | return instance |
| |
|
| | def serialize(cls, instance) -> bytes: |
| | """Return the serialized proto. |
| | |
| | Args: |
| | instance: An instance of this message type, or something |
| | compatible (accepted by the type's constructor). |
| | |
| | Returns: |
| | bytes: The serialized representation of the protocol buffer. |
| | """ |
| | return cls.pb(instance, coerce=True).SerializeToString() |
| |
|
| | def deserialize(cls, payload: bytes) -> "Message": |
| | """Given a serialized proto, deserialize it into a Message instance. |
| | |
| | Args: |
| | payload (bytes): The serialized proto. |
| | |
| | Returns: |
| | ~.Message: An instance of the message class against which this |
| | method was called. |
| | """ |
| | return cls.wrap(cls.pb().FromString(payload)) |
| |
|
| | def _warn_if_including_default_value_fields_is_used_protobuf_5( |
| | cls, including_default_value_fields: Optional[bool] |
| | ) -> None: |
| | """ |
| | Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated if it is set. |
| | |
| | Args: |
| | including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. |
| | """ |
| | if ( |
| | PROTOBUF_VERSION[0] not in ("3", "4") |
| | and including_default_value_fields is not None |
| | ): |
| | warnings.warn( |
| | """The argument `including_default_value_fields` has been removed from |
| | Protobuf 5.x. Please use `always_print_fields_with_no_presence` instead. |
| | """, |
| | DeprecationWarning, |
| | ) |
| |
|
| | def _raise_if_print_fields_values_are_set_and_differ( |
| | cls, |
| | always_print_fields_with_no_presence: Optional[bool], |
| | including_default_value_fields: Optional[bool], |
| | ) -> None: |
| | """ |
| | Raise Exception if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set |
| | and the values differ. |
| | |
| | Args: |
| | always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user. |
| | including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. |
| | Returns: |
| | None |
| | Raises: |
| | ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and |
| | the values differ. |
| | """ |
| | if ( |
| | always_print_fields_with_no_presence is not None |
| | and including_default_value_fields is not None |
| | and always_print_fields_with_no_presence != including_default_value_fields |
| | ): |
| | raise ValueError( |
| | "Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match" |
| | ) |
| |
|
| | def _normalize_print_fields_without_presence( |
| | cls, |
| | always_print_fields_with_no_presence: Optional[bool], |
| | including_default_value_fields: Optional[bool], |
| | ) -> bool: |
| | """ |
| | Return true if fields with no presence should be included in the results. |
| | By default, fields with no presence will be included in the results |
| | when both `always_print_fields_with_no_presence` and |
| | `including_default_value_fields` are not set |
| | |
| | Args: |
| | always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user. |
| | including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user. |
| | Returns: |
| | None |
| | Raises: |
| | ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and |
| | the values differ. |
| | """ |
| |
|
| | cls._warn_if_including_default_value_fields_is_used_protobuf_5( |
| | including_default_value_fields |
| | ) |
| | cls._raise_if_print_fields_values_are_set_and_differ( |
| | always_print_fields_with_no_presence, including_default_value_fields |
| | ) |
| | |
| | return ( |
| | ( |
| | always_print_fields_with_no_presence is None |
| | and including_default_value_fields is None |
| | ) |
| | or always_print_fields_with_no_presence |
| | or including_default_value_fields |
| | ) |
| |
|
| | def to_json( |
| | cls, |
| | instance, |
| | *, |
| | use_integers_for_enums=True, |
| | including_default_value_fields=None, |
| | preserving_proto_field_name=False, |
| | sort_keys=False, |
| | indent=2, |
| | float_precision=None, |
| | always_print_fields_with_no_presence=None, |
| | ) -> str: |
| | """Given a message instance, serialize it to json |
| | |
| | Args: |
| | instance: An instance of this message type, or something |
| | compatible (accepted by the type's constructor). |
| | use_integers_for_enums (Optional(bool)): An option that determines whether enum |
| | values should be represented by strings (False) or integers (True). |
| | Default is True. |
| | including_default_value_fields (Optional(bool)): Deprecated. Use argument |
| | `always_print_fields_with_no_presence` instead. An option that |
| | determines whether the default field values should be included in the results. |
| | This value must match `always_print_fields_with_no_presence`, |
| | if both arguments are explicitly set. |
| | preserving_proto_field_name (Optional(bool)): An option that |
| | determines whether field name representations preserve |
| | proto case (snake_case) or use lowerCamelCase. Default is False. |
| | sort_keys (Optional(bool)): If True, then the output will be sorted by field names. |
| | Default is False. |
| | indent (Optional(int)): The JSON object will be pretty-printed with this indent level. |
| | An indent level of 0 or negative will only insert newlines. |
| | Pass None for the most compact representation without newlines. |
| | float_precision (Optional(int)): If set, use this to specify float field valid digits. |
| | Default is None. |
| | always_print_fields_with_no_presence (Optional(bool)): If True, fields without |
| | presence (implicit presence scalars, repeated fields, and map fields) will |
| | always be serialized. Any field that supports presence is not affected by |
| | this option (including singular message fields and oneof fields). |
| | This value must match `including_default_value_fields`, |
| | if both arguments are explicitly set. |
| | Returns: |
| | str: The json string representation of the protocol buffer. |
| | """ |
| |
|
| | print_fields = cls._normalize_print_fields_without_presence( |
| | always_print_fields_with_no_presence, including_default_value_fields |
| | ) |
| |
|
| | if PROTOBUF_VERSION[0] in ("3", "4"): |
| | return MessageToJson( |
| | cls.pb(instance), |
| | use_integers_for_enums=use_integers_for_enums, |
| | including_default_value_fields=print_fields, |
| | preserving_proto_field_name=preserving_proto_field_name, |
| | sort_keys=sort_keys, |
| | indent=indent, |
| | float_precision=float_precision, |
| | ) |
| | else: |
| | |
| | |
| | |
| | |
| | |
| | return MessageToJson( |
| | cls.pb(instance), |
| | use_integers_for_enums=use_integers_for_enums, |
| | always_print_fields_with_no_presence=print_fields, |
| | preserving_proto_field_name=preserving_proto_field_name, |
| | sort_keys=sort_keys, |
| | indent=indent, |
| | float_precision=float_precision, |
| | ) |
| |
|
| | def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message": |
| | """Given a json string representing an instance, |
| | parse it into a message. |
| | |
| | Args: |
| | payload: A json string representing a message. |
| | ignore_unknown_fields (Optional(bool)): If True, do not raise errors |
| | for unknown fields. |
| | |
| | Returns: |
| | ~.Message: An instance of the message class against which this |
| | method was called. |
| | """ |
| | instance = cls() |
| | Parse(payload, instance._pb, ignore_unknown_fields=ignore_unknown_fields) |
| | return instance |
| |
|
| | def to_dict( |
| | cls, |
| | instance, |
| | *, |
| | use_integers_for_enums=True, |
| | preserving_proto_field_name=True, |
| | including_default_value_fields=None, |
| | float_precision=None, |
| | always_print_fields_with_no_presence=None, |
| | ) -> "Message": |
| | """Given a message instance, return its representation as a python dict. |
| | |
| | Args: |
| | instance: An instance of this message type, or something |
| | compatible (accepted by the type's constructor). |
| | use_integers_for_enums (Optional(bool)): An option that determines whether enum |
| | values should be represented by strings (False) or integers (True). |
| | Default is True. |
| | preserving_proto_field_name (Optional(bool)): An option that |
| | determines whether field name representations preserve |
| | proto case (snake_case) or use lowerCamelCase. Default is True. |
| | including_default_value_fields (Optional(bool)): Deprecated. Use argument |
| | `always_print_fields_with_no_presence` instead. An option that |
| | determines whether the default field values should be included in the results. |
| | This value must match `always_print_fields_with_no_presence`, |
| | if both arguments are explicitly set. |
| | float_precision (Optional(int)): If set, use this to specify float field valid digits. |
| | Default is None. |
| | always_print_fields_with_no_presence (Optional(bool)): If True, fields without |
| | presence (implicit presence scalars, repeated fields, and map fields) will |
| | always be serialized. Any field that supports presence is not affected by |
| | this option (including singular message fields and oneof fields). This value |
| | must match `including_default_value_fields`, if both arguments are explicitly set. |
| | |
| | Returns: |
| | dict: A representation of the protocol buffer using pythonic data structures. |
| | Messages and map fields are represented as dicts, |
| | repeated fields are represented as lists. |
| | """ |
| |
|
| | print_fields = cls._normalize_print_fields_without_presence( |
| | always_print_fields_with_no_presence, including_default_value_fields |
| | ) |
| |
|
| | if PROTOBUF_VERSION[0] in ("3", "4"): |
| | return MessageToDict( |
| | cls.pb(instance), |
| | including_default_value_fields=print_fields, |
| | preserving_proto_field_name=preserving_proto_field_name, |
| | use_integers_for_enums=use_integers_for_enums, |
| | float_precision=float_precision, |
| | ) |
| | else: |
| | |
| | |
| | |
| | |
| | |
| | return MessageToDict( |
| | cls.pb(instance), |
| | always_print_fields_with_no_presence=print_fields, |
| | preserving_proto_field_name=preserving_proto_field_name, |
| | use_integers_for_enums=use_integers_for_enums, |
| | float_precision=float_precision, |
| | ) |
| |
|
| | def copy_from(cls, instance, other): |
| | """Equivalent for protobuf.Message.CopyFrom |
| | |
| | Args: |
| | instance: An instance of this message type |
| | other: (Union[dict, ~.Message): |
| | A dictionary or message to reinitialize the values for this message. |
| | """ |
| | if isinstance(other, cls): |
| | |
| | other = Message.pb(other) |
| | elif isinstance(other, cls.pb()): |
| | |
| | pass |
| | elif isinstance(other, collections.abc.Mapping): |
| | |
| | other = cls._meta.pb(**other) |
| | else: |
| | raise TypeError( |
| | "invalid argument type to copy to {}: {}".format( |
| | cls.__name__, other.__class__.__name__ |
| | ) |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | cls.pb(instance).CopyFrom(other) |
| |
|
| |
|
| | class Message(metaclass=MessageMeta): |
| | """The abstract base class for a message. |
| | |
| | Args: |
| | mapping (Union[dict, ~.Message]): A dictionary or message to be |
| | used to determine the values for this message. |
| | ignore_unknown_fields (Optional(bool)): If True, do not raise errors for |
| | unknown fields. Only applied if `mapping` is a mapping type or there |
| | are keyword parameters. |
| | kwargs (dict): Keys and values corresponding to the fields of the |
| | message. |
| | """ |
| |
|
| | def __init__( |
| | self, |
| | mapping=None, |
| | *, |
| | ignore_unknown_fields=False, |
| | **kwargs, |
| | ): |
| | |
| | |
| | |
| | |
| | |
| | if mapping is None: |
| | if not kwargs: |
| | |
| | super().__setattr__("_pb", self._meta.pb()) |
| | return |
| |
|
| | mapping = kwargs |
| | elif isinstance(mapping, self._meta.pb): |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | mapping = copy.deepcopy(mapping) |
| | if kwargs: |
| | mapping.MergeFrom(self._meta.pb(**kwargs)) |
| |
|
| | super().__setattr__("_pb", mapping) |
| | return |
| | elif isinstance(mapping, type(self)): |
| | |
| | self.__init__(mapping=mapping._pb, **kwargs) |
| | return |
| | elif isinstance(mapping, collections.abc.Mapping): |
| | |
| | mapping = copy.copy(mapping) |
| | |
| | mapping.update(kwargs) |
| | else: |
| | |
| | raise TypeError( |
| | "Invalid constructor input for %s: %r" |
| | % ( |
| | self.__class__.__name__, |
| | mapping, |
| | ) |
| | ) |
| |
|
| | params = {} |
| | |
| | |
| | marshal = self._meta.marshal |
| | for key, value in mapping.items(): |
| | (key, pb_type) = self._get_pb_type_from_key(key) |
| | if pb_type is None: |
| | if ignore_unknown_fields: |
| | continue |
| |
|
| | raise ValueError( |
| | "Unknown field for {}: {}".format(self.__class__.__name__, key) |
| | ) |
| |
|
| | pb_value = marshal.to_proto(pb_type, value) |
| |
|
| | if pb_value is not None: |
| | params[key] = pb_value |
| |
|
| | |
| | super().__setattr__("_pb", self._meta.pb(**params)) |
| |
|
| | def _get_pb_type_from_key(self, key): |
| | """Given a key, return the corresponding pb_type. |
| | |
| | Args: |
| | key(str): The name of the field. |
| | |
| | Returns: |
| | A tuple containing a key and pb_type. The pb_type will be |
| | the composite type of the field, or the primitive type if a primitive. |
| | If no corresponding field exists, return None. |
| | """ |
| |
|
| | pb_type = None |
| |
|
| | try: |
| | pb_type = self._meta.fields[key].pb_type |
| | except KeyError: |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if f"{key}_" in self._meta.fields: |
| | key = f"{key}_" |
| | pb_type = self._meta.fields[key].pb_type |
| |
|
| | return (key, pb_type) |
| |
|
| | def __dir__(self): |
| | desc = type(self).pb().DESCRIPTOR |
| | names = {f_name for f_name in self._meta.fields.keys()} |
| | names.update(m.name for m in desc.nested_types) |
| | names.update(e.name for e in desc.enum_types) |
| | names.update(dir(object())) |
| | |
| | |
| | names.update( |
| | ( |
| | "__bool__", |
| | "__contains__", |
| | "__dict__", |
| | "__getattr__", |
| | "__getstate__", |
| | "__module__", |
| | "__setstate__", |
| | "__weakref__", |
| | ) |
| | ) |
| |
|
| | return names |
| |
|
| | def __bool__(self): |
| | """Return True if any field is truthy, False otherwise.""" |
| | return any(k in self and getattr(self, k) for k in self._meta.fields.keys()) |
| |
|
| | def __contains__(self, key): |
| | """Return True if this field was set to something non-zero on the wire. |
| | |
| | In most cases, this method will return True when ``__getattr__`` |
| | would return a truthy value and False when it would return a falsy |
| | value, so explicitly calling this is not useful. |
| | |
| | The exception case is empty messages explicitly set on the wire, |
| | which are falsy from ``__getattr__``. This method allows to |
| | distinguish between an explicitly provided empty message and the |
| | absence of that message, which is useful in some edge cases. |
| | |
| | The most common edge case is the use of ``google.protobuf.BoolValue`` |
| | to get a boolean that distinguishes between ``False`` and ``None`` |
| | (or the same for a string, int, etc.). This library transparently |
| | handles that case for you, but this method remains available to |
| | accommodate cases not automatically covered. |
| | |
| | Args: |
| | key (str): The name of the field. |
| | |
| | Returns: |
| | bool: Whether the field's value corresponds to a non-empty |
| | wire serialization. |
| | """ |
| | pb_value = getattr(self._pb, key) |
| | try: |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | return self._pb.HasField(key) |
| | except ValueError: |
| | return bool(pb_value) |
| |
|
| | def __delattr__(self, key): |
| | """Delete the value on the given field. |
| | |
| | This is generally equivalent to setting a falsy value. |
| | """ |
| | self._pb.ClearField(key) |
| |
|
| | def __eq__(self, other): |
| | """Return True if the messages are equal, False otherwise.""" |
| | |
| | if isinstance(other, type(self)): |
| | return self._pb == other._pb |
| |
|
| | |
| | if isinstance(other, self._meta.pb): |
| | return self._pb == other |
| |
|
| | |
| | return NotImplemented |
| |
|
| | def __getattr__(self, key): |
| | """Retrieve the given field's value. |
| | |
| | In protocol buffers, the presence of a field on a message is |
| | sufficient for it to always be "present". |
| | |
| | For primitives, a value of the correct type will always be returned |
| | (the "falsy" values in protocol buffers consistently match those |
| | in Python). For repeated fields, the falsy value is always an empty |
| | sequence. |
| | |
| | For messages, protocol buffers does distinguish between an empty |
| | message and absence, but this distinction is subtle and rarely |
| | relevant. Therefore, this method always returns an empty message |
| | (following the official implementation). To check for message |
| | presence, use ``key in self`` (in other words, ``__contains__``). |
| | |
| | .. note:: |
| | |
| | Some well-known protocol buffer types |
| | (e.g. ``google.protobuf.Timestamp``) will be converted to |
| | their Python equivalents. See the ``marshal`` module for |
| | more details. |
| | """ |
| | (key, pb_type) = self._get_pb_type_from_key(key) |
| | if pb_type is None: |
| | raise AttributeError( |
| | "Unknown field for {}: {}".format(self.__class__.__name__, key) |
| | ) |
| | pb_value = getattr(self._pb, key) |
| | marshal = self._meta.marshal |
| | return marshal.to_python(pb_type, pb_value, absent=key not in self) |
| |
|
| | def __ne__(self, other): |
| | """Return True if the messages are unequal, False otherwise.""" |
| | return not self == other |
| |
|
| | def __repr__(self): |
| | return repr(self._pb) |
| |
|
| | def __setattr__(self, key, value): |
| | """Set the value on the given field. |
| | |
| | For well-known protocol buffer types which are marshalled, either |
| | the protocol buffer object or the Python equivalent is accepted. |
| | """ |
| | if key[0] == "_": |
| | return super().__setattr__(key, value) |
| | marshal = self._meta.marshal |
| | (key, pb_type) = self._get_pb_type_from_key(key) |
| | if pb_type is None: |
| | raise AttributeError( |
| | "Unknown field for {}: {}".format(self.__class__.__name__, key) |
| | ) |
| |
|
| | pb_value = marshal.to_proto(pb_type, value) |
| |
|
| | |
| | |
| | |
| | self._pb.ClearField(key) |
| |
|
| | |
| | if pb_value is not None: |
| | self._pb.MergeFrom(self._meta.pb(**{key: pb_value})) |
| |
|
| | def __getstate__(self): |
| | """Serialize for pickling.""" |
| | return self._pb.SerializeToString() |
| |
|
| | def __setstate__(self, value): |
| | """Deserialization for pickling.""" |
| | new_pb = self._meta.pb().FromString(value) |
| | super().__setattr__("_pb", new_pb) |
| |
|
| |
|
| | class _MessageInfo: |
| | """Metadata about a message. |
| | |
| | Args: |
| | fields (Tuple[~.fields.Field]): The fields declared on the message. |
| | package (str): The proto package. |
| | full_name (str): The full name of the message. |
| | file_info (~._FileInfo): The file descriptor and messages for the |
| | file containing this message. |
| | marshal (~.Marshal): The marshal instance to which this message was |
| | automatically registered. |
| | options (~.descriptor_pb2.MessageOptions): Any options that were |
| | set on the message. |
| | """ |
| |
|
| | def __init__( |
| | self, |
| | *, |
| | fields: List[Field], |
| | package: str, |
| | full_name: str, |
| | marshal: Marshal, |
| | options: descriptor_pb2.MessageOptions, |
| | ) -> None: |
| | self.package = package |
| | self.full_name = full_name |
| | self.options = options |
| | self.fields = collections.OrderedDict((i.name, i) for i in fields) |
| | self.fields_by_number = collections.OrderedDict((i.number, i) for i in fields) |
| | self.marshal = marshal |
| | self._pb = None |
| |
|
| | @property |
| | def pb(self) -> Type[message.Message]: |
| | """Return the protobuf message type for this descriptor. |
| | |
| | If a field on the message references another message which has not |
| | loaded, then this method returns None. |
| | """ |
| | return self._pb |
| |
|
| |
|
| | __all__ = ("Message",) |
| |
|