pythondescriptorproto3protobuf-python

How to collate several protobuf messages into one object


I have a a protobuf message in the form of:

message CustomMessage
{
    SubMessageTypeA a_msg = 1;
    SubMessageTypeB b_msg = 2;    
    SubMessageTypeC c_msg = 3;
}

I have several objects (assume 3) of this type that each have one of their respective fields populated. I want to collate these objects into 1 in Python. Here is a snippet of code I'm unsuccessfully using. Appreciate your help in finding the right answer:

collated_obj = CustomMessage()
for obj in [obj_1, obj_2, obj_3]:
    for desc in obj.DESCRIPTOR.fields:
        getattr(collated_obj, desc.name).CopyFrom(getattr(obj, desc.name))

What I'm doing is very brittle and have not been working. As a started, if the field is a basic type (e.g uint32), getattr is failing. Is there a way to find a reference to the field of a proto other than using getattr? Seems like that's the main part I'm stuck on. I can convert everything to json and have an easier life. But trying to avoid repeated conversion and serialization/deserialization if possible.


Solution

  • You can merge the content of two protobuf messages by using MergeFrom() it is way easier than using a descriptor approach, and if the field are basic types the value will be replaced, if they are repeated it will be concatenated.

    from custom_message_pb2 import CustomMessage, SubMessageTypeA, SubMessageTypeB, SubMessageTypeC
    
    
    collated_obj = CustomMessage()
    for obj in [obj_1, obj_2, obj_3]:
        collated_obj.MergeFrom(obj)
    

    edit: If you want to keep with the descriptor approach, you can do it by checking the field type and using the appropriate way to assign or merge the values. Here is what it would look like:

    from google.protobuf.descriptor import FieldDescriptor
    
    def merge_using_descriptors(collated_obj, obj):
        for field in obj.DESCRIPTOR.fields:
            field_value = getattr(obj, field.name)
            
            if field.type == FieldDescriptor.TYPE_MESSAGE:
                if field_value.IsInitialized():
                    getattr(collated_obj, field.name).MergeFrom(field_value)
            elif field.type in [FieldDescriptor.TYPE_BYTES, FieldDescriptor.TYPE_STRING] and field_value:
                setattr(collated_obj, field.name, field_value)
            else:
                if field_value != field.default_value:
                    setattr(collated_obj, field.name, field_value)
    
    collated_obj = CustomMessage()
    for obj in [obj_1, obj_2, obj_3]:
        merge_using_descriptors(collated_obj, obj)