How to dynamically add attributes to an interface

728 views Asked by At

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 !

2

There are 2 answers

0
Martijn Pieters On BEST ANSWER

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']
6
FastTurtle On

It seems like you want to use python's metaclasses. Use of a custom metaclass will allow you to modify the creation of a class, so you can dynamically add or modify attributes upon/before class creation. For an excellent SO answer on metaclasses see this post.

However you should try and restructure your program to avoid having to use metaclasses. Typically they should only be used if you really know you need them. In your case, is it possible to modify schema.TextLine to allow the behavior you want (perhaps by adding a hook)?