Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/proto-plus/proto/marshal/marshal.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ def to_proto(self, proto_type, value, *, strict: bool = False):
# annotation. We need to do the conversion based on the `value`
# field's type.
if isinstance(value, dict) and (
proto_type.DESCRIPTOR.has_options
hasattr(proto_type, "DESCRIPTOR")
and proto_type.DESCRIPTOR.has_options
and proto_type.DESCRIPTOR.GetOptions().map_entry
):
recursive_type = type(proto_type().value)
Expand Down
59 changes: 36 additions & 23 deletions packages/proto-plus/proto/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,28 +659,34 @@ def __init__(
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)
)
try:
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)
pb_value = marshal.to_proto(pb_type, value)

if pb_value is not None:
params[key] = pb_value
if pb_value is not None:
params[key] = pb_value

# Create the internal protocol buffer.
super().__setattr__("_pb", self._meta.pb(**params))
# Create the internal protocol buffer.
super().__setattr__("_pb", self._meta.pb(**params))
except TypeError as e:
raise TypeError(
"Failed to initialize {}. Underlying error: {}".format(
self.__class__.__name__, e
)
) from e

def _get_pb_type_from_key(self, key):
"""Given a key, return the corresponding pb_type.
Expand Down Expand Up @@ -852,16 +858,23 @@ def __setattr__(self, key, value):
"Unknown field for {}: {}".format(self.__class__.__name__, key)
)

pb_value = marshal.to_proto(pb_type, value)
try:
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)
# 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}))
# Merge in the value being set.
if pb_value is not None:
self._pb.MergeFrom(self._meta.pb(**{key: pb_value}))
except TypeError as e:
raise TypeError(
"Failed to set field '{}' on {} to value {}. Underlying error: {}".format(
key, self.__class__.__name__, value, e
)
) from e

def __getstate__(self):
"""Serialize for pickling."""
Expand Down
36 changes: 36 additions & 0 deletions packages/proto-plus/tests/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,39 @@ class Arm(proto.Message):

def test_dir_message_base():
assert set(dir(proto.Message)) == set(dir(type))


def test_invalid_initialization_type_error():
"""Verify that bad types passed to __init__ raise a descriptive TypeError."""

class UserProfile(proto.Message):
username = proto.Field(proto.STRING, number=1)
age = proto.Field(proto.INT32, number=2)

with pytest.raises(TypeError) as excinfo:
# Passing a list where a string is expected
UserProfile(username=["not", "a", "string"])

error_msg = str(excinfo.value)
assert "Failed to initialize UserProfile" in error_msg
assert "Underlying error" in error_msg
assert isinstance(excinfo.value.__cause__, TypeError)


def test_invalid_assignment_type_error():
"""Verify that bad types assigned via __setattr__ raise a descriptive TypeError."""

class UserProfile(proto.Message):
username = proto.Field(proto.STRING, number=1)
age = proto.Field(proto.INT32, number=2)

profile = UserProfile()

with pytest.raises(TypeError) as excinfo:
# Assigning a dictionary where an integer is expected
profile.age = {"invalid": "type"}

error_msg = str(excinfo.value)
assert "Failed to set field 'age' on UserProfile" in error_msg
assert "{'invalid': 'type'}" in error_msg
assert isinstance(excinfo.value.__cause__, TypeError)
Loading