I'm using the pythons factory_boy package to create instances of models for testing purposes.
I want to pass the parameters used when calling Facotry.create() to all the SubFactories in the Factory being called.
Here's how I do it now:
Example 1:
I have to explicitly set the company
when calling the SubFactory (the BagFactory
)
class BagTrackerFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
class BagFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
Example 2:
In this example, I have to add company
to Params
in the BagFactory
, so I can pass it down to ItemFactory
which has the company
parameter.
class BagTrackerFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
class BagFactory(BaseFactory):
item = factory.SubFactory(ItemFactory, company=factory.SelfAttribute("..company"))
class Params:
company = factory.SubFactory(CompanyFactory)
class ItemFactory(BaseFactory):
company = factory.SubFactory(CompanyFactory)
The reason why I do it like this is that it saves time and it makes sense that the Bag
belongs to the same company
as the BagTracker
when created by the same Factory.
Note: the BaseFactory
is factory.alchemy.SQLAlchemyModelFactory
Question:
What I would like is to have company
(and all the other parameters) from the parent Factory be passed down to SubFactories without having to pass it explicitly. And this continues downstream all the way to the last SubFactory, so every model has the same company
, from the topmost parent Factory to the lowest child SubFactory. I hope you understand what I'm saying.
Is there an easy way to do this? Like some option in the factory-boy package?
EDIT:
I ended up doing it the long way, passing down parameters manually. In this example, I'm showing both cases: when the parent factory has the company
parameter(BagTrackerFactory
) and doesn't have it but must pass it downstream (BagFactory
).
class CompanyFactory(BaseFactory):
id = get_sequence()
class Meta:
model = Company
class ItemFactory(BaseFactory):
id = get_sequence()
owner = factory.SubFactory(CompanyFactory)
owner_id = factory.SelfAttribute("owner.id")
class Meta:
model = Item
class BagFactory(BaseFactory):
id = get_sequence()
item = factory.SubFactory(ItemFactory, owner=factory.SelfAttribute("..company"))
item_id = factory.SelfAttribute("item.id")
class Params:
company = factory.SubFactory(CompanyFactory)
class Meta:
model = Bag
class BagTrackerFactory(BaseFactory):
id = get_sequence()
company = factory.SubFactory(CompanyFactory)
company_id = factory.SelfAttribute("company.id")
item = factory.SubFactory(ItemFactory, owner=factory.SelfAttribute("..company"))
item_id = factory.SelfAttribute("item.id")
bag = factory.SubFactory(BagFactory, company=factory.SelfAttribute("..company"))
bag_id = factory.SelfAttribute("bag.id")
class Meta:
model = BagTracker
This is possible, but will have to be done specifically for your codebase.
At its core, a factory has no knowledge of your models' specific structure, hence can't forward the
company
field — for instance, some models might not accept that field in their__init__
, and providing the field would crash.However, if you've got a chain where the field is always accepted, you may use the following pattern:
This works thanks to the
factory_parent
attribute of the stub used when building a factory's parameters: https://factoryboy.readthedocs.io/en/stable/reference.html#parents This field either points to the parent (when the current factory is called as aSubFactory
), or toNone
. With afactory.Maybe
, we can copy the value through afactory.SelfAttribue
when a parent is defined, and instantiate a new value.This can be used afterwards in your code:
If some models must pass the
company
value to their subfactories, but without acompany
field themselves, you may also split the specialWithCompanyFactory
into two classes:WithCompanyFieldFactory
andCompanyPassThroughFactory
: