python-2.7google-cloud-platformmetaclassendpoints-proto-datastoreprotorpc

Python protorpc dymnamic message


I am using protorpc with endpoints-proto-datastore.

I want to build a custom message from a provided structure.

For example, it is the following list of key: ['id1', 'id2', 'id3']

Each key is assigned to a MessageField named CustomField.

I would like to herited from Message and a class containing all key.

def create_custom_container(key_list):
    class cls():
        pass
    for i, k in enumerate(key_list):
        setattr(cls, k, MessageField(CustomField, i))
    return cls

class CustomMessage(Message, create_custom_container(key_list)):
    pass

But it doesn't work, I got: MessageDefinitionError: Message types may only inherit from Message

I saw from the protorpc source code that Message use metaclasses to prevent it to be inherited or have attribut to be modified on the fly.

So, I have no idea how to create my custom message on the fly.


Solution

  • The library goes a long way defining constraints for the Message class - hacking it to force new attributes would probably result in a Message that would not work as expected at all.

    Fortunatelly, instead of hardcoding the class body with a class CustomMessage statement, create your custom class with a call - that allows you to programatically define the contents. That way you don't need to use more than one class on your inheritance tree.

    All you have to do is to call Message's metaclass with the appropriate parameters, instead of the usual call to type, and pass as the class namespace -

    so you can rewrite your body-creating function to:

    def create_custom_body(key_list):
        dct = {}
        for i, k in enumerate(key_list):
            dct[k] = MessageField(CustomField, i)
        return dct
    
    CustomClass  = Message.__class__("CustomClass", (Message,), create_custom_body(key_list))
    

    This will work in this case. If the library's metaclass would use a custom namespace (i.e. it would have a __prepare__ method), though, you'd need to modify this to use types.new_class and an appropriate callback:

    from types import new_class
    
    def create_custom_body(dct, key_list):
        for i, k in enumerate(key_list):
            dct[k] = MessageField(CustomField, i)
        return dct
    
    CustomClass  = types.new_class(
        "CustomClass", (Message,), 
        exec_body=(lambda namespace: create_custom_body(namespace, key_list))
    )
    

    (check the docs at: https://docs.python.org/3/library/types.html)