Choosing from a list of names using factory boy integrated with faker
Asked Answered
U

2

6

I am trying to use factory.faker to randomly choose from a list of four companies and use them as a traffic source for a list of generated names. I am using the below code:

    from django.db import models
    import factory
    import factory.django
    from datetime import datetime
    from django.core.validators import MinValueValidator, MaxValueValidator
    from faker import Faker
    from faker.providers import BaseProvider
    import random

    fake = Faker()

    class User(models.Model):
        name = models.CharField(max_length=64)
        address = models.CharField(max_length=128)
        phone_number = models.CharField(max_length=32)
        login_date = models.DateTimeField(default=datetime.now(), blank=True)
        session_duration = models.IntegerField(default = 0, validators=  [
                           MinValueValidator(0),
                           MaxValueValidator(5)
                           ])
        traffic_source = models.CharField(max_length=32)

    class UserFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = User

        name = factory.Faker('name')
        address = factory.Faker('address')
        phone_number = factory.Faker('phone_number')
        login_date = factory.Faker('date')
        session_duration = factory.Faker('random_int')



        traffic_source = random.choice(['XYZ', 'ABC', '123', '456'])

The issue is that for all 200 iterations I perform using the following in the python shell:

    for _ in range(200): 
        UserFactory.create()

I get the same company for every name, i.e. 'XYZ' for all 200 names.

Am I missing something? I want to get a different company for each of the 200 iterations. Any help is much appreciated. Thank you!

Underbrush answered 4/7, 2020 at 0:39 Comment(0)
A
19

This comes from Python's parsing rules.

Why?

When you write this:

class UserFactory(factory.django.DjangoModelFactory):
    ...
    traffic_source = random.choice(['XYZ', 'ABC', '123', '456'])

Python will execute the following steps:

  1. Read the class declaration body;
  2. Reach the line traffic_source = random.choice(['XYZ', 'ABC', '123', '456']);
  3. Evaluate the call to random.choice, which might return 'ABC';
  4. Once each line of the class body has been read (and its function calls evaluated), create the class:
    UserFactory = type(
        name='UserFactory',
        bases=[factory.django.DjangoModelFactory],
        {'traffic_source': 'ABC', ...},
     )```
    
    

As you can see, the call to random.choice is performed only once, when parsing the class declaration.

This is, basically, the reason for all the factory.XXX declarations: they yield an object that will only execute its specific rules when building an instance from the factory.

So, what should you do?

Here, you should use:

class UserFactory(factory.django.DjangoModelFactory):
    ...
    traffic_source = factory.Faker('random_choices', elements=['XYZ', 'ABC', '123', '456'])
    alt_traffic_source = factory.fuzzy.FuzzyChoice(['XYZ', 'ABC', '123', '456'])

The main difference between factory.Faker('random_choices') and factory.fuzzy.FuzzyChoices is that factory.fuzzy.FuzzyChoices supports lazily evaluating generators; this is useful if you want to choose from a queryset:

  • factory.Faker('random_choices', elements=Company.objects.all()) will perform a DB query at import time;
  • factory.fuzzy.FuzzyChoice(Company.objects.all()) will only query the DB the first time UserFactory.create() is called.
Aaron answered 4/7, 2020 at 11:23 Comment(3)
Awesome, yeah, a simple error on my part. I ended up going with the fuzzychoice method and it worked perfectly. Thank you for the help!Underbrush
Note: if you want only 1 element I found that Faker random_element was what I needed (random_choices returns a list)Cullie
Sadly, factory.fuzzy is now deprecated, and there is no "lazy evaluation" equivalent on Faker (or at least is what i understood from github.com/FactoryBoy/factory_boy/issues/271)Bartholomew
D
1

While not truly random, the effect you're looking for when choosing from among a set of pre-existing records can also be achieved by using FactoryBoy's Iterator, which can also work with a QuerySet. For example, here I wanted every object to be created by someone different from the set of existing fake users:

from django.contrib.auth import get_user_model
...

# Then, within a factory class, for one of the fields:
created_by = factory.Iterator(get_user_model().objects.all())
Drowse answered 31/7, 2020 at 21:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.