Cannot use factory.Maybe without a “decider” model field
Description
Using a factory.Faker as the "decider" for a factory.Maybe instead of the name of a model field causes the following error:
.venv/lib/python3.8/site-packages/factory/base.py:40: in __call__
return cls.create(**kwargs)
.venv/lib/python3.8/site-packages/factory/base.py:528: in create
return cls._generate(enums.CREATE_STRATEGY, kwargs)
.venv/lib/python3.8/site-packages/factory/django.py:117: in _generate
return super()._generate(strategy, params)
.venv/lib/python3.8/site-packages/factory/base.py:465: in _generate
return step.build()
.venv/lib/python3.8/site-packages/factory/builder.py:258: in build
step.resolve(pre)
.venv/lib/python3.8/site-packages/factory/builder.py:199: in resolve
self.attributes[field_name] = getattr(self.stub, field_name)
.venv/lib/python3.8/site-packages/factory/builder.py:344: in __getattr__
value = value.evaluate_pre(
.venv/lib/python3.8/site-packages/factory/declarations.py:476: in evaluate_pre
choice = self.decider.evaluate(instance=instance, step=step, extra={})
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <factory.faker.Faker object at 0x7ff008607670>, instance = <Resolver for <BuildStep for <StepBuilder(<DjangoOptions for OrganizationFactory>, strategy='create')>>>
step = <BuildStep for <StepBuilder(<DjangoOptions for OrganizationFactory>, strategy='create')>>, extra = {}
def evaluate(self, instance, step, extra):
> locale = extra.pop('locale')
E KeyError: 'locale'
.venv/lib/python3.8/site-packages/factory/faker.py:46: KeyError
To Reproduce
Model / Factory code
class Organization(models.Model):
is_active = models.BooleanField()
name = models.SlugField(max_length=64, unique=True, blank=False, null=False)
description = models.TextField(blank=True, null=True)
class OrganizationFactory(factory.django.DjangoModelFactory):
class Meta:
model = Organization
is_active = factory.Faker("boolean", chance_of_getting_true=50)
name = factory.LazyAttributeSequence(lambda o, n: f"organization-{n}")
description = factory.Maybe(
factory.Faker("boolean", chance_of_getting_true=50),
yes_declaration=factory.Faker("text"),
no_declaration=None,
)
The issue
I want description to have a value only some of the time - regardless of the value of any other model fields. So although I could pass "is_active" as the first argument to factory.Maybe it doesn't quite fit my use-case.
I have managed to get it working using this ugly code:
from functools import partial
from faker import Faker
fake = Faker()
class OrganizationFactory(factory.django.DjangoModelFactory):
...
description = factory.Maybe(
factory.LazyFunction(partial(fake.boolean, chance_of_getting_true=50)),
yes_declaration=factory.Faker("text"),
no_declaration=None,
)
But that feels like a hack.
Notes
I feel like this was meant to have been fixed by https://github.com/FactoryBoy/factory_boy/pull/828.
Thanks for the very thorough bug report! Indeed, this should have been fixed in that issue, but some test cases were missing :(
I am experiencing this as well, but at least now I know what the problem is.