Defining circular references using zope.schema

三世轮回 提交于 2019-12-22 09:50:04

问题


I'm trying to do the following, define two classes whose instances mutually reference one another, like Users and Groups in the following exemple. A User can belong to several groups and a Group can contains several users. The actual data is stored in a database and there it is a simple matter of many-to-many relationship using foreign keys. No problem at all.

Afterward the data is loaded through an ORM and stored in instances of python objects. Still no problem at all as the ORM used (SQLAlchemy) manage backrefs.

Now I want to check that the python objects comply to some interface using zope.interface and zope.schema. That's where I get into troubles.

import zope.schema as schema
from zope.interface import Interface, implements

class IGroup(Interface):
    name = schema.TextLine(title=u"Group's name")
#    user_list = schema.List(title = u"List of Users in this group", value_type = sz.Object(IUser))

class IUser(Interface):
    name = schema.TextLine(title=u"User's name")
    group_list = schema.List(title = u"List of Groups containing that user",
        value_type = schema.Object(IGroup))

IGroup._InterfaceClass__attrs['user_list'] = zs.List(title = u"List of Users in this group", required = False, value_type = zs.Object(IUser))

class Group(object):
    implements(IGroup)

    def __init__(self, name):
        self.name = name
        self.user_list = []

class User(object):
    implements(IUser)

    def __init__(self, name):
        self.name = name
        self.group_list = []

alice = User(u'Alice')
bob = User(u'Bob')
chuck = User(u'Chuck')
group_users = Group(u"Users")
group_auditors = Group(u"Auditors")
group_administrators = Group(u"Administrators")

def add_user_in_group(user, group):
    user.group_list.append(group)
    group.user_list.append(user)

add_user_in_group(alice, group_users)
add_user_in_group(bob, group_users)
add_user_in_group(chuck, group_users)
add_user_in_group(chuck, group_auditors)
add_user_in_group(chuck, group_administrators)

for x in [alice, bob, chuck]:
    errors = schema.getValidationErrors(IUser, x)
    if errors: print errors
    print "User ", x.name, " is in groups ", [y.name for y in x.group_list]

for x in [group_users, group_auditors, group_administrators]:
    errors = schema.getValidationErrors(IGroup, x)
    if errors: print errors
    print "Group ", x.name, " contains users ", [y.name for y in x.user_list]

My problem is the commented line. I can't define IGroup using IUser because at that time IUser is not yet defined. I've found a workaround completing the definition of IGroup after the definition of IUser but that is not satisfying at all, because IUser and IGroup are defined in different source files and part of IGroup is defined in the file defining IUser.

Is there any proper way to do that using zope.schema ?


回答1:


Modify the field after definition:

#imports elided

class IFoo(Interface):
    bar = schema.Object(schema=Interface)

class IBar(Interface):
    foo = schema.Object(schema=IFoo)

IFoo['bar'].schema = IBar

Martijn's answer seems a bit more graceful and self-documenting, but this is a bit more succinct. Neither is perfect (compared to say, Django's solution of using string names for foreign keys) -- pick your poison.

IMHO, it would be nice to specify a dotted name to an interface instead of an identifier. You could pretty easily create a subclass of schema.Object to this end for your own use, should you find that approach useful.




回答2:


You could define a base, or abstract, interface for IUser:

class IAbstractUser(Interface):
    name = schema.TextLine(title=u"User's name")

class IGroup(Interface):
    name = schema.TextLine(title=u"Group's name")
    user_list = schema.List(
        title=u"List of Users in this group", 
        value_type=schema.Object(IAbstractUser))

class IUser(IAbstractUser):
    group_list = schema.List(
        title=u"List of Groups containing that user",
        value_type=schema.Object(IGroup))

Because IUser is a subclass of IAbstractUser, objects implementing the former also satisfy the latter interface.

Edit: You can always still apply sdupton's dynamic after-the-fact alteration of the IGroup interface after you defined IUser:

IGroup['user_list'].value_type.schema = IUser

I'd still use the Abstract interface pattern to facilitate better code documentation.



来源:https://stackoverflow.com/questions/9417571/defining-circular-references-using-zope-schema

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!