Using google.protobuf.Any in python file

Kenenbek Arzymatov picture Kenenbek Arzymatov · May 6, 2017 · Viewed 8.8k times · Source

I have such .proto file

syntax = "proto3";
import "google/protobuf/any.proto";

message Request {
  google.protobuf.Any request_parameters = 1;
}

How can I create Request object and populate its fields? I tried this:

import ma_pb2
from google.protobuf.any_pb2 import Any

parameters = {"a": 1, "b": 2}
Request = ma_pb2.Request()
some_any = Any()
some_any.CopyFrom(parameters)
Request.request_parameters = some_any

But I have an error:

TypeError: Parameter to CopyFrom() must be instance of same class: expected google.protobuf.Any got dict.

UPDATE

Following prompts of @Kevin I added new message to .proto file:

message Small {
  string a = 1;
}

Now code looks like this:

Request = ma_pb2.Request()
small = ma_pb2.Small()
small.a = "1"

some_any = Any()
some_any.Pack(small)

Request.request_parameters = small

But at the last assignment I have an error:

Request.request_parameters = small
AttributeError: Assignment not allowed to field "request_parameters" in protocol message object.

What did I do wrong?

Answer

Kevin picture Kevin · May 6, 2017

Any is not a magic box for storing arbitrary keys and values. The purpose of Any is to denote "any" message type, in cases where you might not know which message you want to use until runtime. But at runtime, you still need to have some specific message in mind. You can then use the .Pack() and .Unpack() methods to convert that message into an Any, and at that point you would do something like Request.request_parameters.CopyFrom(some_any).

So, if you want to store this specific dictionary:

{"a": 1, "b": 2}

...you'll need a .proto file which describes some message type that has integer fields named a and b. Personally, I'd see that as overkill; just throw your a and b fields directly into the Request message, unless you have a good reason for separating them out. If you "forget" one of these keys, you can always add it later, so don't worry too much about completeness.


If you really want a "magic box for storing arbitrary keys and values" rather than what I described above, you could use a Map instead of Any. This has the advantage of not requiring you to declare all of your keys upfront, in cases where the set of keys might include arbitrary strings (for example, HTTP headers). It has the disadvantage of being harder to lint or type-check (especially in statically-typed languages), because you can misspell a string more easily than an attribute. As shown in the linked resource, Maps are basically syntactic sugar for a repeated field like the following (that is, the on-wire representation is exactly the same as what you'd get from doing this, so it's backwards compatible to clients which don't support Maps):

message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;