factory_boy
factory_boy copied to clipboard
Lost Dict params when using Traits
For explanation purpose I've created some simple code:
class Y:
def __init__(self, a: int, b: str):
self.a = a
self.b = b
class X:
def __init__(self, y: {str:[Y]}):
self.y = y
class YItemFactory(factory.Factory):
class Meta:
model = Y
a = 1
b = 'c'
class XItemFactory(factory.Factory):
class Meta:
model = X
y = factory.Dict({
'i': factory.List([factory.SubFactory(YItemFactory)]),
'j': factory.List([factory.SubFactory(YItemFactory)]),
})
class Params:
u = factory.Trait(
y__i=factory.List([
factory.SubFactory(YItemFactory, a=2)
]),
)
now, creating instances of X model, using u-param, it's losing part of given dictionary. Is it
print(XItemFactory.create().y)
gives {'j': [<tests.entity.Y object at 0x7f9887f6e710>]}, and
print(XItemFactory.create(u=True).y)
gives {'i': [<tests.entity.Y object at 0x7f9887f6eac8>], 'j': [<tests.entity.Y object at 0x7f9887f6ecc0>]}
OK, that's a tricky one :)
I think this was the same issue as #466 / #446 — which I fixed in the latest release, 2.11.0.
Could you check it and see if the issue still occurs?
Thanks :-)
This appears to be the same bug, still happening with factory-boy 3.2.1.
If a factory defines a subfactory, and a trait is defined which uses double-underscores to set an attribute in the subfactory, the subfactory will not use its defined default for that attribute even when the trait is not used.
Minimal example:
import factory
from factory import fuzzy
class ChildModel:
def __init__(self, val: bool):
self.val = val
class ParentModel:
def __init__(self, child: ChildModel):
self.child = child
class ChildFactory(factory.Factory):
val = fuzzy.FuzzyChoice([True, False])
class Meta:
model = ChildModel
class ParentFactory(factory.Factory):
child = factory.SubFactory(ChildFactory)
class Meta:
model = ParentModel
class Params:
child_true = factory.Trait(child__val=True)
# Fails:
# TypeError: __init__() missing 1 required positional argument: 'val'
ParentFactory()
The mere presence of the child__val definition in a trait prevents the child factory's fuzzy.FuzzyChoice default from getting used.
Workarounds:
- Don't define traits. This works as expected:
class ParentFactory(factory.Factory):
child = factory.SubFactory(ChildFactory)
class Meta:
model = ParentModel
- Define traits by replacing the subfactory, not using dunder kwargs. This works, but it's a lot more typing.
class ParentFactory(factory.Factory):
child = factory.SubFactory(ChildFactory)
class Meta:
model = ParentModel
class Params:
child_true = factory.Trait(child=factory.SubFactory(ChildFactory, val=True))
- Define and auto-use a trait that includes all dunder-defined kwargs. This works, but requires duplicating the default values from the subfactory for anything you've defined in any other trait.
class ParentFactory(factory.Factory):
child = factory.SubFactory(ChildFactory)
child_default = True
class Meta:
model = ParentModel
class Params:
child_default = factory.Trait(child__val=fuzzy.FuzzyChoice([True, False]))
child_true = factory.Trait(child__val=True)