SelfAttributes Fail When Called Through PostGeneration functions
See original GitHub issueI 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:
- Created 6 years ago
- Comments:8 (2 by maintainers)
Top 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 >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@rbarrois Huh, ok so adding an extra
.
on theSelfAttribute
works starting in version2.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 aboutParams
or if you have any suggestions on how to set it up cleaner.Example Scenario
@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 theextracted
field of thepost_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 callingSelfAttribute('..thing2')
, the..
part goes up a level toFooFactory
, not toBooFactory
; usingfactory.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 withclass Params
for passing in parameters.