4

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 !

Gagaro
  • 752
  • 7
  • 17
  • Are you sure your additional attributes need to be first class fields on the schema? Maybe [Tagged Values](https://developer.plone.org/components/interfaces.html#tagged-values) on the interface could work for you. – Lukas Graf Aug 08 '13 at 15:13
  • Sorry, on mobile, having trouble posting the right links: http://docs.zope.org/zope.interface/README.html#tagged-values – Lukas Graf Aug 08 '13 at 15:16
  • My final goal is to have a fieldset in z3c.form with a set of Bool widgets. I don't think tagged values will be helpful in this case. – Gagaro Aug 08 '13 at 15:30

2 Answers2

6

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']
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
0

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)?

Community
  • 1
  • 1
FastTurtle
  • 2,301
  • 19
  • 19
  • `zope.interface` already makes heavy use of metaclasses though, it'll be... tricky to integrate with that. – Martijn Pieters Aug 08 '13 at 15:23
  • Yeah, using metaclasses is tricky to begin with, I can't even imagine how much of a headache trying to do metaclass inheritance must be... All the more reason to find a solution that doesn't involve them! – FastTurtle Aug 08 '13 at 15:29
  • Yeah it seems quite complicated to implement, having an easier solution would be great :). – Gagaro Aug 08 '13 at 15:33
  • @FastTurtle: You can just use the metaclass to produce a new interface object; see my answer. – Martijn Pieters Aug 08 '13 at 15:54
  • @FastTurtle: also note that a Zope and Plone lean *heavily* on interfaces and schemas; avoiding these would be akin to trying to swim up a waterfall. – Martijn Pieters Aug 08 '13 at 15:56
  • 1
    @MartijnPieters Your answer looks great. I like your class decorator and usage of Zope's `InterfaceClass`. I think I'll steal this pattern for the future haha. – FastTurtle Aug 08 '13 at 16:08