polyfactory icon indicating copy to clipboard operation
polyfactory copied to clipboard

Bug: Recursive Dependency Needed for SQLAlchemyFactory Definitions?

Open josephwibowo opened this issue 11 months ago • 1 comments

Description

Hi,

I am confused when defining a 1-to-many relationship between two tables and factories on how to let the factories reference each other.

For example

class BaseFactory(SQLAlchemyFactory[T]):
    __is_base_factory__ = True
    __min_collection_length__ = 1
    __set_relationships__ = True

class companyFactory(BaseFactory):
    __model__ = Company
    id = Use(lambda: companyFactory.__faker__.password(length=10, special_chars=False, digits=True))
    company_name = Use(lambda: companyFactory.__faker__.company())


class employeeFactory(BaseFactory):
    __model__ = Employee
    employee_id = Use(lambda: employeeFactory.__faker__.password(length=10, special_chars=False, digits=True))
    company_id = Use(lambda: employeeFactory.__faker__.password(length=10, special_chars=False, digits=True))
    employee_name = Use(lambda: employeeFactory.__faker__.name())
    salary = Use(lambda: employeeFactory.__faker__.pyfloat(5, 1, True))
    addr_state = Use(lambda: employeeFactory.__faker__.state())
    company_rel = companyFactory

companyFactory.employee_rel = employeeFactory

The _rel is a relationship sqlalchemy var that defines a 1-to-many relationship between the two tables. This leads to a max recursion depth exceeded error since there is a circular dependency within the class definitions...

Let's say I take the employeeFactory rel out and run this:

def test_sqla_factory_persistence() -> None:
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    session = Session(engine)
    BaseFactory.__session__ = session

    companyFactory.create_factory(Company).create_sync()
    employeeFactory.create_factory(Employee).create_sync()

    query = session.query(Company).all()
    for row in query:
        print(row.__dict__)
    query = session.query(Employee).all()
    for row in query:
        print(row.__dict__)

I get:

{'company_name': 'White Inc', 'id': '5wD2g6Laz7'}
{ 'company_name': 'ZpxESJXfkUxmPWkUvXkd', 'id': 'jfKYEXYSZqOLelUPlRPW'}
{'salary': 45381.1, 'employee_id': 'sFfu0QVpp3', 'addr_state': 'North Dakota', 'company_id': '5wD2g6Laz7', 'employee_name': 'Daniel Johnson'}
{ 'salary': 86034.4, 'employee_id': 'SPf2GVqjS2', 'addr_state': 'Tennessee', 'company_id': 'jfKYEXYSZqOLelUPlRPW', 'employee_name': 'Ryan Bennett'}

As you can see, the 2nd company created when I called the employeeFactory create_sync() command creates a company that no longer respects the definitions I want of my companyFactory. However, the employees created do follow the rules since they use the employeeFactory. This is not what I want. I want to be able to have the created company follow the companyFactory I defined.

It's unclear to me how to do this since if I try to add the factory to the rel definition, I get the circular dependency. Please let me know if I'm missing anything obvious

URL to code causing the issue

No response

MCVE

# Your MCVE code here

Steps to reproduce

1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

Screenshots

"In the format of: ![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)"

Logs

E   RecursionError: maximum recursion depth exceeded while calling a Python object
!!! Recursion detected (same locals & position)
=========================================================================================== short test summary info ============================================================================================
FAILED test_factories.py::test_sqla_factory_persistence - RecursionError: maximum recursion depth exceeded while calling a Python object

Release Version

2.18.1

Platform

  • [ ] Linux
  • [x] Mac
  • [ ] Windows
  • [ ] Other (Please specify in the description above)

josephwibowo avatar Jan 29 '25 19:01 josephwibowo

There is a feature in polyfactory to guard against recursive relationships where possible but this is not applied to explicitly defined factory as fields.

Potential workaround would be __set_as_default_type__ docs here and remove explicitly linking to get around this. Would this work for your use case?

adhtruong avatar Feb 05 '25 20:02 adhtruong