Repository: FactoryBoy/factory_boy

Issue:
KeyError: 'locale'
#### Description
KeyError: 'locale' generated when calling Faker.
factory/faker.py:46: KeyError
Seems that the `extra` dictionary is empty, but `locale` key is expected.

#### To Reproduce
running unit test with tox
python 3.10; factory-boy==3.2.1 faker==13.15.0

##### Model / Factory code
```python
@pytest.fixture
def unique_dominant_factors():
  return list(set(DominantFactorsFactory.create_batch(3)))

@pytest.fixture
def portfolio_values_per_dominant_factors(unique_dominant_factors):
    scenario_ids = np.array([3, 4, 5, 6, 7, 8, 9, 10])
    portfolio_values_per_dominant_factors = PortfolioValuesPerDominantFactors(
        scenario_ids=scenario_ids, dominant_factors=unique_dominant_factors
    )
    rng = np.random.default_rng(seed=2021)
    for i in range(len(unique_dominant_factors) - 1):
        portfolio_values_per_dominant_factors.add_instrument_values_by_factor_name(
            unique_dominant_factors[i], rng.normal(size=8)
        )
    portfolio_values_per_dominant_factors.add_instrument_values_by_factor_name(
        unique_dominant_factors[-1], np.zeros(8)  # Portfolio value 0 can also occur
    )

    return portfolio_values_per_dominant_factors
```

##### The issue
*Add a short description along with your code*

```python

    @pytest.fixture
    def unique_dominant_factors():
>       return list(set(DominantFactorsFactory.create_batch(3)))

tests/repositories/test_portfolio_values_per_dominant_factors_repository.py:14:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
.tox/py3/lib/python3.10/site-packages/factory/base.py:540: in create_batch
    return [cls.create(**kwargs) for _ in range(size)]
.tox/py3/lib/python3.10/site-packages/factory/base.py:540: in <listcomp>
    return [cls.create(**kwargs) for _ in range(size)]
.tox/py3/lib/python3.10/site-packages/factory/base.py:528: in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
.tox/py3/lib/python3.10/site-packages/factory/base.py:465: in _generate
    return step.build()
.tox/py3/lib/python3.10/site-packages/factory/builder.py:258: in build
    step.resolve(pre)
.tox/py3/lib/python3.10/site-packages/factory/builder.py:199: in resolve
    self.attributes[field_name] = getattr(self.stub, field_name)
.tox/py3/lib/python3.10/site-packages/factory/builder.py:344: in __getattr__
    value = value.evaluate_pre(
.tox/py3/lib/python3.10/site-packages/factory/declarations.py:477: in evaluate_pre
    choice = self.decider.evaluate(instance=instance, step=step, extra={})
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <factory.faker.Faker object at 0x7f3843200df0>
instance = <Resolver for <BuildStep for <StepBuilder(<FactoryOptions for DominantFactorsFactory>, strategy='create')>>>
step = <BuildStep for <StepBuilder(<FactoryOptions for DominantFactorsFactory>, strategy='create')>>, extra = {}

    def evaluate(self, instance, step, extra):
>       locale = extra.pop('locale')
E       KeyError: 'locale'


```

#### Notes
Factory-boy version 3.0.1 works properly

Hints from issue comments:
@francoisfreitag: From the stack trace, it indeed looks like a bug. Looks like the decider of a Maybe declaration is a Faker field?@radohristov: Looks like the issue is closed, but what exactly is the outcome ?@francoisfreitag: [Raphaël thought to have fixed the issue](https://github.com/FactoryBoy/factory_boy/issues/788#issuecomment-750399692). Apparently, a case slipped through the net.@radohristov: Hi Francois,

Maybe you can provide some updates on this issue ?

Rgds,

Radoslav

On Thu, Jul 21, 2022 at 9:35 AM François Freitag ***@***.***>
wrote:

> Raphaël thought to have fixed the issue
> <https://github.com/FactoryBoy/factory_boy/issues/788#issuecomment-750399692>.
> Apparently, a case slipped through the net.
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/FactoryBoy/factory_boy/issues/965#issuecomment-1191147790>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AJMECP6RQPACTVVGKUB6ZG3VVD4UBANCNFSM53ZHKSRQ>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>
@radohristov: Hi Francois,

Thank you for looking at this, in the attachment there is a striped working
example.
It works on 3.0.1, but crashes on 3.2.1.
If you need additional information please let me know.

Best,

Radoslav

On Mon, Jul 18, 2022 at 4:44 PM François Freitag ***@***.***>
wrote:

> From the stack trace, it indeed looks like a bug. Looks like the decider
> of a Maybe declaration is a Faker field?
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/FactoryBoy/factory_boy/issues/965#issuecomment-1187589901>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AJMECP2XEAUH4RENW5LTNGDVUVUVRANCNFSM53ZHKSRQ>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>
@francoisfreitag: Thanks Radoslav. It looks like GitHub does not process email attachments. Could you upload the example to reproduce to the issue directly?@radohristov: Hi Francois,

I'm bit confused what you want me to do: send the example as an email
attachment or ?

Best, Radoslav

On Tue, Oct 11, 2022 at 3:11 PM François Freitag ***@***.***>
wrote:

> Thanks Radoslav. It looks like GitHub does not process email attachments.
> Could you upload the example to reproduce to the issue directly?
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/FactoryBoy/factory_boy/issues/965#issuecomment-1274665661>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AJMECP7BQONHAK5VMADP3ADWCVRQTANCNFSM53ZHKSRQ>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>
@francoisfreitag: Hi Radoslav,
Please add an example to the GitHub issue using the GitHub web interface (https://github.com/FactoryBoy/factory_boy/issues/965).@ibaguio: hi @francoisfreitag, 

chiming in on this reported issue. i've also encountered this. i've created a simple PR that hopefully resolves this issue.

https://github.com/FactoryBoy/factory_boy/pull/992/@radohristov: Hi Francois,
example file added:
[factory_boy_example.zip](https://github.com/FactoryBoy/factory_boy/files/10012633/factory_boy_example.zip)
.@francoisfreitag: Awesome, I can indeed reproduce the issue with this code (taken from the above zip):
```python
# example.py
import factory


class SomeFactors:
    def __init__(self, factor):
        self.factor = factor


class FactorsFactory(factory.Factory):
    class Meta:
        model = SomeFactors

    factor = factory.Maybe(factory.Faker("pybool"), "value", None)


class TestValuesPerFactors:
    def test_add_and_find(self):
        FactorsFactory.create()
```

```sh
$ pytest example.py
```@markddavidoff: Suggested fix: https://github.com/FactoryBoy/factory_boy/pull/1007@nlarusstone: I'm still running into this issue on these versions:
```
factory-boy              3.3.0
faker                    22.5.1 
```@BMourguesFieldbox: Hello, I encounter the same issue with factory boy 3.3.0 and Faker 24.3.0

The field is defined as is : 
```py
    obs_card_target = factory.Maybe(
        factory.Faker("pybool"),
        factory.Faker("pyfloat", right_digits=2, min_value=0, max_value=100),
    )
 ```
 
 And the traceback : 
 ```
 Traceback (most recent call last):
  File "/home/fbox/control_tower/projects/tests.py", line 334, in test_list_trifr
    hsse_report = HSSEReportFactory()
  File "/home/fbox/env/lib/python3.10/site-packages/factory/base.py", line 40, in __call__
    return cls.create(**kwargs)
  File "/home/fbox/env/lib/python3.10/site-packages/factory/base.py", line 528, in create
    return cls._generate(enums.CREATE_STRATEGY, kwargs)
  File "/home/fbox/env/lib/python3.10/site-packages/factory/django.py", line 121, in _generate
    return super()._generate(strategy, params)
  File "/home/fbox/env/lib/python3.10/site-packages/factory/base.py", line 465, in _generate
    return step.build()
  File "/home/fbox/env/lib/python3.10/site-packages/factory/builder.py", line 270, in build
    step.resolve(pre)
  File "/home/fbox/env/lib/python3.10/site-packages/factory/builder.py", line 211, in resolve
    self.attributes[field_name] = getattr(self.stub, field_name)
  File "/home/fbox/env/lib/python3.10/site-packages/factory/builder.py", line 356, in __getattr__
    value = value.evaluate_pre(
  File "/home/fbox/env/lib/python3.10/site-packages/factory/declarations.py", line 539, in evaluate_pre
    choice = self.decider.evaluate(instance=instance, step=step, extra={})
  File "/home/fbox/env/lib/python3.10/site-packages/factory/faker.py", line 46, in evaluate
    locale = extra.pop('locale')
KeyError: 'locale'
```
@francoisfreitag: I believe the commit fixing the issue hasn’t been released yet. Could you try against the master branch?@BMourguesFieldbox: > I believe the commit fixing the issue hasn’t been released yet. Could you try against the master branch?

Yeah I realised only after posting that the latest commit wasn't tagged, 3.3.0 released like a year ago while the fix for this is 2 months old...

In the end I decided to go with `obs_card_target = factory.Faker("pyfloat", right_digits=2, min_value=0, max_value=100)` and set to None myself if needed

Solution patch:
diff --git a/factory/declarations.py b/factory/declarations.py
index 70abe35..951b45f 100644
--- a/factory/declarations.py
+++ b/factory/declarations.py
@@ -536,7 +536,7 @@ class Maybe(BaseDeclaration):
             return target
 
     def evaluate_pre(self, instance, step, overrides):
-        choice = self.decider.evaluate(instance=instance, step=step, extra={})
+        choice = self.decider.evaluate_pre(instance=instance, step=step, overrides={})
         target = self.yes if choice else self.no
         # The value can't be POST_INSTANTIATION, checked in __init__;
         # evaluate it as `evaluate_pre`


Test patch:
diff --git a/tests/test_regression.py b/tests/test_regression.py
index a9ea1c6..2cca0bd 100644
--- a/tests/test_regression.py
+++ b/tests/test_regression.py
@@ -51,3 +51,24 @@ class FakerRegressionTests(unittest.TestCase):
 
         unknown_author = AuthorFactory(unknown=True)
         self.assertEqual("", unknown_author.fullname)
+
+    def test_evaluated_without_locale(self):
+        """Regression test for `KeyError: 'locale'` raised in `evaluate`.
+
+        See #965
+
+        """
+        class AuthorFactory(factory.Factory):
+            fullname = factory.Faker("name")
+            pseudonym = factory.Maybe(
+                decider=factory.Faker("pybool"),
+                yes_declaration="yes",
+                no_declaration="no",
+            )
+
+            class Meta:
+                model = Author
+
+        author = AuthorFactory()
+
+        self.assertIn(author.pseudonym, ["yes", "no"])