question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Help me understand: factory.errors.CyclicDefinitionError: Cyclic lazy attribute definition for

See original GitHub issue

In the code below, I can create a NotificationEvent, but I cannot create a Notification, if I also try to create the NotificationEvent.

I’ve tried several different things, but I’m finally stuck with this error: factory.errors.CyclicDefinitionError: Cyclic lazy attribute definition for user_account; cycle found in ['account', 'user_account'].

Sadly there is not much in the way of docs to help solve what is going on here. I’m going to try using Params and see if that helps, but I think it does mostly the same as what I have below.

Is it possible to get some more documentation around this error and around how you create Objects that have some nesting, but can be used without the nesting?

# encoding: utf-8

import factory
from .uniregistry_model_factory_base import UniregistryModelFactoryBase
from uniregistrar.models.db import DBSession
from uniregistrar.factories import UserAccountFactory

from notification.models import (Notification, NotificationEvent, NotificationSubcategory,
                                 NotificationSeverity)

class NotificationEventFactory(UniregistryModelFactoryBase):
    class Meta:
        model = NotificationEvent
        sqlalchemy_session = DBSession
        exclude = ("user_account", )

    user_account = factory.SubFactory(UserAccountFactory)
    subcategory = "domain-sold"
    severity = "info"
    account = factory.SelfAttribute("user_account.account")
    message = factory.Sequence(lambda n: "Message %d" % n)
    object = factory.Sequence(lambda n: "Object %d" % n)
    url = factory.Sequence(lambda n: "https://uni.com/url?p=%d" % n)

    @classmethod
    def attributes(cls, create=False, extra=None):
        if 'account' in extra:
            raise ValueError("Pass a user_account, not individual account values")

        return super(NotificationEventFactory, cls).attributes(create, extra)

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        return super(NotificationEventFactory, cls)._create(model_class, *args, **kwargs)


class NotificationFactory(UniregistryModelFactoryBase):
    class Meta:
        model = Notification
        sqlalchemy_session = DBSession
        exclude = ("user_account", )

    user_account = factory.SubFactory(UserAccountFactory)
    user = factory.SelfAttribute("user_account.user")
    event = factory.SubFactory(NotificationEventFactory,
                               user_account=factory.SelfAttribute("user_account"))
    # website_show = defaults to True in the DB
    # viewed_date = timestamp, defaults to NULL in the DB
    # notified_date = timestamp defaults to NULL in the DB

    @classmethod
    def attributes(cls, create=False, extra=None):
        if 'user' in extra:
            raise ValueError("Pass a user_account, not individual user values")

        return super(NotificationFactory, cls).attributes(create, extra)

    @classmethod
    def _create(cls, model_class, *args, **kwargs):
        return super(NotificationFactory, cls)._create(model_class, *args, **kwargs)

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:2
  • Comments:5 (1 by maintainers)

github_iconTop GitHub Comments

8reactions
rbarroiscommented, Nov 12, 2018

Hi! Indeed, we should write a « solving common errors » section in the docs — any help would be more than welcome!

Regarding your issue, let’s walk through the messages.

The error

You get an error saying “I find a cyclic definition: the value of user_account depends on the value of account, which itself relies on user_account”.

Under the hood, factory_boy starts by collecting all declarations from the factory it’s going to instantiate, and walks through all those attributes to “resolve” them (i.e convert a declaration into an actual value). Since some fields might depend on the value of other fields, we keep a list of « attributes currently being computed »: if A depends on B, and B on C, and C on A, we’ll raise when we discover that C is trying to access the value of A. => That’s the issue you see here.

The cause

So, where do we have both account depending on user_account, and user_account on user_account?

When you call your NotificationFactory, it tries to instantiate a NotificationEventFactory with an extra declaration, user_account=factory.SelfAttribute("user_account"). This extra declaration is added to the list of declarations of the NotificationEventFactory during that instantiation.

This is equivalent to the following (shortened) factory:

class NotificationEventFactory(factory.Factory):
    class Meta:
        model = NotificationEvent

    account = factory.SelfAttribute('user_account.account')
    user_account = factory.SelfAttribute('user_account')

Note how:

  • the user_account=factory.SelfAttribute() passed in the SubFactory shadows the initial user_account = factory.SubFactory() from the initial class declaration;
  • Your declaration says that “user_account takes the value of user_account”. => Your issue is in that second point

The solution

From your description, when building a Notification, you want the NotificationEvent to use the same UserAccount as the Notification.

The proper declaration is thus:

class NotificationFactory(factory.Factory):
    class Meta:
        model = Notification

    user_account = factory.SubFactory(UserAccountFactory)
    user = factory.SelfAttribute('user_account.user')  # Pick the 'user' from Notification.user_account
    event = factory.SubFactory(
        NotificationEvent,
        # Retrieve the user_account from the *calling* Notification
        user_account=factory.SelfAttribute('..user_account'),
    )

Basically, any extra declarations added in a SubFactory are evaluated from the point of view of the SubFactory; we need to get back to the factory calling our SubFactory, thus the ..user_account

1reaction
mark0978commented, Nov 12, 2018

That last part was what I had figured out and you did indeed confirm what I was suspecting. The lazy part of that declaration is so lazy it doesn’t get evaluated when the SubFactory “call” is made, but when it is actually used within the SubFactory and that is why you need the … at the beginning of the SelfAttribute.

On Mon, Nov 12, 2018 at 5:00 AM Raphaël Barrois notifications@github.com wrote:

Hi! Indeed, we should write a « solving common errors » section in the docs — any help would be more than welcome!

Regarding your issue, let’s walk through the messages. The error

You get an error saying “I find a cyclic definition: the value of user_account depends on the value of account, which itself relies on user_account”.

Under the hood, factory_boy starts by collecting all declarations from the factory it’s going to instantiate, and walks through all those attributes to “resolve” them (i.e convert a declaration into an actual value). Since some fields might depend on the value of other fields, we keep a list of « attributes currently being computed »: if A depends on B, and B on C, and C on A, we’ll raise when we discover that C is trying to access the value of A. => That’s the issue you see here. The cause

So, where do we have both account depending on user_account, and user_account on user_account?

When you call your NotificationFactory, it tries to instantiate a NotificationEventFactory with an extra declaration, user_account=factory.SelfAttribute(“user_account”). This extra declaration is added to the list of declarations of the NotificationEventFactory during that instantiation.

This is equivalent to the following (shortened) factory:

class NotificationEventFactory(factory.Factory): class Meta: model = NotificationEvent

account = factory.SelfAttribute('user_account.account')
user_account = factory.SelfAttribute('user_account')

Note how:

  • the user_account=factory.SelfAttribute() passed in the SubFactory shadows the initial user_account = factory.SubFactory() from the initial class declaration;
  • Your declaration says that “user_account takes the value of user_account”. => Your issue is in that second point

The solution

From your description, when building a Notification, you want the NotificationEvent to use the same UserAccount as the Notification.

The proper declaration is thus:

class NotificationFactory(factory.Factory): class Meta: model = Notification

user_account = factory.SubFactory(UserAccountFactory)
user = factory.SelfAttribute('user_account.user')  # Pick the 'user' from Notification.user_account
event = factory.SubFactory(
    NotificationEvent,
    # Retrieve the user_account from the *calling* Notification
    user_account=factory.SelfAttribute('..user_account'),
)

Basically, any extra declarations added in a SubFactory are evaluated from the point of view of the SubFactory; we need to get back to the factory calling our SubFactory, thus the …user_account

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/FactoryBoy/factory_boy/issues/540#issuecomment-437822153, or mute the thread https://github.com/notifications/unsubscribe-auth/AAGepoEQw-gOGRZOOcll_GDeI6hUP3Tqks5uuUa1gaJpZM4YXwFm .

Read more comments on GitHub >

github_iconTop Results From Across the Web

django - How to resolve CyclicDefinitionError in factory_boy ...
I've found solution. ModelBFactory should look like this: #in ModelB_App/factories ...
Read more >
How To Resolve Cyclicdefinitionerror In Factoryboy ... - ADocLib
[Read fixes] Steps to fix this factoryboy exception: Full details: errors.CyclicDefinitionError: Cyclic lazy attribute definition for %r; cycle found.
Read more >
Reference — Factory Boy stable documentation
This attribute indicates that the Factory subclass should not be used to generate objects, but instead provides some extra defaults. It will be...
Read more >
Model Your Models With Factory Boy (Setting Up Chained ...
A tip for improving tests: Subclassed Factories; Final Code Samples; Common Errors. TypeError: "my_attribute" is an invalid keyword argument ...
Read more >
Synthesizing Realistic Substitute Data for a Law Enforcement ...
Instead, we should use the safer method, or what is known as the lazy attribute, which will define the attributes each time the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found