Python: Factory Boy 生成对象创建时指定长度的列表

Python: Factory Boy to generate List of length specified on object creation

我正在尝试使用 Factoryboy 在创建时指定长度的对象中创建列表。

我可以创建列表,但由于所提供的 length/size 的惰性性质,每次尝试创建具有指定长度的列表都会导致问题。

这是我目前拥有的:

class FooFactory(factory.Factory):

    class Meta:
        model = command.Foo

    foo_uuid = factory.Faker("uuid4")
    bars = factory.List([
        factory.LazyAttribute(lambda o: BarFactory()
        for _ in range(3))
    ])

这将创建一个包含 3 个随机条的列表。我试过结合使用 Params 和 exclude,但是因为 range 需要一个 Int,而这个 int 直到稍后才会被延迟加载,所以它会导致错误。

我想要类似于 post_generation 生成一对多关系的东西,即

foo = FooFactory(number_of_bars=5)

有人对此有好运吗?

主要解决方案

为此需要做两件事: parametersLazyAttribute (链接指向他们的文档,以获得更多详细信息)。

参数就像工厂属性,不会传递给将要创建的实例。 在这种情况下,它们提供了一种参数化 Bars.

列表长度的方法

但是为了在工厂中使用参数自定义一个字段,我们需要访问self, 也就是说,正在构建的实例。 我们可以使用 LazyAttribute 来实现这一点,它是一个接受带有一个参数的函数的声明: 正在构建的对象。 正是我们所需要的。

所以问题中的片段可以重写如下:

class FooFactory(factory.Factory):
    class Meta:
        model = command.Foo

    class Params:
        number_of_bars = 1

    foo_uuid = factory.Faker("uuid4")
    bars = factory.LazyAttribute(lambda self: [BarFactory()] * self.number_of_bars)

并像这样使用:

foo = FooFactory(number_of_bars=3)

如果未提供 number_of_bars 参数,则使用默认值 1

缺点

遗憾的是,我们在这里可以做的事情有一些限制。

在另一个工厂的定义中使用工厂的首选方式是通过 SubFactory。 这是首选,原因有二:

  1. 它尊重用于父工厂的构建策略
  2. 它收集额外的关键字参数来自定义子工厂

第一个表示如果我们使用SubFactoryFooFactory中构建一个Bar 并用 FooFactory.createFooFactory.build 调用 FooFactoryBar 子工厂会尊重这一点并使用相同的策略。 综上所述,构建策略只构建一个实例, 而创建策略构建 and 将实例保存到正在使用的持久存储中, 例如一个数据库,所以尊重这个选择很重要。 查看 docs 了解更多详情。

第二个表示调用FooFactory时可以直接自定义Bar的属性。 例如:

foo = FooFactory(bar__id=2)

会将 foobarid 设置为 2 而不是 Bar 子工厂默认生成的内容。

但我找不到通过 Params 使用 SubFactory 动态长度的方法。 据我所知,无法在 FactoryBoy 期望 SubFactory 的上下文中访问参数值。 问题是让我们访问正在构建的对象的声明总是期望返回最终的 value, 不是以后要调用的另一个工厂。 这意味着,在上面的示例中,如果我们改写:

class FooFactory(factory.Factory):
    # ... rest of the factory
    bars = factory.LazyAttribute(lambda self: [factory.SubFactory(BarFactory)] * self.number_of_bars)

然后像这样称呼它

foo = FooFactory(number_of_bars=3)

会导致 foofoo.bars 中有 3 个 BarFactory 的列表,而不是 3 Bar 的列表。 并使用 SelfAttribute, 这是一种引用正在构建的实例的另一个属性的方法,也不起作用 因为它不会在像这样的声明中的表达式的其余部分之前计算:

class FooFactory(factory.Factory):
    # ... rest of the factory
    bars = factory.List([factory.SubFactory(BarFactory)] * SelfAttribute("number_of_bars"))

那就是 TypeError: can't multiply sequence by non-int of type 'SelfAttribute'。 一种可能的解决方法是预先调用 BarFactory 并将其传递给 FooFactory:

number_of_bars = 3
bars = BarFactory.create_batch(number_of_bars)
foo = FooFactory(bars=bars)

但这肯定不是那么好。

我最近发现的另一个是RelatedFactoryList。 但这仍然是实验性的,它似乎没有办法访问参数。 此外,由于它是在 之后 基础工厂生成的,如果实例构造函数,它也可能无法工作 期望将该属性作为参数。

有一种方法可以传递列表的长度并保留在子工厂上设置其他属性的能力。它需要创建一个 post_generation 方法。

class FooFactory(factory.Factory):
    class Meta:
        model = command.Foo

    foo_uuid = factory.Faker("uuid4")
    bars__count = 5  # Optional: default number of bars to create

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

    num_bars = kwargs.get('count', 0)
    color = kwargs.get('color')

    if num_bars > 0:
        self.bars = [BarFactory(color=color)] * num_bars
    elif extracted:
        self.bars=extracted

任何具有构造 modelname__paramname 的参数都将作为 kwargs 中的 paramname 传递给 post_generation 方法。

然后您可以将 FooFactory 调用为:

FooFactory.create(bars__color='blue')

它将创建具有 5 个柱(默认值)的 Foo。

您也可以调用 FooFactory 并告诉它创建 10 个 Bars。

FooFactory.create(bars__color='blue', bars__count=10)