Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Passing object as Param results in Cyclic lazy attribute definition error #1065

Open
alxvallejo opened this issue Mar 7, 2024 · 1 comment
Labels

Comments

@alxvallejo
Copy link

alxvallejo commented Mar 7, 2024

The problem

I have some Django Model Factories defined as such:

class TripFactory(DjangoModelFactory):
    class Meta:
        model = Trip

    class Params:
        operator = factory.SubFactory(OperatorFactory)

    trip_id = "100"
    operator_service_day = factory.SubFactory(
        OperatorServiceDayFactory, operator=factory.SelfAttribute('operator')
    )

and the OperatorServiceDayFactory:

class OperatorServiceDayFactory(DjangoModelFactory):
    class Meta:
        model = OperatorServiceDay

    class Params:
        operator = factory.SubFactory(OperatorFactory)

    operator = factory.SelfAttribute("operator")

What I would like to do is call an operator instance from my DB and pass it through to the Factories:

        operator = Operator.objects.filter(id=1).first()
        trip = TripFactory.create(
            operator=operator,
        )

This results in:

raise errors.CyclicDefinitionError(
factory.errors.CyclicDefinitionError: Cyclic lazy attribute definition for 'operator'; cycle found in ['operator'].

Proposed solution

It isn't clear from the Params documentation on how to pass an object to the Params and use it instead of the SubFactory declaration.
I'm not sure how to proceed and I just need an Operator object passed down to my nested Factories.

@alxvallejo alxvallejo changed the title Passing SubFactory as Param results in Cyclic lazy attribute definition error Passing object as Param results in Cyclic lazy attribute definition error Mar 7, 2024
@rbarrois
Copy link
Member

rbarrois commented Mar 8, 2024

There are two issues in your factories:

  • In TripFactory, when you write factory.SubFactory(operator=factory.SelfAttribute("operator")), it translates to OperatorServiceDayFactory.operator = OperatorServiceDayFactory.operator — that's a cyclical one. Instead, use factory.SelfAttribute("..operator") to fetch the value passed to TripFactory
  • Similarly, in OperatorServiceDayFactory, the operator = factory.SelfAttribute("operator") creates a loop. The class Params is intended for keyword arguments that will be used to define the other fields of the instance, but should not be passed to the instance. If the parameter is a field of the model, just declare it in the main declarations — no need to put it in class Params.

Here is how I would declare those factories:

class TripFactory(DjangoModelFactory):
    class Meta:
        model = Trip

    # If `Trip` has an `operator` field:
    operator = factory.SubFactory(OperatorFactory)

    # If `Trip` doesn't have an `operator` field, but you want to be able to
    # call `TripFactory(operator=my_operator)`
    # instead of `TripFactory(operator_service_day__operator=my_operator)`:
    class Params:
        operator = factory.SubFactory(OperatorFactory)

    trip_id = "100"
    operator_service_day = factory.SubFactory(
        OperatorServiceDayFactory,
        # Forward TripFactory.operator to OperatorServiceDayFactory.operator
        operator=factory.SelfAttribute('..operator'),
    )

class OperatorServiceDayFactory(DjangoModelFactory):
    class Meta:
        model = OperatorServiceDay

    # Create an operator, unless one is explictly provided when calling the factory
    operator = factory.SubFactory(OperatorFactory)

@rbarrois rbarrois added the Q&A label Mar 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants