How to use factory boy to test SQLalchemy association object models?

I am using the SQLalchemy object association pattern ( http://docs.sqlalchemy.org/en/rel_1_1/orm/basic_relationships.html#association-object ) for the three model classes.

The main relationship is on the left side. A user can belong to several organizations. I store additional data related to the user organization in the association object class. The association-object-class then maps many-to-one to the Organization.

From SQLAlchemy's point, this relationship works fine. The problem is to check this with the factory, the boy turned out to be complicated and always leads to an error RecursionError: maximum recursion depth exceeded.

Below are three models for the relationship of an association object, where User is parent and Child is 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")

So, all of the above classes and relationships of the SQLAlchemy model seem to work as intended.

Below are three factory -boy classes I'm trying to do.

MemberOrgs factory association member:

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')])

Finally, you need to make the user for tests, and below is the pytest application to make the user. Here the tests fail due to the error "RecursionError: maximum recursion depth".

@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

Error message:

E   RecursionError: maximum recursion depth exceeded
!!! Recursion detected (same locals & position)
+4
source share
1 answer

, . sqlalchemy:

""" 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
+1

Source: https://habr.com/ru/post/1681654/


All Articles