Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/proto/message.py: 44%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 Any, Dict, List, Optional, Type
20import warnings
22import google.protobuf
23from google.protobuf import descriptor_pb2
24from google.protobuf import message
25from google.protobuf.json_format import MessageToDict, MessageToJson, Parse
27from proto import _file_info
28from proto import _package_info
29from proto.fields import Field
30from proto.fields import MapField
31from proto.fields import RepeatedField
32from proto.marshal import Marshal
33from proto.primitives import ProtoType
34from proto.utils import has_upb
37PROTOBUF_VERSION = google.protobuf.__version__
39# extract the major version code
40_PROTOBUF_MAJOR_VERSION = PROTOBUF_VERSION.partition(".")[0]
42_upb = has_upb() # Important to cache result here.
45class MessageMeta(type):
46 """A metaclass for building and registering Message subclasses."""
48 def __new__(mcls, name, bases, attrs):
49 # Do not do any special behavior for Message itself.
50 if not bases:
51 return super().__new__(mcls, name, bases, attrs)
53 # Get the essential information about the proto package, and where
54 # this component belongs within the file.
55 package, marshal = _package_info.compile(name, attrs)
57 # Determine the local path of this proto component within the file.
58 local_path = tuple(attrs.get("__qualname__", name).split("."))
60 # Sanity check: We get the wrong full name if a class is declared
61 # inside a function local scope; correct this.
62 if "<locals>" in local_path:
63 ix = local_path.index("<locals>")
64 local_path = local_path[: ix - 1] + local_path[ix + 1 :]
66 # Determine the full name in protocol buffers.
67 full_name = ".".join((package,) + local_path).lstrip(".")
69 # Special case: Maps. Map fields are special; they are essentially
70 # shorthand for a nested message and a repeated field of that message.
71 # Decompose each map into its constituent form.
72 # https://developers.google.com/protocol-buffers/docs/proto3#maps
73 map_fields = {}
74 for key, field in attrs.items():
75 if not isinstance(field, MapField):
76 continue
78 # Determine the name of the entry message.
79 msg_name = "{pascal_key}Entry".format(
80 pascal_key=re.sub(
81 r"_\w",
82 lambda m: m.group()[1:].upper(),
83 key,
84 ).replace(key[0], key[0].upper(), 1),
85 )
87 # Create the "entry" message (with the key and value fields).
88 #
89 # Note: We instantiate an ordered dictionary here and then
90 # attach key and value in order to ensure that the fields are
91 # iterated in the correct order when the class is created.
92 # This is only an issue in Python 3.5, where the order is
93 # random (and the wrong order causes the pool to refuse to add
94 # the descriptor because reasons).
95 entry_attrs = collections.OrderedDict(
96 {
97 "__module__": attrs.get("__module__", None),
98 "__qualname__": "{prefix}.{name}".format(
99 prefix=attrs.get("__qualname__", name),
100 name=msg_name,
101 ),
102 "_pb_options": {"map_entry": True},
103 }
104 )
105 entry_attrs["key"] = Field(field.map_key_type, number=1)
106 entry_attrs["value"] = Field(
107 field.proto_type,
108 number=2,
109 enum=field.enum,
110 message=field.message,
111 )
112 map_fields[msg_name] = MessageMeta(msg_name, (Message,), entry_attrs)
114 # Create the repeated field for the entry message.
115 map_fields[key] = RepeatedField(
116 ProtoType.MESSAGE,
117 number=field.number,
118 message=map_fields[msg_name],
119 )
121 # Add the new entries to the attrs
122 attrs.update(map_fields)
124 # Okay, now we deal with all the rest of the fields.
125 # Iterate over all the attributes and separate the fields into
126 # their own sequence.
127 fields = []
128 new_attrs = {}
129 oneofs = collections.OrderedDict()
130 proto_imports = set()
131 index = 0
132 for key, field in attrs.items():
133 # Sanity check: If this is not a field, do nothing.
134 if not isinstance(field, Field):
135 # The field objects themselves should not be direct attributes.
136 new_attrs[key] = field
137 continue
139 # Add data that the field requires that we do not take in the
140 # constructor because we can derive it from the metaclass.
141 # (The goal is to make the declaration syntax as nice as possible.)
142 field.mcls_data = {
143 "name": key,
144 "parent_name": full_name,
145 "index": index,
146 "package": package,
147 }
149 # Add the field to the list of fields.
150 fields.append(field)
151 # If this field is part of a "oneof", ensure the oneof itself
152 # is represented.
153 if field.oneof:
154 # Keep a running tally of the index of each oneof, and assign
155 # that index to the field's descriptor.
156 oneofs.setdefault(field.oneof, len(oneofs))
157 field.descriptor.oneof_index = oneofs[field.oneof]
159 # If this field references a message, it may be from another
160 # proto file; ensure we know about the import (to faithfully
161 # construct our file descriptor proto).
162 if field.message and not isinstance(field.message, str):
163 field_msg = field.message
164 if hasattr(field_msg, "pb") and callable(field_msg.pb):
165 field_msg = field_msg.pb()
166 # Sanity check: The field's message may not yet be defined if
167 # it was a Message defined in the same file, and the file
168 # descriptor proto has not yet been generated.
169 #
170 # We do nothing in this situation; everything will be handled
171 # correctly when the file descriptor is created later.
172 if field_msg:
173 proto_imports.add(field_msg.DESCRIPTOR.file.name)
175 # Same thing, but for enums.
176 elif field.enum and not isinstance(field.enum, str):
177 field_enum = (
178 field.enum._meta.pb
179 if hasattr(field.enum, "_meta")
180 else field.enum.DESCRIPTOR
181 )
183 if field_enum:
184 proto_imports.add(field_enum.file.name)
186 # Increment the field index counter.
187 index += 1
189 # As per descriptor.proto, all synthetic oneofs must be ordered after
190 # 'real' oneofs.
191 opt_attrs = {}
192 for field in fields:
193 if field.optional:
194 field.oneof = "_{}".format(field.name)
195 field.descriptor.oneof_index = oneofs[field.oneof] = len(oneofs)
196 opt_attrs[field.name] = field.name
198 # Generating a metaclass dynamically provides class attributes that
199 # instances can't see. This provides idiomatically named constants
200 # that enable the following pattern to check for field presence:
201 #
202 # class MyMessage(proto.Message):
203 # field = proto.Field(proto.INT32, number=1, optional=True)
204 #
205 # m = MyMessage()
206 # MyMessage.field in m
207 if opt_attrs:
208 mcls = type("AttrsMeta", (mcls,), opt_attrs)
210 # Determine the filename.
211 # We determine an appropriate proto filename based on the
212 # Python module.
213 filename = _file_info._FileInfo.proto_file_name(
214 new_attrs.get("__module__", name.lower())
215 )
217 # Get or create the information about the file, including the
218 # descriptor to which the new message descriptor shall be added.
219 file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package)
221 # Ensure any imports that would be necessary are assigned to the file
222 # descriptor proto being created.
223 for proto_import in proto_imports:
224 if proto_import not in file_info.descriptor.dependency:
225 file_info.descriptor.dependency.append(proto_import)
227 # Retrieve any message options.
228 opts = descriptor_pb2.MessageOptions(**new_attrs.pop("_pb_options", {}))
230 # Create the underlying proto descriptor.
231 desc = descriptor_pb2.DescriptorProto(
232 name=name,
233 field=[i.descriptor for i in fields],
234 oneof_decl=[
235 descriptor_pb2.OneofDescriptorProto(name=i) for i in oneofs.keys()
236 ],
237 options=opts,
238 )
240 # If any descriptors were nested under this one, they need to be
241 # attached as nested types here.
242 child_paths = [p for p in file_info.nested.keys() if local_path == p[:-1]]
243 for child_path in child_paths:
244 desc.nested_type.add().MergeFrom(file_info.nested.pop(child_path))
246 # Same thing, but for enums
247 child_paths = [p for p in file_info.nested_enum.keys() if local_path == p[:-1]]
248 for child_path in child_paths:
249 desc.enum_type.add().MergeFrom(file_info.nested_enum.pop(child_path))
251 # Add the descriptor to the file if it is a top-level descriptor,
252 # or to a "holding area" for nested messages otherwise.
253 if len(local_path) == 1:
254 file_info.descriptor.message_type.add().MergeFrom(desc)
255 else:
256 file_info.nested[local_path] = desc
258 # Create the MessageInfo instance to be attached to this message.
259 new_attrs["_meta"] = _MessageInfo(
260 fields=fields,
261 full_name=full_name,
262 marshal=marshal,
263 options=opts,
264 package=package,
265 )
267 # Run the superclass constructor.
268 cls = super().__new__(mcls, name, bases, new_attrs)
270 # The info class and fields need a reference to the class just created.
271 cls._meta.parent = cls
272 for field in cls._meta.fields.values():
273 field.parent = cls
275 # Add this message to the _FileInfo instance; this allows us to
276 # associate the descriptor with the message once the descriptor
277 # is generated.
278 file_info.messages[full_name] = cls
280 # Generate the descriptor for the file if it is ready.
281 if file_info.ready(new_class=cls):
282 file_info.generate_file_pb(new_class=cls, fallback_salt=full_name)
284 # Done; return the class.
285 return cls
287 @classmethod
288 def __prepare__(mcls, name, bases, **kwargs):
289 return collections.OrderedDict()
291 @property
292 def meta(cls):
293 return cls._meta
295 def __dir__(self):
296 try:
297 names = set(dir(type))
298 names.update(
299 (
300 "meta",
301 "pb",
302 "wrap",
303 "serialize",
304 "deserialize",
305 "to_json",
306 "from_json",
307 "to_dict",
308 "copy_from",
309 )
310 )
311 desc = self.pb().DESCRIPTOR
312 names.update(t.name for t in desc.nested_types)
313 names.update(e.name for e in desc.enum_types)
315 return names
316 except AttributeError:
317 return dir(type)
319 def pb(cls, obj=None, *, coerce: bool = False):
320 """Return the underlying protobuf Message class or instance.
322 Args:
323 obj: If provided, and an instance of ``cls``, return the
324 underlying protobuf instance.
325 coerce (bool): If provided, will attempt to coerce ``obj`` to
326 ``cls`` if it is not already an instance.
327 """
328 if obj is None:
329 return cls.meta.pb
330 if not isinstance(obj, cls):
331 if coerce:
332 obj = cls(obj)
333 else:
334 raise TypeError(
335 "%r is not an instance of %s"
336 % (
337 obj,
338 cls.__name__,
339 )
340 )
341 return obj._pb
343 def wrap(cls, pb):
344 """Return a Message object that shallowly wraps the descriptor.
346 Args:
347 pb: A protocol buffer object, such as would be returned by
348 :meth:`pb`.
349 """
350 # Optimized fast path.
351 instance = cls.__new__(cls)
352 super(cls, instance).__setattr__("_pb", pb)
353 return instance
355 def serialize(cls, instance) -> bytes:
356 """Return the serialized proto.
358 Args:
359 instance: An instance of this message type, or something
360 compatible (accepted by the type's constructor).
362 Returns:
363 bytes: The serialized representation of the protocol buffer.
364 """
365 return cls.pb(instance, coerce=True).SerializeToString()
367 def deserialize(cls, payload: bytes) -> "Message":
368 """Given a serialized proto, deserialize it into a Message instance.
370 Args:
371 payload (bytes): The serialized proto.
373 Returns:
374 ~.Message: An instance of the message class against which this
375 method was called.
376 """
377 return cls.wrap(cls.pb().FromString(payload))
379 def _warn_if_including_default_value_fields_is_used_protobuf_5(
380 cls, including_default_value_fields: Optional[bool]
381 ) -> None:
382 """
383 Warn Protobuf 5.x+ users that `including_default_value_fields` is deprecated if it is set.
385 Args:
386 including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user.
387 """
388 if (
389 _PROTOBUF_MAJOR_VERSION not in ("3", "4")
390 and including_default_value_fields is not None
391 ):
392 warnings.warn(
393 """The argument `including_default_value_fields` has been removed from
394 Protobuf 5.x. Please use `always_print_fields_with_no_presence` instead.
395 """,
396 DeprecationWarning,
397 )
399 def _raise_if_print_fields_values_are_set_and_differ(
400 cls,
401 always_print_fields_with_no_presence: Optional[bool],
402 including_default_value_fields: Optional[bool],
403 ) -> None:
404 """
405 Raise Exception if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set
406 and the values differ.
408 Args:
409 always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user.
410 including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user.
411 Returns:
412 None
413 Raises:
414 ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and
415 the values differ.
416 """
417 if (
418 always_print_fields_with_no_presence is not None
419 and including_default_value_fields is not None
420 and always_print_fields_with_no_presence != including_default_value_fields
421 ):
422 raise ValueError(
423 "Arguments `always_print_fields_with_no_presence` and `including_default_value_fields` must match"
424 )
426 def _normalize_print_fields_without_presence(
427 cls,
428 always_print_fields_with_no_presence: Optional[bool],
429 including_default_value_fields: Optional[bool],
430 ) -> bool:
431 """
432 Return true if fields with no presence should be included in the results.
433 By default, fields with no presence will be included in the results
434 when both `always_print_fields_with_no_presence` and
435 `including_default_value_fields` are not set
437 Args:
438 always_print_fields_with_no_presence (Optional(bool)): The value of `always_print_fields_with_no_presence` set by the user.
439 including_default_value_fields (Optional(bool)): The value of `including_default_value_fields` set by the user.
440 Returns:
441 None
442 Raises:
443 ValueError: if both `always_print_fields_with_no_presence` and `including_default_value_fields` are set and
444 the values differ.
445 """
447 cls._warn_if_including_default_value_fields_is_used_protobuf_5(
448 including_default_value_fields
449 )
450 cls._raise_if_print_fields_values_are_set_and_differ(
451 always_print_fields_with_no_presence, including_default_value_fields
452 )
453 # Default to True if neither `always_print_fields_with_no_presence` or `including_default_value_fields` is set
454 return (
455 (
456 always_print_fields_with_no_presence is None
457 and including_default_value_fields is None
458 )
459 or always_print_fields_with_no_presence
460 or including_default_value_fields
461 )
463 def to_json(
464 cls,
465 instance,
466 *,
467 use_integers_for_enums=True,
468 including_default_value_fields=None,
469 preserving_proto_field_name=False,
470 sort_keys=False,
471 indent=2,
472 float_precision=None,
473 always_print_fields_with_no_presence=None,
474 ) -> str:
475 """Given a message instance, serialize it to json
477 Args:
478 instance: An instance of this message type, or something
479 compatible (accepted by the type's constructor).
480 use_integers_for_enums (Optional(bool)): An option that determines whether enum
481 values should be represented by strings (False) or integers (True).
482 Default is True.
483 including_default_value_fields (Optional(bool)): Deprecated. Use argument
484 `always_print_fields_with_no_presence` instead. An option that
485 determines whether the default field values should be included in the results.
486 This value must match `always_print_fields_with_no_presence`,
487 if both arguments are explicitly set.
488 preserving_proto_field_name (Optional(bool)): An option that
489 determines whether field name representations preserve
490 proto case (snake_case) or use lowerCamelCase. Default is False.
491 sort_keys (Optional(bool)): If True, then the output will be sorted by field names.
492 Default is False.
493 indent (Optional(int)): The JSON object will be pretty-printed with this indent level.
494 An indent level of 0 or negative will only insert newlines.
495 Pass None for the most compact representation without newlines.
496 float_precision (Optional(int)): If set, use this to specify float field valid digits.
497 Default is None. [DEPRECATED] float_precision was removed in Protobuf 7.x.
498 always_print_fields_with_no_presence (Optional(bool)): If True, fields without
499 presence (implicit presence scalars, repeated fields, and map fields) will
500 always be serialized. Any field that supports presence is not affected by
501 this option (including singular message fields and oneof fields).
502 This value must match `including_default_value_fields`,
503 if both arguments are explicitly set.
504 Returns:
505 str: The json string representation of the protocol buffer.
506 """
507 return _message_to_map(map_fn=MessageToJson, **locals())
509 def from_json(cls, payload, *, ignore_unknown_fields=False) -> "Message":
510 """Given a json string representing an instance,
511 parse it into a message.
513 Args:
514 payload: A json string representing a message.
515 ignore_unknown_fields (Optional(bool)): If True, do not raise errors
516 for unknown fields.
518 Returns:
519 ~.Message: An instance of the message class against which this
520 method was called.
521 """
522 instance = cls()
523 Parse(payload, instance._pb, ignore_unknown_fields=ignore_unknown_fields)
524 return instance
526 def to_dict(
527 cls,
528 instance,
529 *,
530 use_integers_for_enums=True,
531 preserving_proto_field_name=True,
532 including_default_value_fields=None,
533 float_precision=None,
534 always_print_fields_with_no_presence=None,
535 ) -> Dict[str, Any]:
536 """Given a message instance, return its representation as a python dict.
538 Args:
539 instance: An instance of this message type, or something
540 compatible (accepted by the type's constructor).
541 use_integers_for_enums (Optional(bool)): An option that determines whether enum
542 values should be represented by strings (False) or integers (True).
543 Default is True.
544 preserving_proto_field_name (Optional(bool)): An option that
545 determines whether field name representations preserve
546 proto case (snake_case) or use lowerCamelCase. Default is True.
547 including_default_value_fields (Optional(bool)): Deprecated. Use argument
548 `always_print_fields_with_no_presence` instead. An option that
549 determines whether the default field values should be included in the results.
550 This value must match `always_print_fields_with_no_presence`,
551 if both arguments are explicitly set.
552 float_precision (Optional(int)): If set, use this to specify float field valid digits.
553 Default is None. [DEPRECATED] float_precision was removed in Protobuf 7.x.
554 always_print_fields_with_no_presence (Optional(bool)): If True, fields without
555 presence (implicit presence scalars, repeated fields, and map fields) will
556 always be serialized. Any field that supports presence is not affected by
557 this option (including singular message fields and oneof fields). This value
558 must match `including_default_value_fields`, if both arguments are explicitly set.
560 Returns:
561 dict: A representation of the protocol buffer using pythonic data structures.
562 Messages and map fields are represented as dicts,
563 repeated fields are represented as lists.
564 """
565 return _message_to_map(map_fn=MessageToDict, **locals())
567 def copy_from(cls, instance, other):
568 """Equivalent for protobuf.Message.CopyFrom
570 Args:
571 instance: An instance of this message type
572 other: (Union[dict, ~.Message):
573 A dictionary or message to reinitialize the values for this message.
574 """
575 if isinstance(other, cls):
576 # Just want the underlying proto.
577 other = Message.pb(other)
578 elif isinstance(other, cls.pb()):
579 # Don't need to do anything.
580 pass
581 elif isinstance(other, collections.abc.Mapping):
582 # Coerce into a proto
583 other = cls._meta.pb(**other)
584 else:
585 raise TypeError(
586 "invalid argument type to copy to {}: {}".format(
587 cls.__name__, other.__class__.__name__
588 )
589 )
591 # Note: we can't just run self.__init__ because this may be a message field
592 # for a higher order proto; the memory layout for protos is NOT LIKE the
593 # python memory model. We cannot rely on just setting things by reference.
594 # Non-trivial complexity is (partially) hidden by the protobuf runtime.
595 cls.pb(instance).CopyFrom(other)
598class Message(metaclass=MessageMeta):
599 """The abstract base class for a message.
601 Args:
602 mapping (Union[dict, ~.Message]): A dictionary or message to be
603 used to determine the values for this message.
604 ignore_unknown_fields (Optional(bool)): If True, do not raise errors for
605 unknown fields. Only applied if `mapping` is a mapping type or there
606 are keyword parameters.
607 kwargs (dict): Keys and values corresponding to the fields of the
608 message.
609 """
611 def __init__(
612 self,
613 mapping=None,
614 *,
615 ignore_unknown_fields=False,
616 **kwargs,
617 ):
618 # We accept several things for `mapping`:
619 # * An instance of this class.
620 # * An instance of the underlying protobuf descriptor class.
621 # * A dict
622 # * Nothing (keyword arguments only).
623 if mapping is None:
624 if not kwargs:
625 # Special fast path for empty construction.
626 super().__setattr__("_pb", self._meta.pb())
627 return
629 mapping = kwargs
630 elif isinstance(mapping, self._meta.pb):
631 # Make a copy of the mapping.
632 # This is a constructor for a new object, so users will assume
633 # that it will not have side effects on the arguments being
634 # passed in.
635 #
636 # The `wrap` method on the metaclass is the public API for taking
637 # ownership of the passed in protobuf object.
638 mapping = copy.deepcopy(mapping)
639 if kwargs:
640 mapping.MergeFrom(self._meta.pb(**kwargs))
642 super().__setattr__("_pb", mapping)
643 return
644 elif isinstance(mapping, type(self)):
645 # Just use the above logic on mapping's underlying pb.
646 self.__init__(mapping=mapping._pb, **kwargs)
647 return
648 elif isinstance(mapping, collections.abc.Mapping):
649 # Can't have side effects on mapping.
650 mapping = copy.copy(mapping)
651 # kwargs entries take priority for duplicate keys.
652 mapping.update(kwargs)
653 else:
654 # Sanity check: Did we get something not a map? Error if so.
655 raise TypeError(
656 "Invalid constructor input for %s: %r"
657 % (
658 self.__class__.__name__,
659 mapping,
660 )
661 )
663 params = {}
664 # Update the mapping to address any values that need to be
665 # coerced.
666 marshal = self._meta.marshal
667 for key, value in mapping.items():
668 (key, pb_type) = self._get_pb_type_from_key(key)
669 if pb_type is None:
670 if ignore_unknown_fields:
671 continue
673 raise ValueError(
674 "Unknown field for {}: {}".format(self.__class__.__name__, key)
675 )
677 pb_value = marshal.to_proto(pb_type, value)
679 if pb_value is not None:
680 params[key] = pb_value
682 # Create the internal protocol buffer.
683 super().__setattr__("_pb", self._meta.pb(**params))
685 def _get_pb_type_from_key(self, key):
686 """Given a key, return the corresponding pb_type.
688 Args:
689 key(str): The name of the field.
691 Returns:
692 A tuple containing a key and pb_type. The pb_type will be
693 the composite type of the field, or the primitive type if a primitive.
694 If no corresponding field exists, return None.
695 """
697 pb_type = None
699 try:
700 pb_type = self._meta.fields[key].pb_type
701 except KeyError:
702 # Underscores may be appended to field names
703 # that collide with python or proto-plus keywords.
704 # In case a key only exists with a `_` suffix, coerce the key
705 # to include the `_` suffix. It's not possible to
706 # natively define the same field with a trailing underscore in protobuf.
707 # See related issue
708 # https://github.com/googleapis/python-api-core/issues/227
709 if f"{key}_" in self._meta.fields:
710 key = f"{key}_"
711 pb_type = self._meta.fields[key].pb_type
713 return (key, pb_type)
715 def __dir__(self):
716 desc = type(self).pb().DESCRIPTOR
717 names = {f_name for f_name in self._meta.fields.keys()}
718 names.update(m.name for m in desc.nested_types)
719 names.update(e.name for e in desc.enum_types)
720 names.update(dir(object()))
721 # Can't think of a better way of determining
722 # the special methods than manually listing them.
723 names.update(
724 (
725 "__bool__",
726 "__contains__",
727 "__dict__",
728 "__getattr__",
729 "__getstate__",
730 "__module__",
731 "__setstate__",
732 "__weakref__",
733 )
734 )
736 return names
738 def __bool__(self):
739 """Return True if any field is truthy, False otherwise."""
740 return any(k in self and getattr(self, k) for k in self._meta.fields.keys())
742 def __contains__(self, key):
743 """Return True if this field was set to something non-zero on the wire.
745 In most cases, this method will return True when ``__getattr__``
746 would return a truthy value and False when it would return a falsy
747 value, so explicitly calling this is not useful.
749 The exception case is empty messages explicitly set on the wire,
750 which are falsy from ``__getattr__``. This method allows to
751 distinguish between an explicitly provided empty message and the
752 absence of that message, which is useful in some edge cases.
754 The most common edge case is the use of ``google.protobuf.BoolValue``
755 to get a boolean that distinguishes between ``False`` and ``None``
756 (or the same for a string, int, etc.). This library transparently
757 handles that case for you, but this method remains available to
758 accommodate cases not automatically covered.
760 Args:
761 key (str): The name of the field.
763 Returns:
764 bool: Whether the field's value corresponds to a non-empty
765 wire serialization.
766 """
767 pb_value = getattr(self._pb, key)
768 try:
769 # Protocol buffers "HasField" is unfriendly; it only works
770 # against composite, non-repeated fields, and raises ValueError
771 # against any repeated field or primitive.
772 #
773 # There is no good way to test whether it is valid to provide
774 # a field to this method, so sadly we are stuck with a
775 # somewhat inefficient try/except.
776 return self._pb.HasField(key)
777 except ValueError:
778 return bool(pb_value)
780 def __delattr__(self, key):
781 """Delete the value on the given field.
783 This is generally equivalent to setting a falsy value.
784 """
785 self._pb.ClearField(key)
787 def __eq__(self, other):
788 """Return True if the messages are equal, False otherwise."""
789 # If these are the same type, use internal protobuf's equality check.
790 if isinstance(other, type(self)):
791 return self._pb == other._pb
793 # If the other type is the target protobuf object, honor that also.
794 if isinstance(other, self._meta.pb):
795 return self._pb == other
797 # Ask the other object.
798 return NotImplemented
800 def __getattr__(self, key):
801 """Retrieve the given field's value.
803 In protocol buffers, the presence of a field on a message is
804 sufficient for it to always be "present".
806 For primitives, a value of the correct type will always be returned
807 (the "falsy" values in protocol buffers consistently match those
808 in Python). For repeated fields, the falsy value is always an empty
809 sequence.
811 For messages, protocol buffers does distinguish between an empty
812 message and absence, but this distinction is subtle and rarely
813 relevant. Therefore, this method always returns an empty message
814 (following the official implementation). To check for message
815 presence, use ``key in self`` (in other words, ``__contains__``).
817 .. note::
819 Some well-known protocol buffer types
820 (e.g. ``google.protobuf.Timestamp``) will be converted to
821 their Python equivalents. See the ``marshal`` module for
822 more details.
823 """
824 (key, pb_type) = self._get_pb_type_from_key(key)
825 if pb_type is None:
826 raise AttributeError(
827 "Unknown field for {}: {}".format(self.__class__.__name__, key)
828 )
829 pb_value = getattr(self._pb, key)
830 marshal = self._meta.marshal
831 return marshal.to_python(pb_type, pb_value, absent=key not in self)
833 def __ne__(self, other):
834 """Return True if the messages are unequal, False otherwise."""
835 return not self == other
837 def __repr__(self):
838 return repr(self._pb)
840 def __setattr__(self, key, value):
841 """Set the value on the given field.
843 For well-known protocol buffer types which are marshalled, either
844 the protocol buffer object or the Python equivalent is accepted.
845 """
846 if key[0] == "_":
847 return super().__setattr__(key, value)
848 marshal = self._meta.marshal
849 (key, pb_type) = self._get_pb_type_from_key(key)
850 if pb_type is None:
851 raise AttributeError(
852 "Unknown field for {}: {}".format(self.__class__.__name__, key)
853 )
855 pb_value = marshal.to_proto(pb_type, value)
857 # Clear the existing field.
858 # This is the only way to successfully write nested falsy values,
859 # because otherwise MergeFrom will no-op on them.
860 self._pb.ClearField(key)
862 # Merge in the value being set.
863 if pb_value is not None:
864 self._pb.MergeFrom(self._meta.pb(**{key: pb_value}))
866 def __getstate__(self):
867 """Serialize for pickling."""
868 return self._pb.SerializeToString()
870 def __setstate__(self, value):
871 """Deserialization for pickling."""
872 new_pb = self._meta.pb().FromString(value)
873 super().__setattr__("_pb", new_pb)
876class _MessageInfo:
877 """Metadata about a message.
879 Args:
880 fields (Tuple[~.fields.Field]): The fields declared on the message.
881 package (str): The proto package.
882 full_name (str): The full name of the message.
883 file_info (~._FileInfo): The file descriptor and messages for the
884 file containing this message.
885 marshal (~.Marshal): The marshal instance to which this message was
886 automatically registered.
887 options (~.descriptor_pb2.MessageOptions): Any options that were
888 set on the message.
889 """
891 def __init__(
892 self,
893 *,
894 fields: List[Field],
895 package: str,
896 full_name: str,
897 marshal: Marshal,
898 options: descriptor_pb2.MessageOptions,
899 ) -> None:
900 self.package = package
901 self.full_name = full_name
902 self.options = options
903 self.fields = collections.OrderedDict((i.name, i) for i in fields)
904 self.fields_by_number = collections.OrderedDict((i.number, i) for i in fields)
905 self.marshal = marshal
906 self._pb = None
908 @property
909 def pb(self) -> Type[message.Message]:
910 """Return the protobuf message type for this descriptor.
912 If a field on the message references another message which has not
913 loaded, then this method returns None.
914 """
915 return self._pb
918def _message_to_map(
919 cls,
920 map_fn,
921 instance,
922 *,
923 including_default_value_fields=None,
924 always_print_fields_with_no_presence=None,
925 float_precision=None,
926 **kwargs,
927):
928 """
929 Helper for logic for Message.to_dict and Message.to_json
930 """
932 # The `including_default_value_fields` argument was removed from protobuf 5.x
933 # and replaced with `always_print_fields_with_no_presence` which is similar but
934 # handles optional fields consistently by not affecting them.
935 # The old flag accidentally had inconsistent behavior between optional fields
936 # in proto2 and proto3.
937 print_fields = cls._normalize_print_fields_without_presence(
938 always_print_fields_with_no_presence, including_default_value_fields
939 )
940 if _PROTOBUF_MAJOR_VERSION in ("3", "4"):
941 kwargs["including_default_value_fields"] = print_fields
942 else:
943 kwargs["always_print_fields_with_no_presence"] = print_fields
945 if float_precision:
946 # float_precision removed in protobuf 7
947 if _PROTOBUF_MAJOR_VERSION in ("3", "4", "5", "6"):
948 kwargs["float_precision"] = float_precision
949 warning_msg = "`float_precision` will be removed in Protobuf 7.x."
951 else: # pragma: NO COVER
952 warning_msg = (
953 "`float_precision` was removed in Protobuf 7.x+, and will be ignored."
954 )
955 warnings.warn(warning_msg, DeprecationWarning, stacklevel=3)
956 # suppress similar float_precision warning from protobuf library.
957 with warnings.catch_warnings():
958 warnings.filterwarnings("ignore", message=".*float_precision.*")
959 return map_fn(cls.pb(instance), **kwargs)
962__all__ = ("Message",)