Change default faker locale in factory_boy - python

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'

The Faker.override_default_locale() is a context manager, although it's not very clear from the docs.
As such, to change the default locale for a part of a test:
with factory.Faker.override_default_locale('es_ES'):
ExampleFactory()
For the whole test:
#factory.Faker.override_default_locale('es_ES')
def test_foo(self):
user = ExampleFactory()
For all the tests (Django):
# 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)
More on it here.

UPD As I said, this solution is suboptimal:
factory.Faker._DEFAULT_LOCALE is a private field
fake() and faker() use the private interface
fake() doesn't work since factory-boy==3.1.0
if I were to use faker, I'd use it directly, not via factory-boy
You should generally prefer the other answer. Leaving this one for posterity.
Not a good solution, but for now it's as good as it gets. You can change the variable that holds the value:
import factory
factory.Faker._DEFAULT_LOCALE = 'xx_XX'
Moreover, you can create a file like this (app/faker.py):
import factory
from faker.providers import BaseProvider
factory.Faker._DEFAULT_LOCALE = 'xx_XX'
def fake(name):
return factory.Faker(name).generate({})
def faker():
return factory.Faker._get_faker()
class MyProvider(BaseProvider):
def category_name(self):
return self.random_element(category_names)
...
factory.Faker.add_provider(MyProvider)
category_names = [...]
Then, once you import the file, the locale changes. Also, you get your providers and an easy way to use factory_boy's faker outside of the factories:
from app.faker import fake
print(fake('random_int'))
print(faker().random_int())

I'm having same issue as yours. For a temporary solution try passing locale in factory.Faker.
For example:
name = factory.Faker('first_name', locale='es_ES')

With Django, you can simply insert the following lines in <myproject>/settings.py:
import factory
factory.Faker._DEFAULT_LOCALE = 'fr_FR'

Further to #xelnor's answer, if using pytest (instead of Django manage.py test), add a hookwrapper on the pytest_runtestloop hook in your conftest.py to set the default locale for all the tests:
#pytest.hookimpl(hookwrapper=True)
def pytest_runtestloop(session):
with factory.Faker.override_default_locale(translation.to_locale(settings.LANGUAGE_CODE)):
outcome = yield

Related

Django setUpTestData does not deepcopy related files

I want to use django setUpTestData to prepare some heavy data shared between multiples unit tests. Instances of my Model are created with some file fields.
However, from one test to another, file contents are not renewed, it has side effects between my tests
here is a minimalist example :
models.py
from django.db import models
class MyModel(models.Model):
my_file = models.FileField(upload_to="tests/")
test.py
from django.core.files.base import ContentFile
from django.test import TestCase
from core.models import MyModel
class Test(TestCase):
#classmethod
def setUpTestData(cls):
cls.instance = MyModel()
cls.instance.my_file.save("file.txt", ContentFile("Hello from setUpTestData"))
def test_a(self):
with open(self.instance.my_file.path, "r") as fp:
self.assertEqual(fp.read(), "Hello from setUpTestData")
self.instance.my_file.save("file.txt", ContentFile("Modified data"))
def test_b(self):
with open(self.instance.my_file.path, "r") as fp:
self.assertEqual(fp.read(), "Hello from setUpTestData")
self.instance.my_file.save("file.txt", ContentFile("Modified data"))
Running any of the two test alone works, however running one after the other one fails:
AssertionError: 'Modified datatUpTestData' != 'Hello from setUpTestData'
- Modified datatUpTestData
+ Hello from setUpTestData
How to ensure that file are correctly reset? Am I concerned by theses lines from the documentation ?
Objects assigned to class attributes in setUpTestData() must support creating deep copies with copy.deepcopy()
I feel like fileField should be handled by default by Django but it doesn't work, what should I do? Should I try to override __deepcopy__ for my models? Modifying my code for testing purpose is a bad pattern.
I've found a solution by using setUp and tearDown
def setUp(self) : # copy original files
shutil.copytree(settings.MEDIA_ROOT, "/tmp/tests", dirs_exist_ok=True)
def tearDown(self) : # restore original files
shutil.copytree("/tmp/tests", settings.MEDIA_ROOT, dirs_exist_ok=True)

How to avoid import errors in django within same app

I have a django app where I want MyModel instances to be saved using an Enum choice, which I process in the save() method, such as:
# app/models.py
from django.db import models
from app.utils import translate_choice
from enum import Enum
class MyEnum(Enum):
CHOICE_A = 'Choice A'
CHOICE_B = 'Choice B'
class MyModel(models.Model):
...
choice = models.CharField(
max_length=10,
choices=[(tag.name, tag.value) for tag in MyEnum],
)
...
def save(self, *args, **kwargs):
self.choice = translate_choice(self.choice)
super().save(*args, **kwargs)
Whereas on the app/utils.py I have:
from app.models import MyEnum
def translate_choice(value):
...
return MyEnum.CHOICE_A # For example
I keep getting ImportError: cannot import name 'MyEnum' errors on the app/utils.py file when running the application. Is this due to a python circular import error, or am I missing something else? When moving the translate_choice() method to app/models.py it stops happening, but I would like to use this same method in other modules and it is kind of weird having a transforming feature within the models when used in another app.
Thank you in advance
It's probably due to the circular import, as you've guessed yourself. You can try putting the import statement not at the top of the file, but inside the function that uses the imported object:
def translate_choice(value):
from app.models import MyEnum
...
return MyEnum.CHOICE_A # For example
This is, admittedly, not the most elegant solution. See also the answers to this post, where you can find other approaches.

How can I monkey-patch a decorator in Django's models while testing?

I have a #memoize decorator in my models, which caches some details on the model itself, to avoid multiple database calls when called many times (especially in templates). However, since I store the objects and refer to them in tests, this breaks things.
For example, if I do mygroup.subscribers, add a subscriber and try it again, it will return an incorrect number of subscribers, since it's been memoized.
How can I monkey-patch that decorator to do nothing from my tests.py? I haven't found a way to do it cleanly, since models get loaded first.
At the beginning of memoize implementation, checks if it is in testing mode as per this answer:
from django.core import mail
# at the beginning of your memoize
if hasattr(mail, 'outbox'):
# return without memorizing
You can disable your decorator in your test runner, the test environment will be set up before models are loaded.
For example:
from django.test.simple import DjangoTestSuiteRunner
from utils import decorators
class PatchTestSuiteRunner(DjangoTestSuiteRunner):
def setup_test_environment(self, **kwargs):
super(PatchTestSuiteRunner, self).setup_test_environment(**kwargs)
self.__orig_memoize = decorators.memoize
decorators.memoize = lambda x: x
def teardown_test_environment(self, **kwargs):
decorators.memoize = self.__orig_memoize
super(PatchTestSuiteRunner, self).teardown_test_environment(**kwargs)
Then put in your settings.py:
TEST_RUNNER = 'test.PatchTestSuiteRunner'
And the tests can be run without memoization:
# myapp/models.py
class TestObject(object):
def __init__(self, value):
self.value = value
#memoize
def get_value(self):
return self.value
# myapp/test.py
from django.test import TestCase
from .models import TestObject
class NoMemoizeTestCase(TestCase):
def test_memoize(self):
t = TestObject(0)
self.assertEqual(t.get_value(), 0)
t.value = 1
self.assertEqual(t.get_value(), 1)
Note that although we're restoring the original decorator in the test runner's teardown_test_environment, memoization will not be restored on already decorated functions. Memoization could be restored if we use a more complex testing decorator, but this is probably not required in standard use cases.

Python - Accessing subclasses' variables from parent class while calling classmethods

i'm trying to build sort of a "mini django model" for working with Django and MongoDB without using the norel Django's dist (i don't need ORM access for these...).
So, what i'm trying to do is to mimic the standart behavior or "implementation" of default models of django... that's what i've got so far:
File "models.py" (the base)
from django.conf import settings
import pymongo
class Model(object):
#classmethod
def db(cls):
db = pymongo.Connection(settings.MONGODB_CONF['host'], settings.MONGODB_CONF['port'])
#classmethod
class objects(object):
#classmethod
def all(cls):
db = Model.db() #Not using yet... not even sure if that's the best way to do it
print Model.collection
File "mongomodels.py" (the implementation)
from mongodb import models
class ModelTest1(models.Model):
database = 'mymongodb'
collection = 'mymongocollection1'
class ModelTest2(models.Model):
database = 'mymongodb'
collection = 'mymongocollection2'
File "views.py" (the view)
from mongomodels import ModelTest1, ModelTest2
print ModelTest1.objects.all() #Should print 'mymongocollection1'
print ModelTest2.objects.all() #Should print 'mymongocollection2'
The problem is that it's not accessing the variables from ModelTest1, but from the original Model... what's wrong??
You must give objects some sort of link to class that contains it. Currently, you are just hard-coding it to use Model()s atttributes. Because you are not instantiating these classes, you will either have to use either a decorator or a metaclass to create the object class for you in each subclass of Model().

How can I check the logging message and the method called my unittests?

I'm using Django 1.3 and need to check the output and number of interactions in my logging system. For logging I'm using Django-Sentry though it appears that it's working just like the regular Python logger.
I'm using python-mockito for mocking and if possible I would like to check the number of times different methods have been called and the messages they return.
I'm trying to achieve a check that does something like:
from foo import views
logger = mock()
views.logger = logger
do_method()
verify(logger).error(any, any)
do_method()
verifyZeroInteractions(logger)
Also being able to check the parameters would be nice.
models.py:
from django.db import models
import logging
from sentry.client.handlers import SentryHandler
logger = logging.getLogger(__name__)
try:
is_logging_setup = True
except NameError:
is_logging_setup = True
logger.setLevel(settings.LOGGING_LEVEL)
logger.addHandler(SentryHandler())
class Foo(models.Model):
def bar(self):
logger.warning("Rawr", 'extra': { 'data': 'foo' })
tests.py:
class TestModelFoo(TestCase):
def setUp(self):
self.foo = Foo()
def test_getting_logged(self):
self.foo.bar()
# Check the log output.
Any suggestions on how I can catch the output?
Here's some code that does just that with the standard mock python library.
with mock.patch('infra.manifest.fake_put') as fake_patch:
infra.manifest.copy_files(root, files, folder, True)
args, kwargs = fake_patch.call_args
self.assertEqual((u'/etc/a.tmp', u'/tmp/a.tmp'), args)
self.assertEqual({'use_sudo': True}, kwargs)
It's the mock.patch method you're interested in. I think there are other frameworks where you can specify a passthrough keyword which will also call the original method, this one will turn the patched method into a mock call and not call the original method.

Categories

Resources