FactoryBoy / factory_boy

A test fixtures replacement for Python
https://factoryboy.readthedocs.io/
MIT License
3.48k stars 392 forks source link

Share faker generator with Factory Boy's faker registry #554

Open chriswyatt opened 5 years ago

chriswyatt commented 5 years ago

The problem

No documented way to share an existing faker generator with Factory Boy. Sometimes you might not always want to use the faker library from Factory Boy's Faker class, so it would be nice if Factory Boy could share a common faker generator.

Proposed solution

Allow an easy way to attach an existing faker generator to Factory Boy. Perhaps something like factory.Faker.attach_fake(faker_generator), assuming that's possible?

Workaround

import factory
from faker import Factory as FakerFactory

LOCALE = 'en-US'
fake = FakerFactory.create(locale=LOCALE)
factory.Faker._DEFAULT_LOCALE = LOCALE
factory.Faker._FAKER_REGISTRY[LOCALE] = fake

Extra notes

rbarrois commented 5 years ago

That's indeed a limitation. Do you have some examples of things you run on those custom fakers? This would help designing a relevant API :)

chriswyatt commented 5 years ago

The only thing special about the faker in my projects, is setting the locale, adding custom providers, and also setting the faker seed. Nothing particularly special with my use case.

It was mainly being able to use the custom providers, as I could have lived with the locale being wrong, and the seed not being the same.

Here's an example of a couple of providers I wrote. The rest I can't share, as they're more sensitive (these aren't open source projects):

from faker.providers.date_time import Provider as DateTimeProvider
from faker.providers.python import Provider as PythonProvider
from faker.providers.misc import Provider as MiscProvider

class Rfc3339Provider(DateTimeProvider):

    def rfc_3339(self, utc=False):
        """
        Returns a RFC 3339 string
        """
        tz = pytz.timezone(random.choice(pytz.all_timezones))
        return generate(self.date_time(tzinfo=tz), utc=utc)

class ExtraProvider(PythonProvider, MiscProvider):

    def float_32(self, positive=None):
        fraction = self.generator.random.randrange(0, 1 << 23)
        exponent = self.generator.random.randrange(1, 255)

        if positive is None:
            positive_ = self.boolean()
        else:
            assert positive in (False, True)
            positive_ = positive

        bin_32 = fraction | (exponent << 23) | ((not positive_) << 31)

        (float_32, ) = unpack('>f', pack('>I', bin_32))
        return float_32

fake.add_provider(Rfc3339Provider)
fake.add_provider(ExtraProvider)

The seed stuff is useful for having a chance of replicating inconsistent test failures, as you can feed in the same seed, and hopefully the tests will fail in the same way. Personally I haven't needed to do this (yet), but it's something that could be useful:

seed = random.seed()
# ... store seed in DB
fake.seed(seed)
x-yuri commented 3 years ago

I believe resolving #407 resolves this issue as well. Since this one was created in hope to work around the former. As it is you can add custom providers:

import factory, faker

class MyProvider(faker.providers.BaseProvider):
    def smth(self):
        return self.random_element([1, 2, 3])

factory.Faker.add_provider(MyProvider)

And set the seed. Although I believe it's poorly documented. And I'm going to file a corresponding issue. I can mention there anybody interested.

x-yuri commented 3 years ago

The only thing special about the faker in my projects, is setting the locale, adding custom providers, and also setting the faker seed.

Setting the locale: https://github.com/FactoryBoy/factory_boy/issues/407#issuecomment-754281399 https://github.com/FactoryBoy/factory_boy/pull/832

Adding providers: see the previous post.

Setting the seed: https://github.com/FactoryBoy/factory_boy/issues/833

chriswyatt commented 3 years ago

Thank you. This is very useful. Seeing as the other tickets, cover it, I guess this can be closed then?