structure saas with tools
This commit is contained in:
969
.venv/lib/python3.10/site-packages/proto/message.py
Normal file
969
.venv/lib/python3.10/site-packages/proto/message.py
Normal file
@@ -0,0 +1,969 @@
|
||||
# Copyright 2018 Google LLC
|
||||
#
|
||||
# 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
|
||||
#
|
||||
# https://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.
|
||||
|
||||
import collections
|
||||
import collections.abc
|
||||
import copy
|
||||
import re
|
||||
from typing import Any, Dict, 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() # Important to cache result here.
|
||||
|
||||
|
||||
class MessageMeta(type):
|
||||
"""A metaclass for building and registering Message subclasses."""
|
||||
|
||||
def __new__(mcls, name, bases, attrs):
|
||||
# Do not do any special behavior for Message itself.
|
||||
if not bases:
|
||||
return super().__new__(mcls, name, bases, attrs)
|
||||
|
||||
# Get the essential information about the proto package, and where
|
||||
# this component belongs within the file.
|
||||
package, marshal = _package_info.compile(name, attrs)
|
||||
|
||||
# Determine the local path of this proto component within the file.
|
||||
local_path = tuple(attrs.get("__qualname__", name).split("."))
|
||||
|
||||
# Sanity check: We get the wrong full name if a class is declared
|
||||
# inside a function local scope; correct this.
|
||||
if "<locals>" in local_path:
|
||||
ix = local_path.index("<locals>")
|
||||
local_path = local_path[: ix - 1] + local_path[ix + 1 :]
|
||||
|
||||
# Determine the full name in protocol buffers.
|
||||
full_name = ".".join((package,) + local_path).lstrip(".")
|
||||
|
||||
# Special case: Maps. Map fields are special; they are essentially
|
||||
# shorthand for a nested message and a repeated field of that message.
|
||||
# Decompose each map into its constituent form.
|
||||
# https://developers.google.com/protocol-buffers/docs/proto3#maps
|
||||
map_fields = {}
|
||||
for key, field in attrs.items():
|
||||
if not isinstance(field, MapField):
|
||||
continue
|
||||
|
||||
# Determine the name of the entry message.
|
||||
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),
|
||||
)
|
||||
|
||||
# Create the "entry" message (with the key and value fields).
|
||||
#
|
||||
# Note: We instantiate an ordered dictionary here and then
|
||||
# attach key and value in order to ensure that the fields are
|
||||
# iterated in the correct order when the class is created.
|
||||
# This is only an issue in Python 3.5, where the order is
|
||||
# random (and the wrong order causes the pool to refuse to add
|
||||
# the descriptor because reasons).
|
||||
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)
|
||||
|
||||
# Create the repeated field for the entry message.
|
||||
map_fields[key] = RepeatedField(
|
||||
ProtoType.MESSAGE,
|
||||
number=field.number,
|
||||
message=map_fields[msg_name],
|
||||
)
|
||||
|
||||
# Add the new entries to the attrs
|
||||
attrs.update(map_fields)
|
||||
|
||||
# Okay, now we deal with all the rest of the fields.
|
||||
# Iterate over all the attributes and separate the fields into
|
||||
# their own sequence.
|
||||
fields = []
|
||||
new_attrs = {}
|
||||
oneofs = collections.OrderedDict()
|
||||
proto_imports = set()
|
||||
index = 0
|
||||
for key, field in attrs.items():
|
||||
# Sanity check: If this is not a field, do nothing.
|
||||
if not isinstance(field, Field):
|
||||
# The field objects themselves should not be direct attributes.
|
||||
new_attrs[key] = field
|
||||
continue
|
||||
|
||||
# Add data that the field requires that we do not take in the
|
||||
# constructor because we can derive it from the metaclass.
|
||||
# (The goal is to make the declaration syntax as nice as possible.)
|
||||
field.mcls_data = {
|
||||
"name": key,
|
||||
"parent_name": full_name,
|
||||
"index": index,
|
||||
"package": package,
|
||||
}
|
||||
|
||||
# Add the field to the list of fields.
|
||||
fields.append(field)
|
||||
# If this field is part of a "oneof", ensure the oneof itself
|
||||
# is represented.
|
||||
if field.oneof:
|
||||
# Keep a running tally of the index of each oneof, and assign
|
||||
# that index to the field's descriptor.
|
||||
oneofs.setdefault(field.oneof, len(oneofs))
|
||||
field.descriptor.oneof_index = oneofs[field.oneof]
|
||||
|
||||
# If this field references a message, it may be from another
|
||||
# proto file; ensure we know about the import (to faithfully
|
||||
# construct our file descriptor proto).
|
||||
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()
|
||||
# Sanity check: The field's message may not yet be defined if
|
||||
# it was a Message defined in the same file, and the file
|
||||
# descriptor proto has not yet been generated.
|
||||
#
|
||||
# We do nothing in this situation; everything will be handled
|
||||
# correctly when the file descriptor is created later.
|
||||
if field_msg:
|
||||
proto_imports.add(field_msg.DESCRIPTOR.file.name)
|
||||
|
||||
# Same thing, but for enums.
|
||||
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)
|
||||
|
||||
# Increment the field index counter.
|
||||
index += 1
|
||||
|
||||
# As per descriptor.proto, all synthetic oneofs must be ordered after
|
||||
# 'real' oneofs.
|
||||
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
|
||||
|
||||
# Generating a metaclass dynamically provides class attributes that
|
||||
# instances can't see. This provides idiomatically named constants
|
||||
# that enable the following pattern to check for field presence:
|
||||
#
|
||||
# class MyMessage(proto.Message):
|
||||
# field = proto.Field(proto.INT32, number=1, optional=True)
|
||||
#
|
||||
# m = MyMessage()
|
||||
# MyMessage.field in m
|
||||
if opt_attrs:
|
||||
mcls = type("AttrsMeta", (mcls,), opt_attrs)
|
||||
|
||||
# Determine the filename.
|
||||
# We determine an appropriate proto filename based on the
|
||||
# Python module.
|
||||
filename = _file_info._FileInfo.proto_file_name(
|
||||
new_attrs.get("__module__", name.lower())
|
||||
)
|
||||
|
||||
# Get or create the information about the file, including the
|
||||
# descriptor to which the new message descriptor shall be added.
|
||||
file_info = _file_info._FileInfo.maybe_add_descriptor(filename, package)
|
||||
|
||||
# Ensure any imports that would be necessary are assigned to the file
|
||||
# descriptor proto being created.
|
||||
for proto_import in proto_imports:
|
||||
if proto_import not in file_info.descriptor.dependency:
|
||||
file_info.descriptor.dependency.append(proto_import)
|
||||
|
||||
# Retrieve any message options.
|
||||
opts = descriptor_pb2.MessageOptions(**new_attrs.pop("_pb_options", {}))
|
||||
|
||||
# Create the underlying proto descriptor.
|
||||
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,
|
||||
)
|
||||
|
||||
# If any descriptors were nested under this one, they need to be
|
||||
# attached as nested types here.
|
||||
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))
|
||||
|
||||
# Same thing, but for enums
|
||||
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))
|
||||
|
||||
# Add the descriptor to the file if it is a top-level descriptor,
|
||||
# or to a "holding area" for nested messages otherwise.
|
||||
if len(local_path) == 1:
|
||||
file_info.descriptor.message_type.add().MergeFrom(desc)
|
||||
else:
|
||||
file_info.nested[local_path] = desc
|
||||
|
||||
# Create the MessageInfo instance to be attached to this message.
|
||||
new_attrs["_meta"] = _MessageInfo(
|
||||
fields=fields,
|
||||
full_name=full_name,
|
||||
marshal=marshal,
|
||||
options=opts,
|
||||
package=package,
|
||||
)
|
||||
|
||||
# Run the superclass constructor.
|
||||
cls = super().__new__(mcls, name, bases, new_attrs)
|
||||
|
||||
# The info class and fields need a reference to the class just created.
|
||||
cls._meta.parent = cls
|
||||
for field in cls._meta.fields.values():
|
||||
field.parent = cls
|
||||
|
||||
# Add this message to the _FileInfo instance; this allows us to
|
||||
# associate the descriptor with the message once the descriptor
|
||||
# is generated.
|
||||
file_info.messages[full_name] = cls
|
||||
|
||||
# Generate the descriptor for the file if it is ready.
|
||||
if file_info.ready(new_class=cls):
|
||||
file_info.generate_file_pb(new_class=cls, fallback_salt=full_name)
|
||||
|
||||
# Done; return the class.
|
||||
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`.
|
||||
"""
|
||||
# Optimized fast path.
|
||||
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
|
||||
)
|
||||
# Default to True if neither `always_print_fields_with_no_presence` or `including_default_value_fields` is set
|
||||
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:
|
||||
# The `including_default_value_fields` argument was removed from protobuf 5.x
|
||||
# and replaced with `always_print_fields_with_no_presence` which very similar but has
|
||||
# handles optional fields consistently by not affecting them.
|
||||
# The old flag accidentally had inconsistent behavior between proto2
|
||||
# optional and proto3 optional fields.
|
||||
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,
|
||||
) -> Dict[str, Any]:
|
||||
"""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:
|
||||
# The `including_default_value_fields` argument was removed from protobuf 5.x
|
||||
# and replaced with `always_print_fields_with_no_presence` which very similar but has
|
||||
# handles optional fields consistently by not affecting them.
|
||||
# The old flag accidentally had inconsistent behavior between proto2
|
||||
# optional and proto3 optional fields.
|
||||
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):
|
||||
# Just want the underlying proto.
|
||||
other = Message.pb(other)
|
||||
elif isinstance(other, cls.pb()):
|
||||
# Don't need to do anything.
|
||||
pass
|
||||
elif isinstance(other, collections.abc.Mapping):
|
||||
# Coerce into a proto
|
||||
other = cls._meta.pb(**other)
|
||||
else:
|
||||
raise TypeError(
|
||||
"invalid argument type to copy to {}: {}".format(
|
||||
cls.__name__, other.__class__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
# Note: we can't just run self.__init__ because this may be a message field
|
||||
# for a higher order proto; the memory layout for protos is NOT LIKE the
|
||||
# python memory model. We cannot rely on just setting things by reference.
|
||||
# Non-trivial complexity is (partially) hidden by the protobuf runtime.
|
||||
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,
|
||||
):
|
||||
# We accept several things for `mapping`:
|
||||
# * An instance of this class.
|
||||
# * An instance of the underlying protobuf descriptor class.
|
||||
# * A dict
|
||||
# * Nothing (keyword arguments only).
|
||||
if mapping is None:
|
||||
if not kwargs:
|
||||
# Special fast path for empty construction.
|
||||
super().__setattr__("_pb", self._meta.pb())
|
||||
return
|
||||
|
||||
mapping = kwargs
|
||||
elif isinstance(mapping, self._meta.pb):
|
||||
# Make a copy of the mapping.
|
||||
# This is a constructor for a new object, so users will assume
|
||||
# that it will not have side effects on the arguments being
|
||||
# passed in.
|
||||
#
|
||||
# The `wrap` method on the metaclass is the public API for taking
|
||||
# ownership of the passed in protobuf object.
|
||||
mapping = copy.deepcopy(mapping)
|
||||
if kwargs:
|
||||
mapping.MergeFrom(self._meta.pb(**kwargs))
|
||||
|
||||
super().__setattr__("_pb", mapping)
|
||||
return
|
||||
elif isinstance(mapping, type(self)):
|
||||
# Just use the above logic on mapping's underlying pb.
|
||||
self.__init__(mapping=mapping._pb, **kwargs)
|
||||
return
|
||||
elif isinstance(mapping, collections.abc.Mapping):
|
||||
# Can't have side effects on mapping.
|
||||
mapping = copy.copy(mapping)
|
||||
# kwargs entries take priority for duplicate keys.
|
||||
mapping.update(kwargs)
|
||||
else:
|
||||
# Sanity check: Did we get something not a map? Error if so.
|
||||
raise TypeError(
|
||||
"Invalid constructor input for %s: %r"
|
||||
% (
|
||||
self.__class__.__name__,
|
||||
mapping,
|
||||
)
|
||||
)
|
||||
|
||||
params = {}
|
||||
# Update the mapping to address any values that need to be
|
||||
# coerced.
|
||||
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
|
||||
|
||||
# Create the internal protocol buffer.
|
||||
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:
|
||||
# Underscores may be appended to field names
|
||||
# that collide with python or proto-plus keywords.
|
||||
# In case a key only exists with a `_` suffix, coerce the key
|
||||
# to include the `_` suffix. It's not possible to
|
||||
# natively define the same field with a trailing underscore in protobuf.
|
||||
# See related issue
|
||||
# https://github.com/googleapis/python-api-core/issues/227
|
||||
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()))
|
||||
# Can't think of a better way of determining
|
||||
# the special methods than manually listing them.
|
||||
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:
|
||||
# Protocol buffers "HasField" is unfriendly; it only works
|
||||
# against composite, non-repeated fields, and raises ValueError
|
||||
# against any repeated field or primitive.
|
||||
#
|
||||
# There is no good way to test whether it is valid to provide
|
||||
# a field to this method, so sadly we are stuck with a
|
||||
# somewhat inefficient try/except.
|
||||
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 these are the same type, use internal protobuf's equality check.
|
||||
if isinstance(other, type(self)):
|
||||
return self._pb == other._pb
|
||||
|
||||
# If the other type is the target protobuf object, honor that also.
|
||||
if isinstance(other, self._meta.pb):
|
||||
return self._pb == other
|
||||
|
||||
# Ask the other object.
|
||||
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)
|
||||
|
||||
# Clear the existing field.
|
||||
# This is the only way to successfully write nested falsy values,
|
||||
# because otherwise MergeFrom will no-op on them.
|
||||
self._pb.ClearField(key)
|
||||
|
||||
# Merge in the value being set.
|
||||
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",)
|
||||
Reference in New Issue
Block a user