2012-02-23 10 views
3

私は次のことをしようとしています。インスタンスを相互に参照する2つのクラスを定義します。ユーザーは複数のグループに属し、グループには複数のユーザーが含まれます。実際のデータはデータベースに格納されており、そこには外部キーを使用する多対多関係の単純な問題があります。全く問題なし。zope.schemaを使って循環参照を定義する

その後、データは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] 

私の問題はコメント行です。私はIUserを使ってIGroupを定義することはできません。なぜなら、IUserはまだ定義されていないからです。 IUserとIGroupが異なるソースファイルで定義され、IGroupの一部がIUserを定義するファイルで定義されているため、IUserの定義後にIGroupの定義を完了するための回避策が見つかりましたが、それはまったく満足できません。

zope.schemaを使用して適切な方法がありますか?

答えて

3

は、定義した後にフィールドを変更します。

#imports elided 

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

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

IFoo['bar'].schema = IBar 

マルタインの答えは、もう少し優雅で自己文書ようだが、これはもう少し簡潔です。どちらも完璧ではありません(外部キーに文字列名を使用するDjangoの解決策と比べて) - あなたの毒を選んでください。

IMHOでは、識別子の代わりにインターフェイスにドット付きの名前を指定するとよいでしょう。そのアプローチが有用であると分かっていれば、あなた自身が使うために、この目的のためにかなり簡単にschema.Objectのサブクラスを作成することができます。

+1

私のコメントでは、Martijnの答えは実際には全く働いていないと確信しています(私の回避策はうまくいきません)。私はあなたのことが分かりません。理論的根拠:私が理解しているように、IBarは定義時にIFooのフィールドのコピーを使用して定義されているため、後でIFooを変更すると埋め込みIFooは変更されません。しかし、私はそれが本当に分かりません... – kriss

+1

はい、Pythonの元のクラスはここにサルでパッチが当てられています。したがって、インターフェイスモジュールをインポートした後のランタイム中のIFooのすべてのコンシューマは、検証とイントロスペクションのためのスキーマ。この手法は、一貫した成功を収めた複数のプロダクションプロジェクトで使用されています。 – sdupton

2

あなたたIUserのベース、または抽象インタフェースを定義することができ:IAbstractUserのサブクラスである

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

ので、前者を実現するオブジェクトも後者のインターフェイスを満たします。

編集:あなたがたIUserを定義した後、あなたは常に、まだのigroupインターフェイスのsduptonの動的な事後変更を適用することができます。

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

私はまだ良いコードのドキュメントを容易にするために、Abstractインタフェースパターンを使用すると思います。

+1

私が正しく答えを理解していれば、user_listスキーマは項目がIUser実装(つまり:nameとgroup_list)であるかどうかチェックしませんが、IAbstractUser(つまり:名前はありますがgroup_listのチェックはありません)私はそれがすでに私の現在の回避策がとにかくやっていると思うし、あなたの提案ははるかにクリーンです。実際のループに入ることなく実際の循環参照を記述する方法はありませんか? – kriss

+1

実際、user_listスキーマはIUserをチェックせず、妥協であるIAbstractUserだけをチェックします。これを@sduptonのソリューションと組み合わせて、 'user_list'を動的に変更して、IUserを定義した後にのみIUserを受け入れ、バリデーターをさらに強化することができます。これは、Pythonでの循環参照の性質です。もう半分の前に半分必要であり、それらを結びつけます。 –