Skip to content

Constructing message from dictionary fails when message has nested Struct #424

@Ark-kun

Description

@Ark-kun

Usually, if a certain dictionary can be used to initialize a message, then the same dictionary can be used inside a bigger dictionary to initialize a field of a bigger message.

However this invariant fails for the Struct message when it's nested more than once:

from google.protobuf import struct_pb2
import proto

class Msg1(proto.Message):
    struct_field: struct_pb2.Struct = proto.Field(
        proto.MESSAGE,
        number=1,
        message=struct_pb2.Struct,
    )

class Msg2(proto.Message):
    msg1_field: Msg1 = proto.Field(
        proto.MESSAGE,
        number=1,
        message=Msg1,
    )

# This succeeds
Msg1({"struct_field": {"foo": "bar"}})

# This fails:
Msg2({"msg1_field": {"struct_field": {"foo": "bar"}}})
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
[/usr/local/lib/python3.10/dist-packages/proto/message.py](https://localhost:8080/#) in __init__(self, mapping, ignore_unknown_fields, **kwargs)
    580             try:
--> 581                 pb_value = marshal.to_proto(pb_type, value)
    582             except ValueError:

5 frames
[/usr/local/lib/python3.10/dist-packages/proto/marshal/marshal.py](https://localhost:8080/#) in to_proto(self, proto_type, value, strict)
    227 
--> 228         pb_value = self.get_rule(proto_type=proto_type).to_proto(value)
    229 

[/usr/local/lib/python3.10/dist-packages/proto/marshal/rules/message.py](https://localhost:8080/#) in to_proto(self, value)
     35                 # Try the fast path first.
---> 36                 return self._descriptor(**value)
     37             except TypeError as ex:

ValueError: Protocol message Struct has no "foo" field.

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
[<ipython-input-6-ccab0235a57d>](https://localhost:8080/#) in <cell line: 1>()
----> 1 Msg2({"msg1_field": {"struct_field": {"foo": "bar"}}})

[/usr/local/lib/python3.10/dist-packages/proto/message.py](https://localhost:8080/#) in __init__(self, mapping, ignore_unknown_fields, **kwargs)
    607                         value[f"{item}_"] = value.pop(item)
    608 
--> 609                 pb_value = marshal.to_proto(pb_type, value)
    610 
    611             if pb_value is not None:

[/usr/local/lib/python3.10/dist-packages/proto/marshal/marshal.py](https://localhost:8080/#) in to_proto(self, proto_type, value, strict)
    226             return {k: self.to_proto(recursive_type, v) for k, v in value.items()}
    227 
--> 228         pb_value = self.get_rule(proto_type=proto_type).to_proto(value)
    229 
    230         # Sanity check: If we are in strict mode, did we get the value we want?

[/usr/local/lib/python3.10/dist-packages/proto/marshal/rules/message.py](https://localhost:8080/#) in to_proto(self, value)
     34             try:
     35                 # Try the fast path first.
---> 36                 return self._descriptor(**value)
     37             except TypeError as ex:
     38                 # If we have a type error,

ValueError: Protocol message Struct has no "foo" field.

Trying to work around the issue leads to incorrect values:

# This produces incorrect value:
Msg2({"msg1_field": {"struct_field": {"fields": {"foo": "bar"}}}})
msg1_field {
  struct_field {
    fields {
      key: "fields"
      value {
        struct_value {
          fields {
            key: "foo"
            value {
              string_value: "bar"
            }
          }
        }
      }
    }
  }
}

See how "fields" is duplicated. First as a Struct.fields, then as a string map key.

  • Package version: 1.23.0

Metadata

Metadata

Assignees

Labels

priority: p2Moderately-important priority. Fix may not be included in next release.type: bugError or flaw in code with unintended results or allowing sub-optimal usage patterns.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions