pythonplonezope.interface

How to dynamically add attributes to an interface


I need to add an attribute for every attribute in an interface. So I am trying to dynamically modify it to add them, but not with much success for now.

Let's say I have the following interface:

class IMember(Interface):
    first_name = schema.TextLine(title=u'first name')
    last_name = schema.TextLine(title=u'last name')

And I would like to modify it like that:

class IMember(Interface):
    first_name = schema.TextLine(title=u'first name')
    last_name = schema.TextLine(title=u'last name')
    visbility_first_name = schema.Bool(title=u'Display: first name')
    visbility_last_name = schema.Bool(title=u'Display: last name')

I tried modifying the class afterwards, but as it was already initialized, the schema was set and I was not sure how to change it. I also thought about writing a directive (e.g.: interface.Implements()) but it seems quite complicated to do just to add attributes.

My final goal is to add a z3c.form fieldset with a set of Bool widgets.

So, is there a way to do it in Python, or do I have to modify the interface and add all the attributes manually ?

Thanks !


Solution

  • You can create a dynamic subclass of the interface using the InterfaceClass metatype.

    Create a dictionary of the additional schema fields:

    fields = {}
    for name, attr in IMember.namesAndDescriptions():
        if isinstance(attr, schema.Field):
            fields['visible_' + name] = schema.Bool(title=u'Display: ' + attr.title)
    

    Now you can create a dynamic interface subclassing your existing interface:

    from zope.interface.interface import InterfaceClass
    
    IMemberExtended = InterfaceClass('IMemberExtended', (IMember,), fields)
    

    This can all be wrapped up in a class decorator if you so desire:

    from zope.interface.interface import InterfaceClass
    from zope import schema
    
    def add_visibility_fields(iface):            
        fields = {}
        for name, attr in iface.namesAndDescriptions():
            if isinstance(attr, schema.Field):
                fields['visible_' + name] = schema.Bool(title=u'Display: ' + attr.title)
    
        return InterfaceClass(iface.__name__, (iface,), fields)
    

    which you'd use on your existing interface:

    @add_visibility_fields
    class IMember(Interface):
        first_name = schema.TextLine(title=u'first name')
        last_name = schema.TextLine(title=u'last name')
    

    This creates a subclass; you can also replace the whole interface with the generated interface:

    def add_visibility_fields(iface):            
        fields = {}
        for name, attr in iface.namesAndDescriptions():
            fields[name] = attr
            if isinstance(attr, schema.Field):
                fields['visible_' + name] = schema.Bool(title=u'Display: ' + attr.title)
    
        return InterfaceClass(iface.__name__, iface.__bases__, fields)
    

    Demo of that last version:

    >>> @add_visibility_fields
    ... class IMember(Interface):
    ...     first_name = schema.TextLine(title=u'first name')
    ...     last_name = schema.TextLine(title=u'last name')
    ... 
    >>> IMember.names()
    ['visible_last_name', 'first_name', 'last_name', 'visible_first_name']