Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/proto/message.py: 58%
259 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:25 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:25 +0000
1# Copyright 2018 Google LLC
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15import collections
16import collections.abc
17import copy
18import re
19from typing import List, Type
21from google.protobuf import descriptor_pb2
22from google.protobuf import message
23from google.protobuf.json_format import MessageToDict, MessageToJson, Parse
25from proto import _file_info
26from proto import _package_info
27from proto.fields import Field
28from proto.fields import MapField
29from proto.fields import RepeatedField
30from proto.marshal import Marshal
31from proto.primitives import ProtoType
32from proto.utils import has_upb
35_upb = has_upb() # Important to cache result here.
38class MessageMeta(type):
39 """A metaclass for building and registering Message subclasses."""
41 def __new__(mcls, name, bases, attrs):
42 # Do not do any special behavior for Message itself.
43 if not bases:
44 return super().__new__(mcls, name, bases, attrs)
46 # Get the essential information about the proto package, and where
47 # this component belongs within the file.
48 package, marshal = _package_info.compile(name, attrs)
50 # Determine the local path of this proto component within the file.
51 local_path = tuple(attrs.get("__qualname__", name).split("."))
53 # Sanity check: We get the wrong full name if a class is declared
54 # inside a function local scope; correct this.
55 if "<locals>" in local_path:
56 ix = local_path.index("<locals>")
57 local_path = local_path[: ix - 1] + local_path[ix + 1 :]
59 # Determine the full name in protocol buffers.
60 full_name = ".".join((package,) + local_path).lstrip(".")
62 # Special case: Maps. Map fields are special; they are essentially
63 # shorthand for a nested message and a repeated field of that message.
64 # Decompose each map into its constituent form.
65 # https://developers.google.com/protocol-buffers/docs/proto3#maps
66 map_fields = {}
67 for key, field in attrs.items():
68 if not isinstance(field, MapField):
69 continue
71 # Determine the name of the entry message.
72 msg_name = "{pascal_key}Entry".format(
73 pascal_key=re.sub(
74 r"_\w",
75 lambda m: m.group()[1:].upper(),
76 key,
77 ).replace(key[0], key[0].upper(), 1),
78 )
80 # Create the "entry" message (with the key and value fields).
81 #
82 # Note: We instantiate an ordered dictionary here and then
83 # attach key and value in order to ensure that the fields are
84 # iterated in the correct order when the class is created.
85 # This is only an issue in Python 3.5, where the order is
86 # random (and the wrong order causes the pool to refuse to add
87 # the descriptor because reasons).
88 entry_attrs = collections.OrderedDict(
89 {
90 "__module__": attrs.get("__module__", None),
91 "__qualname__": "{prefix}.{name}".format(
92 prefix=attrs.get("__qualname__", name),
93 name=msg_name,
94 ),
95 "_pb_options": {"map_entry": True},
96 }
97 )
98 entry_attrs["key"] = Field(field.map_key_type, number=1)
99 entry_attrs["value"] = Field(
100 field.proto_type,
101 number=2,
102 enum=field.enum,
103 message=field.message,
104 )
105 map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs)
107 # Create the repeated field for the entry message.
108 map_fields[key] = RepeatedField(
109 ProtoType.MESSAGE,
110 number=field.number,
111 message=map_fields[msg_name],
112 )
114 # Add the new entries to the attrs
115 attrs.update(map_fields)
117 # Okay, now we deal with all the rest of the fields.
118 # Iterate over all the attributes and separate the fields into
119 # their own sequence.
120 fields = []
121 new_attrs = {}
122 oneofs = collections.OrderedDict()
123 proto_imports = set()
124 index = 0
125 for key, field in attrs.items():
126 # Sanity check: If this is not a field, do nothing.
127 if not isinstance(field, Field):
128 # The field objects themselves should not be direct attributes.
129 new_attrs[key] = field
130 continue
132 # Add data that the field requires that we do not take in the
133 # constructor because we can derive it from the metaclass.
134 # (The goal is to make the declaration syntax as nice as possible.)
135 field.mcls_data = {
136 "name": key,
137 "parent_name": full_name,
138 "index": index,
139 "package": package,
140 }
142 # Add the field to the list of fields.
143 fields.append(field)
144 # If this field is part of a "oneof", ensure the oneof itself
145 # is represented.
146 if field.oneof:
147 # Keep a running tally of the index of each oneof, and assign
148 # that index to the field's descriptor.
149 oneofs.setdefault(field.oneof, len(oneofs))
150 field.descriptor.oneof_index = oneofs[field.oneof]
152 # If this field references a message, it may be from another
153 # proto file; ensure we know about the import (to faithfully
154 # construct our file descriptor proto).
155 if field.message and not isinstance(field.message, str):
156 field_msg = field.message
157 if hasattr(field_msg, "pb") and callable(field_msg.pb):
158 field_msg = field_msg.pb()
159 # Sanity check: The field's message may not yet be defined if
160 # it was a Message defined in the same file, and the file
161 # descriptor proto has not yet been generated.
162 #
163 # We do nothing in this situation; everything will be handled
164 # correctly when the file descriptor is created later.
165 if field_msg:
166 proto_imports.add(field_msg.DESCRIPTOR.file.name)
168 # Same thing, but for enums.
169 elif field.enum and not isinstance(field.enum, str):
170 field_enum = (
171 field.enum._meta.pb
172 if hasattr(field.enum, "_meta")
173 else field.enum.DESCRIPTOR
174 )
176 if field_enum:
177 proto_imports.add(field_enum.file.name)
179 # Increment the field index counter.
180 index += 1
182 # As per descriptor.proto, all synthetic oneofs must be ordered after
183 # 'real' oneofs.
184 opt_attrs = {}
185 for field in fields:
186 if field.optional:
187 field.oneof = "_{}".format(field.name)
188 field.descriptor.oneof_index = oneofs[field.oneof] = len(oneofs)
189 opt_attrs[field.name] = field.name
191 # Generating a metaclass dynamically provides class attributes that
192 # instances can't see. This provides idiomatically named constants
193 # that enable the following pattern to check for field presence:
194 #
195 # class MyMessage(proto.Message):
196 # field = proto.Field(proto.INT32, number=1, optional=True)
197 #
198 # m = MyMessage()
199 # MyMessage.field in m
200 if opt_attrs:
201 mcls = type("AttrsMeta", (mcls,), opt_attrs)
203 # Determine the filename.
204 # We determine an appropriate proto filename based on the
205 # Python module.
206 filename = _file_info._FileInfo.proto_file_name(
207 new_attrs.get("__module__", name.lower())
208 )
210 # Get or create the information about the file, including the
211 # descriptor to which the new message descriptor shall be added.
212 file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package)
214 # Ensure any imports that would be necessary are assigned to the file
215 # descriptor proto being created.
216 for proto_import in proto_imports:
217 if proto_import not in file_info.descriptor.dependency:
218 file_info.descriptor.dependency.append(proto_import)
220 # Retrieve any message options.
221 opts = descriptor_pb2.MessageOptions(**new_attrs.pop("_pb_options", {}))
223 # Create the underlying proto descriptor.
224 desc = descriptor_pb2.DescriptorProto(
225 name=name,
226 field=[i.descriptor for i in fields],
227 oneof_decl=[
228 descriptor_pb2.OneofDescriptorProto(name=i) for i in oneofs.keys()
229 ],
230 options=opts,
231 )
233 # If any descriptors were nested under this one, they need to be
234 # attached as nested types here.
235 child_paths = [p for p in file_info.nested.keys() if local_path == p[:-1]]
236 for child_path in child_paths:
237 desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path))
239 # Same thing, but for enums
240 child_paths = [p for p in file_info.nested_enum.keys() if local_path == p[:-1]]
241 for child_path in child_paths:
242 desc.enum_type.add().MergeFrom(file_info.nested_enum.pop(child_path))
244 # Add the descriptor to the file if it is a top-level descriptor,
245 # or to a "holding area" for nested messages otherwise.
246 if len(local_path) == 1:
247 file_info.descriptor.message_type.add().MergeFrom(desc)
248 else:
249 file_info.nested[local_path] = desc
251 # Create the MessageInfo instance to be attached to this message.
252 new_attrs["_meta"] = _MessageInfo(
253 fields=fields,
254 full_name=full_name,
255 marshal=marshal,
256 options=opts,
257 package=package,
258 )
260 # Run the superclass constructor.
261 cls = super().__new__(mcls, name, bases, new_attrs)
263 # The info class and fields need a reference to the class just created.
264 cls._meta.parent = cls
265 for field in cls._meta.fields.values():
266 field.parent = cls
268 # Add this message to the _FileInfo instance; this allows us to
269 # associate the descriptor with the message once the descriptor
270 # is generated.
271 file_info.messages[full_name] = cls
273 # Generate the descriptor for the file if it is ready.
274 if file_info.ready(new_class=cls):
275 file_info.generate_file_pb(new_class=cls, fallback_salt=full_name)
277 # Done; return the class.
278 return cls
280 @classmethod
281 def __prepare__(mcls, name, bases, **kwargs):
282 return collections.OrderedDict()
284 @property
285 def meta(cls):
286 return cls._meta
288 def __dir__(self):
289 try:
290 names = set(dir(type))
291 names.update(
292 (
293 "meta",
294 "pb",
295 "wrap",
296 "serialize",
297 "deserialize",
298 "to_json",
299 "from_json",
300 "to_dict",
301 "copy_from",
302 )
303 )
304 desc = self.pb().DESCRIPTOR
305 names.update(t.name for t in desc.nested_types)
306 names.update(e.name for e in desc.enum_types)
308 return names
309 except AttributeError:
310 return dir(type)
312 def pb(cls, obj=None, *, coerce: bool = False):
313 """Return the underlying protobuf Message class or instance.
315 Args:
316 obj: If provided, and an instance of ``cls``, return the
317 underlying protobuf instance.
318 coerce (bool): If provided, will attempt to coerce ``obj`` to
319 ``cls`` if it is not already an instance.
320 """
321 if obj is None:
322 return cls.meta.pb
323 if not isinstance(obj, cls):
324 if coerce:
325 obj = cls(obj)
326 else:
327 raise TypeError(
328 "%r is not an instance of %s"
329 % (
330 obj,
331 cls.__name__,
332 )
333 )
334 return obj._pb
336 def wrap(cls, pb):
337 """Return a Message object that shallowly wraps the descriptor.
339 Args:
340 pb: A protocol buffer object, such as would be returned by
341 :meth:`pb`.
342 """
343 # Optimized fast path.
344 instance = cls.__new__(cls)
345 super(cls, instance).__setattr__("_pb", pb)
346 return instance
348 def serialize(cls, instance) -> bytes:
349 """Return the serialized proto.
351 Args:
352 instance: An instance of this message type, or something
353 compatible (accepted by the type's constructor).
355 Returns:
356 bytes: The serialized representation of the protocol buffer.
357 """
358 return cls.pb(instance, coerce=True).SerializeToString()
360 def deserialize(cls, payload: bytes) -> "Message":
361 """Given a serialized proto, deserialize it into a Message instance.
363 Args:
364 payload (bytes): The serialized proto.
366 Returns:
367 ~.Message: An instance of the message class against which this
368 method was called.
369 """
370 return cls.wrap(cls.pb().FromString(payload))
372 def to_json(
373 cls,
374 instance,
375 *,
376 use_integers_for_enums=True,
377 including_default_value_fields=True,
378 preserving_proto_field_name=False,
379 indent=2,
380 ) -> str:
381 """Given a message instance, serialize it to json
383 Args:
384 instance: An instance of this message type, or something
385 compatible (accepted by the type's constructor).
386 use_integers_for_enums (Optional(bool)): An option that determines whether enum
387 values should be represented by strings (False) or integers (True).
388 Default is True.
389 preserving_proto_field_name (Optional(bool)): An option that
390 determines whether field name representations preserve
391 proto case (snake_case) or use lowerCamelCase. Default is False.
392 indent: The JSON object will be pretty-printed with this indent level.
393 An indent level of 0 or negative will only insert newlines.
394 Pass None for the most compact representation without newlines.
396 Returns:
397 str: The json string representation of the protocol buffer.
398 """
399 return MessageToJson(
400 cls.pb(instance),
401 use_integers_for_enums=use_integers_for_enums,
402 including_default_value_fields=including_default_value_fields,
403 preserving_proto_field_name=preserving_proto_field_name,
404 indent=indent,
405 )
407 def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message":
408 """Given a json string representing an instance,
409 parse it into a message.
411 Args:
412 paylod: A json string representing a message.
413 ignore_unknown_fields (Optional(bool)): If True, do not raise errors
414 for unknown fields.
416 Returns:
417 ~.Message: An instance of the message class against which this
418 method was called.
419 """
420 instance = cls()
421 Parse(payload, instance._pb, ignore_unknown_fields=ignore_unknown_fields)
422 return instance
424 def to_dict(
425 cls,
426 instance,
427 *,
428 use_integers_for_enums=True,
429 preserving_proto_field_name=True,
430 including_default_value_fields=True,
431 ) -> "Message":
432 """Given a message instance, return its representation as a python dict.
434 Args:
435 instance: An instance of this message type, or something
436 compatible (accepted by the type's constructor).
437 use_integers_for_enums (Optional(bool)): An option that determines whether enum
438 values should be represented by strings (False) or integers (True).
439 Default is True.
440 preserving_proto_field_name (Optional(bool)): An option that
441 determines whether field name representations preserve
442 proto case (snake_case) or use lowerCamelCase. Default is True.
443 including_default_value_fields (Optional(bool)): An option that
444 determines whether the default field values should be included in the results.
445 Default is True.
447 Returns:
448 dict: A representation of the protocol buffer using pythonic data structures.
449 Messages and map fields are represented as dicts,
450 repeated fields are represented as lists.
451 """
452 return MessageToDict(
453 cls.pb(instance),
454 including_default_value_fields=including_default_value_fields,
455 preserving_proto_field_name=preserving_proto_field_name,
456 use_integers_for_enums=use_integers_for_enums,
457 )
459 def copy_from(cls, instance, other):
460 """Equivalent for protobuf.Message.CopyFrom
462 Args:
463 instance: An instance of this message type
464 other: (Union[dict, ~.Message):
465 A dictionary or message to reinitialize the values for this message.
466 """
467 if isinstance(other, cls):
468 # Just want the underlying proto.
469 other = Message.pb(other)
470 elif isinstance(other, cls.pb()):
471 # Don't need to do anything.
472 pass
473 elif isinstance(other, collections.abc.Mapping):
474 # Coerce into a proto
475 other = cls._meta.pb(**other)
476 else:
477 raise TypeError(
478 "invalid argument type to copy to {}: {}".format(
479 cls.__name__, other.__class__.__name__
480 )
481 )
483 # Note: we can't just run self.__init__ because this may be a message field
484 # for a higher order proto; the memory layout for protos is NOT LIKE the
485 # python memory model. We cannot rely on just setting things by reference.
486 # Non-trivial complexity is (partially) hidden by the protobuf runtime.
487 cls.pb(instance).CopyFrom(other)
490class Message(metaclass=MessageMeta):
491 """The abstract base class for a message.
493 Args:
494 mapping (Union[dict, ~.Message]): A dictionary or message to be
495 used to determine the values for this message.
496 ignore_unknown_fields (Optional(bool)): If True, do not raise errors for
497 unknown fields. Only applied if `mapping` is a mapping type or there
498 are keyword parameters.
499 kwargs (dict): Keys and values corresponding to the fields of the
500 message.
501 """
503 def __init__(
504 self,
505 mapping=None,
506 *,
507 ignore_unknown_fields=False,
508 **kwargs,
509 ):
510 # We accept several things for `mapping`:
511 # * An instance of this class.
512 # * An instance of the underlying protobuf descriptor class.
513 # * A dict
514 # * Nothing (keyword arguments only).
515 if mapping is None:
516 if not kwargs:
517 # Special fast path for empty construction.
518 super().__setattr__("_pb", self._meta.pb())
519 return
521 mapping = kwargs
522 elif isinstance(mapping, self._meta.pb):
523 # Make a copy of the mapping.
524 # This is a constructor for a new object, so users will assume
525 # that it will not have side effects on the arguments being
526 # passed in.
527 #
528 # The `wrap` method on the metaclass is the public API for taking
529 # ownership of the passed in protobuf object.
530 mapping = copy.deepcopy(mapping)
531 if kwargs:
532 mapping.MergeFrom(self._meta.pb(**kwargs))
534 super().__setattr__("_pb", mapping)
535 return
536 elif isinstance(mapping, type(self)):
537 # Just use the above logic on mapping's underlying pb.
538 self.__init__(mapping=mapping._pb, **kwargs)
539 return
540 elif isinstance(mapping, collections.abc.Mapping):
541 # Can't have side effects on mapping.
542 mapping = copy.copy(mapping)
543 # kwargs entries take priority for duplicate keys.
544 mapping.update(kwargs)
545 else:
546 # Sanity check: Did we get something not a map? Error if so.
547 raise TypeError(
548 "Invalid constructor input for %s: %r"
549 % (
550 self.__class__.__name__,
551 mapping,
552 )
553 )
555 params = {}
556 # Update the mapping to address any values that need to be
557 # coerced.
558 marshal = self._meta.marshal
559 for key, value in mapping.items():
560 (key, pb_type) = self._get_pb_type_from_key(key)
561 if pb_type is None:
562 if ignore_unknown_fields:
563 continue
565 raise ValueError(
566 "Unknown field for {}: {}".format(self.__class__.__name__, key)
567 )
569 try:
570 pb_value = marshal.to_proto(pb_type, value)
571 except ValueError:
572 # Underscores may be appended to field names
573 # that collide with python or proto-plus keywords.
574 # In case a key only exists with a `_` suffix, coerce the key
575 # to include the `_` suffix. It's not possible to
576 # natively define the same field with a trailing underscore in protobuf.
577 # See related issue
578 # https://github.com/googleapis/python-api-core/issues/227
579 if isinstance(value, dict):
580 if _upb:
581 # In UPB, pb_type is MessageMeta which doesn't expose attrs like it used to in Python/CPP.
582 keys_to_update = [
583 item
584 for item in value
585 if item not in pb_type.DESCRIPTOR.fields_by_name
586 and f"{item}_" in pb_type.DESCRIPTOR.fields_by_name
587 ]
588 else:
589 keys_to_update = [
590 item
591 for item in value
592 if not hasattr(pb_type, item)
593 and hasattr(pb_type, f"{item}_")
594 ]
595 for item in keys_to_update:
596 value[f"{item}_"] = value.pop(item)
598 pb_value = marshal.to_proto(pb_type, value)
600 if pb_value is not None:
601 params[key] = pb_value
603 # Create the internal protocol buffer.
604 super().__setattr__("_pb", self._meta.pb(**params))
606 def _get_pb_type_from_key(self, key):
607 """Given a key, return the corresponding pb_type.
609 Args:
610 key(str): The name of the field.
612 Returns:
613 A tuple containing a key and pb_type. The pb_type will be
614 the composite type of the field, or the primitive type if a primitive.
615 If no corresponding field exists, return None.
616 """
618 pb_type = None
620 try:
621 pb_type = self._meta.fields[key].pb_type
622 except KeyError:
623 # Underscores may be appended to field names
624 # that collide with python or proto-plus keywords.
625 # In case a key only exists with a `_` suffix, coerce the key
626 # to include the `_` suffix. It's not possible to
627 # natively define the same field with a trailing underscore in protobuf.
628 # See related issue
629 # https://github.com/googleapis/python-api-core/issues/227
630 if f"{key}_" in self._meta.fields:
631 key = f"{key}_"
632 pb_type = self._meta.fields[key].pb_type
634 return (key, pb_type)
636 def __dir__(self):
637 desc = type(self).pb().DESCRIPTOR
638 names = {f_name for f_name in self._meta.fields.keys()}
639 names.update(m.name for m in desc.nested_types)
640 names.update(e.name for e in desc.enum_types)
641 names.update(dir(object()))
642 # Can't think of a better way of determining
643 # the special methods than manually listing them.
644 names.update(
645 (
646 "__bool__",
647 "__contains__",
648 "__dict__",
649 "__getattr__",
650 "__getstate__",
651 "__module__",
652 "__setstate__",
653 "__weakref__",
654 )
655 )
657 return names
659 def __bool__(self):
660 """Return True if any field is truthy, False otherwise."""
661 return any(k in self and getattr(self, k) for k in self._meta.fields.keys())
663 def __contains__(self, key):
664 """Return True if this field was set to something non-zero on the wire.
666 In most cases, this method will return True when ``__getattr__``
667 would return a truthy value and False when it would return a falsy
668 value, so explicitly calling this is not useful.
670 The exception case is empty messages explicitly set on the wire,
671 which are falsy from ``__getattr__``. This method allows to
672 distinguish between an explicitly provided empty message and the
673 absence of that message, which is useful in some edge cases.
675 The most common edge case is the use of ``google.protobuf.BoolValue``
676 to get a boolean that distinguishes between ``False`` and ``None``
677 (or the same for a string, int, etc.). This library transparently
678 handles that case for you, but this method remains available to
679 accommodate cases not automatically covered.
681 Args:
682 key (str): The name of the field.
684 Returns:
685 bool: Whether the field's value corresponds to a non-empty
686 wire serialization.
687 """
688 pb_value = getattr(self._pb, key)
689 try:
690 # Protocol buffers "HasField" is unfriendly; it only works
691 # against composite, non-repeated fields, and raises ValueError
692 # against any repeated field or primitive.
693 #
694 # There is no good way to test whether it is valid to provide
695 # a field to this method, so sadly we are stuck with a
696 # somewhat inefficient try/except.
697 return self._pb.HasField(key)
698 except ValueError:
699 return bool(pb_value)
701 def __delattr__(self, key):
702 """Delete the value on the given field.
704 This is generally equivalent to setting a falsy value.
705 """
706 self._pb.ClearField(key)
708 def __eq__(self, other):
709 """Return True if the messages are equal, False otherwise."""
710 # If these are the same type, use internal protobuf's equality check.
711 if isinstance(other, type(self)):
712 return self._pb == other._pb
714 # If the other type is the target protobuf object, honor that also.
715 if isinstance(other, self._meta.pb):
716 return self._pb == other
718 # Ask the other object.
719 return NotImplemented
721 def __getattr__(self, key):
722 """Retrieve the given field's value.
724 In protocol buffers, the presence of a field on a message is
725 sufficient for it to always be "present".
727 For primitives, a value of the correct type will always be returned
728 (the "falsy" values in protocol buffers consistently match those
729 in Python). For repeated fields, the falsy value is always an empty
730 sequence.
732 For messages, protocol buffers does distinguish between an empty
733 message and absence, but this distinction is subtle and rarely
734 relevant. Therefore, this method always returns an empty message
735 (following the official implementation). To check for message
736 presence, use ``key in self`` (in other words, ``__contains__``).
738 .. note::
740 Some well-known protocol buffer types
741 (e.g. ``google.protobuf.Timestamp``) will be converted to
742 their Python equivalents. See the ``marshal`` module for
743 more details.
744 """
745 (key, pb_type) = self._get_pb_type_from_key(key)
746 if pb_type is None:
747 raise AttributeError(
748 "Unknown field for {}: {}".format(self.__class__.__name__, key)
749 )
750 pb_value = getattr(self._pb, key)
751 marshal = self._meta.marshal
752 return marshal.to_python(pb_type, pb_value, absent=key not in self)
754 def __ne__(self, other):
755 """Return True if the messages are unequal, False otherwise."""
756 return not self == other
758 def __repr__(self):
759 return repr(self._pb)
761 def __setattr__(self, key, value):
762 """Set the value on the given field.
764 For well-known protocol buffer types which are marshalled, either
765 the protocol buffer object or the Python equivalent is accepted.
766 """
767 if key[0] == "_":
768 return super().__setattr__(key, value)
769 marshal = self._meta.marshal
770 (key, pb_type) = self._get_pb_type_from_key(key)
771 if pb_type is None:
772 raise AttributeError(
773 "Unknown field for {}: {}".format(self.__class__.__name__, key)
774 )
776 pb_value = marshal.to_proto(pb_type, value)
778 # Clear the existing field.
779 # This is the only way to successfully write nested falsy values,
780 # because otherwise MergeFrom will no-op on them.
781 self._pb.ClearField(key)
783 # Merge in the value being set.
784 if pb_value is not None:
785 self._pb.MergeFrom(self._meta.pb(**{key: pb_value}))
787 def __getstate__(self):
788 """Serialize for pickling."""
789 return self._pb.SerializeToString()
791 def __setstate__(self, value):
792 """Deserialization for pickling."""
793 new_pb = self._meta.pb().FromString(value)
794 super().__setattr__("_pb", new_pb)
797class _MessageInfo:
798 """Metadata about a message.
800 Args:
801 fields (Tuple[~.fields.Field]): The fields declared on the message.
802 package (str): The proto package.
803 full_name (str): The full name of the message.
804 file_info (~._FileInfo): The file descriptor and messages for the
805 file containing this message.
806 marshal (~.Marshal): The marshal instance to which this message was
807 automatically registered.
808 options (~.descriptor_pb2.MessageOptions): Any options that were
809 set on the message.
810 """
812 def __init__(
813 self,
814 *,
815 fields: List[Field],
816 package: str,
817 full_name: str,
818 marshal: Marshal,
819 options: descriptor_pb2.MessageOptions,
820 ) -> None:
821 self.package = package
822 self.full_name = full_name
823 self.options = options
824 self.fields = collections.OrderedDict((i.name, i) for i in fields)
825 self.fields_by_number = collections.OrderedDict((i.number, i) for i in fields)
826 self.marshal = marshal
827 self._pb = None
829 @property
830 def pb(self) -> Type[message.Message]:
831 """Return the protobuf message type for this descriptor.
833 If a field on the message references another message which has not
834 loaded, then this method returns None.
835 """
836 return self._pb
839__all__ = ("Message",)