Определение циклических ссылок с помощью zope.schema - PullRequest
3 голосов
/ 23 февраля 2012

Я пытаюсь сделать следующее, определить два класса, чьи экземпляры взаимно ссылаются друг на друга, как «Пользователи» и «Группы» в следующем примере.Пользователь может принадлежать к нескольким группам, а группа может содержать несколько пользователей.Фактические данные хранятся в базе данных, и здесь очень просто установить отношение «многие ко многим» с использованием внешних ключей.Никаких проблем.

После этого данные загружаются через ORM и сохраняются в экземплярах объектов Python.Все еще не проблема, поскольку ORM (SQLAlchemy) управляет обратными ссылками.

Теперь я хочу проверить соответствие объектов python какому-либо интерфейсу, используя zope.interface и zope.schema.Вот тут я и попадаю в неприятности.

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]

Моя проблема - закомментированная строка.Я не могу определить IGroup, используя IUser, потому что в то время IUser еще не определен.Я нашел обходной путь, завершающий определение IGroup после определения IUser, но это совсем не удовлетворяет, потому что IUser и IGroup определены в разных исходных файлах, а часть IGroup определена в файле, определяющем IUser.

Есть ли какой-нибудь правильный способ сделать это, используя zope.schema?

Ответы [ 2 ]

3 голосов
/ 24 февраля 2012

Изменить поле после определения:

#imports elided

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

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

IFoo['bar'].schema = IBar

Ответ Мартина кажется немного более изящным и самодокументированным, но это немного более лаконично. Ни то, ни другое не является идеальным (по сравнению, скажем, решение Django использовать имена строк для внешних ключей) - выберите свой яд.

ИМХО, было бы неплохо указать пунктирное имя для интерфейса вместо идентификатора. Вы можете довольно легко создать подкласс схемы. С этой целью вы можете использовать его самостоятельно, если этот подход окажется полезным.

2 голосов
/ 24 февраля 2012

Вы можете определить базовый или абстрактный интерфейс для 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))

Поскольку IUser является подклассом IAbstractUser, объекты, реализующие первый, также удовлетворяют второму интерфейсу.

Редактировать: Вы всегда можете применить динамическое постфактумное изменение интерфейса IGroup от sdupton после того, как вы определили IUser:

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

Я бы по-прежнему использовал шаблон интерфейса Abstract для упрощения документирования кода.

...