Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change default faker locale in factory_boy #407

Open
3ynm opened this issue Aug 21, 2017 · 10 comments · May be fixed by #832
Open

Change default faker locale in factory_boy #407

3ynm opened this issue Aug 21, 2017 · 10 comments · May be fixed by #832

Comments

@3ynm
Copy link

3ynm commented Aug 21, 2017

How can I set the default locale in Python's factory_boy for all of my Factories?

In docs says that one should set it with factory.Faker.override_default_locale but that does nothing to my fakers...

import factory
from app.models import Example
from custom_fakers import CustomFakers

# I use custom fakers, this indeed are added
factory.Faker.add_provider(CustomFakers)
# But not default locales
factory.Faker.override_default_locale('es_ES')

class ExampleFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Example

    name = factory.Faker('first_name')


>>> from example import ExampleFactory
>>> e1 = ExampleFactory()
>>> e1.name
>>> u'Chad'
@rbarrois
Copy link
Member

Indeed: factory.Faker.override_default_locale() is a decorator and context manager: you can only use it as:

with factory.Faker.override_default_locale('es_ES'):
    e1 = ExampleFactory()

Or:

@factory.Faker.override_default_locale('es_ES')
def test_foo(self):
    e1 = ExampleFactory()

The idea is that changing the default locale is mostly used for a few specific tests while the whole suite doesn't care; hence, having a decorator/context manager ensures that the default locale is restored once those tests have run.

However, this is not very clear in the docs; I think we should improve them.
Alternatively, we could add an extra entrypoint to set the global, initial configuration for Faker (including adding extra providers).

@3ynm
Copy link
Author

3ynm commented Sep 1, 2017

Indeed I think allowing to set a global language is a must, thanks :)

By the way, global configuration for additional provider does work.

@elbenfreund
Copy link

I agree with @HACKTIVISTA that an easy (and preferably randomizable) way to change the locale would be a major advantage. Right now I am considering using vanilla faker via a custom fake fixture that does just that. It would be nice to have something similar with factory_boy's otherwise lovely faker integration.

If thats something you would be interested in getting a PR please let me know.

@chriswyatt
Copy link

chriswyatt commented Jan 10, 2019

This is something I'd also find useful. I also think it might be useful to be able to attach an existing faker generator, so I filed a ticket: #554

thibault added a commit to MTES-MCT/aides-territoires that referenced this issue Jan 15, 2019
Long story short, the `faker` lib uses the en_us default locale, hence
phone numbers longer than french ones, which was completely breaking the
tests.

There is no easy way to set factory_boy's embedded version of faker, so
it's just easier to update the fields lengths.

See FactoryBoy/factory_boy#407
@x-yuri
Copy link

x-yuri commented Apr 6, 2019

See workaround here.

@x-yuri
Copy link

x-yuri commented Jan 4, 2021

I was about to create a new issue. But found this one. Even with some feedback from me :)

I looked into it once again. There are basically 2 ways.

As one might find out, the default locale is taken from faker. But setting the default locale is not covered by the faker's documentation. The good news is that with faker it's probably not that important, since usually you specify the locale once, when you instantiate the generator (e.g. Faker(locale='...')). I tried looking into changing faker's default, but first, faker's and factory-boy's default locales are default in different ways. Second, I couldn't easily find a way such that I'd be sure I don't introduce issues.

Another option is to do:

factory.Faker._DEFAULT_LOCALE = '...'

after importing factory. And apparently before declaring factories. This one will probably work out. But I don't like this underscore in front of the name. And the fact that it's not documented.

@rbarrois Do you think the second way can be considered official (except for underscore)? Or do you have any better suggestions?

UPD But considering that Django project may contain several applications. It doesn't seem right to change the default locale in just one of the them. Some centralized way would probably be preferable.

@rbarrois
Copy link
Member

rbarrois commented Jan 4, 2021

So, let's look at the different situations where one might want to change the default locale:

  • For part of a specific test: this is covered by with override_default_locale():
  • For a specific test function: this is covered by @override_default_locale()
  • For a collection of tests grouped in a TestCase subclass: @override_default_locale() might not be enough here
  • For a whole run of the program / test suite, with a fixed value from run to run
  • For a whole run of the program / test suite, with different values between runs

For the last two cases, we'll have to look into the program setup:

  • For a program (say, generating a realistic dataset), it is usually easy to add a with override_default_locale() within the program's main() function
  • For a test run, this has to happen within the test runner setup

For Django, I believe that the recommended mode is to:

  1. Subclass Django's DiscoverRunner
  2. Overload its run_tests function, to wrap the initial method inside a with override_default_locale()
  3. Point your settings' TEST_RUNNER to your class:
# settings.py
TEST_RUNNER = 'myproject.testing.MyTestRunner'

# myproject/testing.py
import factory
from django.conf import settings
from django.util import translation
import django.test.runner

class MyTestRunner(django.test.runner.DiscoverRunner):
    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        with factory.Faker.override_default_locale(translation.to_locale(settings.LANGUAGE_CODE)):
            return super().run_tests(test_labels, extra_tests=extra_tests, **kwargs)

@x-yuri
Copy link

x-yuri commented Jan 7, 2021

@rbarrois Oh, it appears I've got my case covered. Do you think we can document it? Supposedly by adding a common recipe?

@x-yuri x-yuri linked a pull request Jan 9, 2021 that will close this issue
@robvdl
Copy link

robvdl commented Jan 14, 2021

For pytest django I just put it in conftest.py since that loads before any tests start:

import faker.config

faker.config.DEFAULT_LOCALE = "en_NZ"

Works a charm, if you check the Faker class, it populates _DEFAULT_LOCALE from there.

@x-yuri
Copy link

x-yuri commented Jan 16, 2021

Indeed, just importing factory or faker doesn't make use of the faker.config.DEFAULT_LOCALE value. So sounds safe.

Although I'd like to point out at a slight distinction between factory-boy's and faker's default locales. For factory-boy the default locale is a locale used when no locale is specified. That is true for faker as well. But in addition faker falls back to the default locale if a provider doesn't support the locale you demanded and doesn't provide its own fallback. For now the only unlocalized provider that doesn't support en_US is the bank provider, but it provides a fallback.

In other words, changing factory-boy's and faker's default locales might have different effects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants