How to specify the database for Factory Boy? #171

Closed
houmie opened this Issue Oct 24, 2014 · 7 comments

Comments

Projects
None yet
6 participants
@rbarrois

This comment has been minimized.

Show comment
Hide comment
@rbarrois

rbarrois Oct 27, 2014

Member

Hi,

The answer on stackoverflow is perfectly valid; I'll copy it here for future reference:

Simply override the _get_manager function:

    @classmethod
    def _get_manager(cls, model_class):
        manager = super(MyCustomFactory, cls)._get_manager(model_class)
        return manager.using('global')

With Django, the reference database is often managed at the model level, using a database router (see https://docs.djangoproject.com/en/dev/topics/db/multi-db/#using-routers).

In your situation, I guess this wasn't sufficient; could you describe your setup so I can see whether I could provide support within factory_boy ?

Member

rbarrois commented Oct 27, 2014

Hi,

The answer on stackoverflow is perfectly valid; I'll copy it here for future reference:

Simply override the _get_manager function:

    @classmethod
    def _get_manager(cls, model_class):
        manager = super(MyCustomFactory, cls)._get_manager(model_class)
        return manager.using('global')

With Django, the reference database is often managed at the model level, using a database router (see https://docs.djangoproject.com/en/dev/topics/db/multi-db/#using-routers).

In your situation, I guess this wasn't sufficient; could you describe your setup so I can see whether I could provide support within factory_boy ?

@rbarrois rbarrois added the Q&A label Oct 27, 2014

@TBeijen

This comment has been minimized.

Show comment
Hide comment
@TBeijen

TBeijen Jan 27, 2015

I'm running into the same issue, my setup:

if IS_UNITTEST:
    DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
    DATABASES['default']['TEST_MIRROR'] = 'master'
    DATABASES['default']['TEST_DEPENDENCIES'] = ['master']
    DATABASES['master']['ENGINE'] = 'django.db.backends.sqlite3'
    DATABASES['master']['TEST_DEPENDENCIES'] = []

Our app mainly reads and just recently we needed to introduce a separate master DB. Of course I could make the write DB 'default' and change the former default into slave but I feel that should not be motivated by testsuite issues.

I've changed the _get_manager() method to actually use the django db routing and that seems to work nicely:

import factory
from django.db.utils import ConnectionRouter

class ModelFactory(factory.DjangoModelFactory):

    @classmethod
    def _get_manager(cls, model_class):
        manager = super(ModelFactory, cls)._get_manager(model_class)
        router = ConnectionRouter()
        db = router.db_for_write(model_class)
        return manager.using(db)

class ArticleFactory(ModelFactory):
    FACTORY_FOR = Article

Of course I can issue a pull request.

TBeijen commented Jan 27, 2015

I'm running into the same issue, my setup:

if IS_UNITTEST:
    DATABASES['default']['ENGINE'] = 'django.db.backends.sqlite3'
    DATABASES['default']['TEST_MIRROR'] = 'master'
    DATABASES['default']['TEST_DEPENDENCIES'] = ['master']
    DATABASES['master']['ENGINE'] = 'django.db.backends.sqlite3'
    DATABASES['master']['TEST_DEPENDENCIES'] = []

Our app mainly reads and just recently we needed to introduce a separate master DB. Of course I could make the write DB 'default' and change the former default into slave but I feel that should not be motivated by testsuite issues.

I've changed the _get_manager() method to actually use the django db routing and that seems to work nicely:

import factory
from django.db.utils import ConnectionRouter

class ModelFactory(factory.DjangoModelFactory):

    @classmethod
    def _get_manager(cls, model_class):
        manager = super(ModelFactory, cls)._get_manager(model_class)
        router = ConnectionRouter()
        db = router.db_for_write(model_class)
        return manager.using(db)

class ArticleFactory(ModelFactory):
    FACTORY_FOR = Article

Of course I can issue a pull request.

@rbarrois

This comment has been minimized.

Show comment
Hide comment
@rbarrois

rbarrois Jan 27, 2015

Member

Hi @TBeijen

Thanks for the use case, which I hadn't identified (the app just reads, but tests need to write to another database).
Before writing a pull request, could you describe the behavior you'd like to see?

I was thinking along the lines of:

class ArticleFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Article
        database = 'master'
Member

rbarrois commented Jan 27, 2015

Hi @TBeijen

Thanks for the use case, which I hadn't identified (the app just reads, but tests need to write to another database).
Before writing a pull request, could you describe the behavior you'd like to see?

I was thinking along the lines of:

class ArticleFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Article
        database = 'master'

@rbarrois rbarrois closed this in 636ca46 Mar 26, 2015

@rbarrois rbarrois added the Feature label Mar 26, 2015

@mmautner

This comment has been minimized.

Show comment
Hide comment
@mmautner

mmautner Apr 10, 2017

Is there a reasonable way to monkeypatch the database name at run-time?

Is there a reasonable way to monkeypatch the database name at run-time?

@TBeijen

This comment has been minimized.

Show comment
Hide comment
@TBeijen

TBeijen Apr 12, 2017

Apparently I totally forgot to get back to this issue.

As to the monkeypatch question. My first question would be: Why? Do you really need a single factory to use different databases? If possible just make it part of your manager.

If varying per test is needed, I suppose the _get_manager() class-method might be a good starting point.
Something along the lines of a context manager that takes a factory class and db name as argument and replaces the get_manager() method to return a manager that uses the desired db.

You'd end up with something like this:

with use_db(MyFactory, 'replica'):
    MyFactory.create(foo='bar')

Sidenote: I have the feeling issue tracker is not the best place for support, but I'm unaware of a gitter channel or other chat room, continuation of discussion best here: https://groups.google.com/forum/#!forum/factoryboy (see Readme).

TBeijen commented Apr 12, 2017

Apparently I totally forgot to get back to this issue.

As to the monkeypatch question. My first question would be: Why? Do you really need a single factory to use different databases? If possible just make it part of your manager.

If varying per test is needed, I suppose the _get_manager() class-method might be a good starting point.
Something along the lines of a context manager that takes a factory class and db name as argument and replaces the get_manager() method to return a manager that uses the desired db.

You'd end up with something like this:

with use_db(MyFactory, 'replica'):
    MyFactory.create(foo='bar')

Sidenote: I have the feeling issue tracker is not the best place for support, but I'm unaware of a gitter channel or other chat room, continuation of discussion best here: https://groups.google.com/forum/#!forum/factoryboy (see Readme).

@jeffwidman

This comment has been minimized.

Show comment
Hide comment
@jeffwidman

jeffwidman Apr 12, 2017

Member

There's also a StackOverflow tag for FB.

Member

jeffwidman commented Apr 12, 2017

There's also a StackOverflow tag for FB.

@eLRuLL

This comment has been minimized.

Show comment
Hide comment
@eLRuLL

eLRuLL Feb 13, 2018

the Context Manager idea looks promising, @TBeijen I would really appreciate if you could share a working example of that. I tried to implement one by myself but got stuck on overriding a @classmethod and how to pass the database name to _get_manager inside the __enter__ method (this was my first attempt to create a Context Manager).

I also created a SO question about it.

Kind Regards.

eLRuLL commented Feb 13, 2018

the Context Manager idea looks promising, @TBeijen I would really appreciate if you could share a working example of that. I tried to implement one by myself but got stuck on overriding a @classmethod and how to pass the database name to _get_manager inside the __enter__ method (this was my first attempt to create a Context Manager).

I also created a SO question about it.

Kind Regards.

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