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.

SelfAttributes Fail When Called Through PostGeneration functions

See original GitHub issue

I have a simplified set of factory definitions below. If i try to create a WorkOrderKit object, via WorkOrderKitFactory(), it successfully generates a workorderkit with factory_boy 2.6.1 but fails with 2.9.2. I’m wondering if this is a bug or if it worked unintentionally before and this is the intended behavior. (If it is the intended behavior, do you have any suggestions on achieving this behavior now?)

The whole example django project: https://bitbucket.org/marky1991/factory-test/ .

If you would like to test it yourself, checkout the project, setup the database, run setup_db.psql, and then run factory_test/factory_test/factory_test_app/test.py.

Please let me know if anything is unclear or if you have any questions.

import factory
from factory.declarations import SubFactory, SelfAttribute
from factory.fuzzy import FuzzyText, FuzzyChoice
from factory_test_app import models

class ItemFactory(factory.DjangoModelFactory):
    class Meta:
        model = models.Item

    barcode = factory.fuzzy.FuzzyText(length=10)

class OrderHdrFactory(factory.DjangoModelFactory):
    order_nbr = factory.fuzzy.FuzzyText(length=20)
    class Meta:
        model = models.OrderHdr

    @factory.post_generation
    def order_dtls(self, create, extracted, **kwargs):
        if not create:
            return

        if extracted is not None:
            for order_dtl in extracted:
                order_dtl.order = self
                author.save()
            return
        for _ in range(5):
            OrderDtlFactory(order=self,
                            **kwargs)

class WorkOrderKitFactory(factory.DjangoModelFactory):
    class Meta:
        model = models.WorkOrderKit
    
    work_order_nbr = factory.fuzzy.FuzzyText(length=20)
    item = SubFactory(ItemFactory)
    sales_order = SubFactory(OrderHdrFactory,
                             order_dtls__item=SelfAttribute("..item"))

class OrderDtlFactory(factory.DjangoModelFactory):
    class Meta:
        model = models.OrderDtl

    order = SubFactory(OrderHdrFactory,
                       order_dtls=[])
    item = SubFactory(ItemFactory)

The traceback in 2.9.1:

Traceback (most recent call last):
  File "factory_test_app/test.py", line 8, in <module>
    kit = WorkOrderKitFactory()
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/base.py", line 46, in __call__
    return cls.create(**kwargs)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/base.py", line 568, in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/base.py", line 505, in _generate
    return step.build()
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/builder.py", line 275, in build
    step.resolve(pre)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/builder.py", line 224, in resolve
    self.attributes[field_name] = getattr(self.stub, field_name)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/builder.py", line 366, in __getattr__
    extra=declaration.context,
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/declarations.py", line 306, in evaluate
    return self.generate(step, defaults)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/declarations.py", line 395, in generate
    return step.recurse(subfactory, params, force_sequence=force_sequence)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/builder.py", line 236, in recurse
    return builder.build(parent_step=self, force_sequence=force_sequence)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/builder.py", line 296, in build
    context=postgen_context,
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/declarations.py", line 570, in call
    instance, create, context.value, **context.extra)
  File "/home/lgfdev/factory_test/factory_test/factory_test_app/factories.py", line 29, in order_dtls
    **kwargs)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/base.py", line 46, in __call__
    return cls.create(**kwargs)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/base.py", line 568, in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/base.py", line 505, in _generate
    return step.build()
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/builder.py", line 275, in build
    step.resolve(pre)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/builder.py", line 224, in resolve
    self.attributes[field_name] = getattr(self.stub, field_name)
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/builder.py", line 366, in __getattr__
    extra=declaration.context,
  File "/home/lgfdev/ve_factory_test/local/lib/python2.7/site-packages/factory/declarations.py", line 137, in evaluate
    target = step.chain[self.depth - 1]
IndexError: tuple index out of range

Issue Analytics

  • State:open
  • Created 6 years ago
  • Comments:8 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
AlecRosenbaumcommented, Sep 16, 2019

@rbarrois Huh, ok so adding an extra . on the SelfAttribute works starting in version 2.11.0 but doesn’t work in lower versions. Thanks!

The situation I am setting up is a little different from the previous example, so I’ll put some runnable code (with pytest) here documenting the use case. I’m not sure how I would use Params, since the code has to run after creation. Please let me know if I’m wrong about Params or if you have any suggestions on how to set it up cleaner.

Example Scenario

import factory

# ==============
# --- Models ---
# ==============

RELATIONSHIPS = set()


def rel_key(_from, to, type):
    return "{}::{}::{}".format(_from, to, type)


class User(object):
    def __init__(self, name):
        self.name = name

    def add_to_organization(self, org):
        # some mumbo-jumbo here
        RELATIONSHIPS.add(rel_key(_from=self.name, to=org.name, type="member"))

    def member_of_organization(self, org):
        key = rel_key(_from=self.name, to=org.name, type="member")
        return key in RELATIONSHIPS


class Organization(object):
    def __init__(self, name):
        self.name = name


class Event(object):
    def __init__(self, organization, user):
        self.organization = organization
        self.user = user


# =================
# --- Factories ---
# =================


class UserFactory(factory.Factory):
    name = "Johnny Smith"

    class Meta:
        model = User

    @factory.post_generation
    def organization(user_inst, create, extracted, **kwargs):
        if create and extracted:
            user_inst.add_to_organization(extracted)


class OrganizationFactory(factory.Factory):
    name = "The Smith Organization"

    class Meta:
        model = Organization


class EventFactory(factory.Factory):
    organization = OrganizationFactory()
    user = factory.SubFactory(
        UserFactory, organization=factory.SelfAttribute("...organization")
    )

    class Meta:
        model = Event


# ====================
# --- Example Test ---
# ====================


def test_event():
    my_event = EventFactory()

    assert my_event.organization
    assert my_event.user
    assert my_event.user.member_of_organization(my_event.organization)

0reactions
rbarroiscommented, Sep 16, 2019

@AlecRosenbaum could you share some code on the kind of issue you’re seeing?

In @Subaku example code, what happens is that the overridden install value is designed to be passed into the extracted field of the post_generation method; and resolved within the context of that method’s parameters.

In other words, there is an intermediate level where parameters for def install() are computed (required for cases where more parameters are provided to that declaration, where each parameter might use values from other parameters). When calling SelfAttribute('..thing2'), the .. part goes up a level to FooFactory, not to BooFactory; using factory.SelfAttribute('...thing2') should work.

However, passing in a pre-generation declaration (SelfAttribute here) to shadow a post-generation declaration makes the factories much harder to read, and could fail in unexpected ways; I recommend designing the factory differently, for instance playing with class Params for passing in parameters.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Reference — Factory Boy stable documentation
PostGeneration : this class allows calling a given function with the generated object as argument. post_generation() : decorator performing the same functions ......
Read more >
Passing an object created with SubFactory and LazyAttribute ...
SelfAttribute ('object') related__otherrelated__param = factory. ... it seems RelatedFactory calls are executed before PostGeneration calls.
Read more >
Factory Boy Documentation - Read the Docs
Once defined, a factory can be instantiated through different methods: ... PostGeneration: this class allows calling a given function with ...
Read more >
pytest-factoryboy - PyPI
Fixtures are contributed to the same module where register function is called. Factory Fixture. Factory fixtures allow using factories without importing them.
Read more >
Test factory functions in Django - lukeplant.me.uk
Using Django ORM create calls directly in your tests is not a great solution, because database constraints often force you to specify fields ......
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