question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Populating PostGenerationMethodCall via Traits

See original GitHub issue

Consider the following approach (with 2.11.1):

from django.test import TestCase

import factory


class UserFactory(factory.django.DjangoModelFactory):
    username = factory.Sequence(lambda n: 'USER_%03d' % n)
    password = factory.PostGenerationMethodCall('set_password', 'supersecret')

    class Meta:
        model = 'auth.User'

    class Params:
        weak_password = factory.Trait(password='notsecretatall')


class PostGenerationMethodAndTraits(TestCase):

    def test_populate_post_generation_through_trait(self):
        user = UserFactory(weak_password=True)
        self.assertTrue(user.check_password('notsecretatall'))

Which gives the following results:

Traceback (most recent call last):
  File "/app/src/post_and_traits/tests.py", line 21, in test_populate_post_generation_through_trait
    self.assertTrue(user.check_password('notsecretatall'))
AssertionError: False is not true

I’m not sure if this is intended behaviour or unintended, as it works with <=2.8.1.

However, with >2.8.1 one has to redeclare the PostGenerationMethodCall in the Trait in order to make it work: e.g.

weak_password = factory.Trait(password=factory.PostGenerationMethodCall('set_password', 'notsecretatall'))

This could fall as far to being a design decision; whether the generation approach should have to be redeclared completely or if the generation approach should be “static”, hence the traits would only make the argument(and keywords) differ.

I’m not sure what is wanted here, although I’ve personally run in to very few cases where the method would differ rather than only the argument(and keywords) and thus would see it fitting if one only had to write weak_password = factory.Trait(password='notsecretatall') for changing argument for PostGenerationMethodCall. And instead maybe use PostGeneration for the eventual case of changing both method and argument.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:4
  • Comments:6 (1 by maintainers)

github_iconTop GitHub Comments

4reactions
BenVospercommented, Mar 19, 2020

I’ve also just noticed the bug mentioned above. Kwargs defined inside traits don’t seem to be getting passed to any post-generation methods as they would if they were used to instantiate the factory.

Here’s an even more stripped-back example:

import factory as factory_boy


class Foo:
    def __init__(self, bar):
        self.bar = bar


class FooFactory(factory_boy.Factory):

    class Meta:
        model = Foo

    bar = 1

    class Params:
        bar_trait = factory_boy.Trait(
            bar_post_generation=2
        )

    @factory_boy.post_generation
    def bar_post_generation(self, create, extracted, **kwargs):
        if create and extracted:
            self.bar = extracted


# Using default set in factory definition
foo = FooFactory()
assert foo.bar == 1

# Overriding value in factory definition
foo = FooFactory(bar=2)
assert foo.bar == 2

# Passing value directly to post-generation
foo = FooFactory(bar_post_generation=2)
assert foo.bar == 2

# Passing value to post-generation via trait
# Fails on 2.12.0
foo = FooFactory(bar_trait=True)
assert foo.bar == 2

Everything works up until that last assertion.

Other than this (admittedly niche) issue, I love Factory Boy! I use it whenever I can. Thanks for your hard work maintainers 😃

4reactions
dlobuecommented, Jun 28, 2018

@rbarrois

Actually, there is a bug here in that post generation isn’t being called when a trait defines an argument that should be passed to the post-generate method. Here’s a simple example:

import factory


class Target(object):
    def __init__(self, m2m=None):
        self.m2m = m2m or []


class TargetFactory(factory.Factory):
    class Meta:
        model = Target

    class Params:
        empty = factory.Trait(
            m2m=None,
            m2m__num=1,
        )

    m2m__num = 3

    @factory.post_generation
    def m2m(self, create, extracted, num=0, **kwargs):
        print('In m2m post_generation: %r, %r' % ((create, extracted, num), kwargs))
        for i in range(num):
            self.m2m.append(extracted)


def main():
    print("no args")
    value = TargetFactory()
    print('value of m2m: %r\n' % value.m2m)

    print("m2m given argument")
    value = TargetFactory(m2m='blarg')
    print('value of m2m: %r\n' % value.m2m)

    print("m2m given argument and empty trait active")
    value = TargetFactory(empty=True, m2m='blarg')
    print('value of m2m: %r\n' % value.m2m)


if __name__ == '__main__':
    main()

Here’s the output of this script with version 2.11.1:

no args
In m2m post_generation: (True, None, 3), {}
value of m2m: [None, None, None]

m2m given argument
In m2m post_generation: (True, 'blarg', 3), {}
value of m2m: ['blarg', 'blarg', 'blarg']

m2m given argument and empty trait active
value of m2m: []

And here’s the output of this script with version 2.8.1:

no args
In m2m post_generation: (True, None, 3), {}
value of m2m: [None, None, None]

m2m given argument
In m2m post_generation: (True, 'blarg', 3), {}
value of m2m: ['blarg', 'blarg', 'blarg']

m2m given argument and empty trait active
In m2m post_generation: (True, 'blarg', 1), {}
value of m2m: ['blarg']

IMO the functionality and output of 2.8.1 is correct: the post-generate method is run, and parameters passed to the factory constructor override parameters defined in the trait.

Thanks, and I hope this helps!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Factory Boy Documentation
When many fields should be updated based on a flag, use Traits instead: ... PostGenerationMethodCall: allows you to hook a particular ...
Read more >
Factory Boy Documentation - Read the Docs
Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as fields ......
Read more >
Choosing from a populated table in factory boy while using flask
For SQLAlchemy, you need a way to return a generator instead of a list; I think this would work with factory.Iterator(User.query) . User.query....
Read more >
https://raw.githubusercontent.com/FactoryBoy/facto...
Trait ` feature). - :class:`~factory.PostGenerationMethodCall` only allows to pass one positional argument; use keyword arguments for extra parameters.
Read more >
October | 2014 | aliteralmind — Computer Programming Blog
10 posts published by aliteralmind during October 2014. ... maxUserLen + " characters, inclusive.";. var passwordMsg = "Password must be ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found