Search code examples
pythonattributesbufferprotocolselement

Looping over Protocol Buffers attributes in Python


I would like help with recursively looping over all attributes/sub objects contained in a protocol buffers message, assuming that we do not know the names of them, or how many there are.

As an example, take the following .proto file from the tutorial on the google website:

  message Person {
    required string name = 1;
    required int32 id = 2;
    optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

and to use it...:

person = tutorial.Person()
person.id = 1234
person.name = "John Doe"
person.email = "[email protected]"
phone = person.phone.add()
phone.number = "555-4321"
phone.type = tutorial.Person.HOME

Given Person, How do I then access both the name of the attribute and its value for each element: person.id, person.name, person.email, person.phone.number, person.phone.type?

I have tried the following, however it doesn't seem to recurs into person.phone.number or person.phone.type.

object_of_interest = Person

while( hasattr(object_of_interest, "_fields") ):
  for obj in object_of_interest._fields:
    # Do_something_with_object(obj) # eg print obj.name
    object_of_interest = obj

I have tried using obj.DESCRIPTOR.fields_by_name.keys to access the sub elements, but these are the string representations of the sub objects, not the objects themselves.

obj.name gives me the attribute of the name, but im not sure how to actually get the value of that attribute, eg obj.name may give me 'name', but how do i get 'john doe' out of it?


Solution

  • I'm not super familiar with protobufs, so there may well be an easier way or api for this kind of thing. However, below shows an example of how you could iterate/introspect and objects fields and print them out. Hopefully enough to get you going in the right direction at least...

    import addressbook_pb2 as addressbook
    
    person = addressbook.Person(id=1234, name="John Doe", email="[email protected]")
    person.phone.add(number="1234567890")
    
    
    def dump_object(obj):
        for descriptor in obj.DESCRIPTOR.fields:
            value = getattr(obj, descriptor.name)
            if descriptor.type == descriptor.TYPE_MESSAGE:
                if descriptor.label == descriptor.LABEL_REPEATED:
                    map(dump_object, value)
                else:
                    dump_object(value)
            elif descriptor.type == descriptor.TYPE_ENUM:
                enum_name = descriptor.enum_type.values[value].name
                print "%s: %s" % (descriptor.full_name, enum_name)
            else:
                print "%s: %s" % (descriptor.full_name, value)
    
    dump_object(person)
    

    which outputs

    tutorial.Person.name: John Doe
    tutorial.Person.id: 1234
    tutorial.Person.email: [email protected]
    tutorial.Person.PhoneNumber.number: 1234567890
    tutorial.Person.PhoneNumber.type: HOME