django-rest-frameworkfakerfactory-boy

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


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!


Solution

  • 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_element', 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: