factory_boy icon indicating copy to clipboard operation
factory_boy copied to clipboard

Lost Dict params when using Traits

Open marjanoitaljano opened this issue 7 years ago • 2 comments

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>]}

marjanoitaljano avatar Feb 19 '18 11:02 marjanoitaljano

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 :-)

rbarrois avatar May 05 '18 00:05 rbarrois

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:

  1. Don't define traits. This works as expected:
class ParentFactory(factory.Factory):                    
    child = factory.SubFactory(ChildFactory)            
 
    class Meta:
        model = ParentModel
  1. 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))
  1. 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)

yourcelf avatar Feb 24 '23 18:02 yourcelf