我正在使用SQLalchemy关联对象模式(
http://docs.sqlalchemy.org/en/rel_1_1/orm/basic_relationships.html#association-object)用于三个模型类.
基本关系在左侧一个用户可以属于多个组织.我正在关联对象类中存储额外的用户组织相关数据.然后,关联对象类将多对一映射到组织.
从SQLAlchemy来看,这种关系很好.问题是测试这个与工厂男孩已经证明是困难的并且总是导致错误RecursionError:超出最大递归深度.
以下是关联对象关系的三个模型,其中User是父级,Child是Organization:
class MemberOrgsAssoc(Model):
"""The left side of the relationship maps a User as a one-to-many to
Organizations. User-Organization relevant data is stored in
this association-object table. Then, there is a one-to-many from
this association-object table to the Organization table. """
__tablename__ = 'member_orgs'
member_id = Column(db.Integer, db.ForeignKey("users.id"), primary_key=True)
org_id = Column(db.Integer, db.ForeignKey("organizations.id"), primary_key=True)
manager_id = Column(db.Integer, db.ForeignKey("users.id"))
org_title = Column(db.Unicode(50))
organization = relationship("Organization", back_populates="members")
member = relationship("User", back_populates="organizations",
foreign_keys=[member_id])
manager = relationship("User", back_populates="subordinates",
foreign_keys=[manager_id])
class User(SurrogatePK, Model):
"""A user of the app."""
__tablename__ = 'users'
username = Column(db.Unicode(80), unique=True, nullable=False)
organizations = relationship("MemberOrgsAssoc", back_populates="member",
primaryjoin = "member_orgs.c.member_id == User.id",
lazy="dynamic")
subordinates = relationship("MemberOrgsAssoc", back_populates="manager",
primaryjoin = "member_orgs.c.manager_id == User.id",
lazy="dynamic")
class Organization(SurrogatePK, Model):
"""An organization that Users may belong to."""
__tablename__ = 'organizations'
name = Column(db.Unicode(128), nullable=False)
members = relationship("MemberOrgsAssoc", back_populates="organization")
因此,以上所有SQLAlchemy模型类和关系似乎都按预期工作.
以下是我试图创作的三个工厂男孩班.
MemberOrgs association-object工厂:
class MemberOrgsAssocFactory(BaseFactory):
"""Association-object table Factory"""
class Meta:
"""Factory config"""
model = MemberOrgsAssoc
member_id = factory.SubFactory('tests.factories.UserFactory')
org_id = factory.SubFactory('tests.factories.OrganizationFactory')
manager_id = factory.SubFactory('tests.factories.UserFactory')
org_title = Sequence(lambda n: 'CEO{0}'.format(n))
organization = factory.SubFactory('tests.factories.OrganizationFactory')
member = factory.SubFactory('tests.factories.UserFactory')
manager = factory.SubFactory('tests.factories.UserFactory')
class UserFactory(BaseFactory):
"""User factory."""
class Meta:
"""Factory configuration."""
model = User
username = Sequence(lambda n: 'user{0}'.format(n))
organizations = factory.List(
[factory.SubFactory('tests.factories.MemberOrgsAssocFactory')])
subordinates = factory.List(
[factory.SubFactory('tests.factories.MemberOrgsAssocFactory')])
class OrganizationFactory(BaseFactory):
"""Company factory"""
class Meta:
"""Factory config"""
model = Organization
id = Sequence(lambda n: '{0}'.format(n))
name = Sequence(lambda n: 'company{0}'.format(n))
members = factory.List(
[factory.SubFactory('tests.factories.MemberOrgsAssocFactory')])
最后,需要让用户进行测试,因此下面是制作用户的pytest fixture.这是由于“RecursionError:超出最大递归深度”导致测试失败的地方.
@pytest.fixture(scope='function')
def user(db):
"""An user for the unit tests.
setup reference: https://github.com/FactoryBoy/factory_boy/issues/101
# how to handle self referential foreign key relation in factory boy
# https://github.com/FactoryBoy/factory_boy/issues/173
"""
user = UserFactory(
organizations__0=None,
subordinates__0=None,
)
a = MemberOrgsAssocFactory(
is_org_admin=True,
is_default_org=True,
is_active=True,
)
a.organization=OrganizationFactory()
user.organizations.append(a)
db.session.commit()
return user
错误信息:
E RecursionError: maximum recursion depth exceeded
!!! Recursion detected (same locals & position)
最佳答案 或多或少地解决了这个问题,虽然总体上有点脆弱.必须仔细遵循所需的模式
as laid out in the sqlalchemy docs:
""" EXAMPLE USE:
# create User object, append an Organization object via association
p = User()
a = MemberOrgsAssoc(extra_data="some data")
a.organization = Organization()
p.organizations.append(a)
# iterate through Organization objects via association, including association attributes:
for assoc in p.organizations:
print(assoc.extra_data)
print(assoc.child)
"""
下面对pytest fixture的更改解决了RecursionError问题并使其正常工作:
@pytest.fixture(scope='function')
def user(db):
"""An user for the tests."""
user = UserFactory(
organizations='',
subordinates=''
)
a = MemberOrgsAssocFactory(
member_id=None,
org_id=None,
manager_id=None,
is_org_admin=True,
is_default_org=True,
is_active=True,
organization=None,
member=None,
manager=None
)
a.organization = OrganizationFactory(members=[])
user.organizations.append(a)
db.session.commit()
# debugging
# thisuser = User.get_by_id(user.id)
# for assoc in thisuser.organizations:
# if assoc.is_default_org:
# print('The default organization of thisuser is -> {}'.format(assoc.organization.name))
return user