Using factory_boy with ORMs
===========================

.. currentmodule:: factory


factory_boy provides custom :class:`Factory` subclasses for various ORMs,
adding dedicated features.


Django
------

.. currentmodule:: factory.django


The first versions of factory_boy were designed specifically for Django,
but the library has now evolved to be framework-independent.

Most features should thus feel quite familiar to Django users.

The :class:`DjangoModelFactory` subclass
"""""""""""""""""""""""""""""""""""""""""

All factories for a Django :class:`~django.db.models.Model` should use the
:class:`DjangoModelFactory` base class.


.. class:: DjangoModelFactory(factory.Factory)

    Dedicated class for Django :class:`~django.db.models.Model` factories.

    This class provides the following features:

    * The :attr:`~factory.FactoryOptions.model` attribute also supports the ``'app.Model'``
      syntax
    * :func:`~factory.Factory.create()` uses :meth:`Model.objects.create() <django.db.models.query.QuerySet.create>`
    * When using :class:`~factory.RelatedFactory` or :class:`~factory.PostGeneration`
      attributes, the base object will be :meth:`saved <django.db.models.Model.save>`
      once all post-generation hooks have run.


.. note:: With Django versions 1.8.0 to 1.8.3, it was no longer possible to call ``.build()``
          on a factory if this factory used a :class:`~factory.SubFactory` pointing
          to another model: Django refused to set a :class:`~djang.db.models.ForeignKey`
          to an unsaved :class:`~django.db.models.Model` instance.

          See https://code.djangoproject.com/ticket/10811 and https://code.djangoproject.com/ticket/25160 for details.


.. class:: DjangoOptions(factory.base.FactoryOptions)

    The ``class Meta`` on a :class:`~DjangoModelFactory` supports extra parameters:

    .. attribute:: database

        .. versionadded:: 2.5.0

        All queries to the related model will be routed to the given database.
        It defaults to ``'default'``.

    .. attribute:: django_get_or_create

        .. versionadded:: 2.4.0

        Fields whose name are passed in this list will be used to perform a
        :meth:`Model.objects.get_or_create() <django.db.models.query.QuerySet.get_or_create>`
        instead of the usual :meth:`Model.objects.create() <django.db.models.query.QuerySet.create>`:

        .. code-block:: python

            class UserFactory(factory.django.DjangoModelFactory):
                class Meta:
                    model = 'myapp.User'  # Equivalent to ``model = myapp.models.User``
                    django_get_or_create = ('username',)

                username = 'john'

        .. code-block:: pycon

            >>> User.objects.all()
            []
            >>> UserFactory()                   # Creates a new user
            <User: john>
            >>> User.objects.all()
            [<User: john>]

            >>> UserFactory()                   # Fetches the existing user
            <User: john>
            >>> User.objects.all()              # No new user!
            [<User: john>]

            >>> UserFactory(username='jack')    # Creates another user
            <User: jack>
            >>> User.objects.all()
            [<User: john>, <User: jack>]



Extra fields
""""""""""""


.. class:: FileField

    Custom declarations for :class:`django.db.models.FileField`

    .. method:: __init__(self, from_path='', from_file='', data=b'', filename='example.dat')

        :param str from_path: Use data from the file located at ``from_path``,
                              and keep its filename
        :param file from_file: Use the contents of the provided file object; use its filename
                               if available, unless ``filename`` is also provided.
        :param func from_func: Use function that returns a file object
        :param bytes data: Use the provided bytes as file contents
        :param str filename: The filename for the FileField

.. note:: If the value ``None`` was passed for the :class:`FileField` field, this will
          disable field generation:

.. code-block:: python

    class MyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.MyModel

        the_file = factory.django.FileField(filename='the_file.dat')

.. code-block:: pycon

    >>> MyFactory(the_file__data=b'uhuh').the_file.read()
    b'uhuh'
    >>> MyFactory(the_file=None).the_file
    None


.. class:: ImageField

    Custom declarations for :class:`django.db.models.ImageField`

    .. method:: __init__(self, from_path='', from_file='', filename='example.jpg', width=100, height=100, color='green', format='JPEG')

        :param str from_path: Use data from the file located at ``from_path``,
                              and keep its filename
        :param file from_file: Use the contents of the provided file object; use its filename
                               if available
        :param func from_func: Use function that returns a file object
        :param str filename: The filename for the ImageField
        :param int width: The width of the generated image (default: ``100``)
        :param int height: The height of the generated image (default: ``100``)
        :param str color: The color of the generated image (default: ``'green'``)
        :param str format: The image format (as supported by PIL) (default: ``'JPEG'``)

.. note:: If the value ``None`` was passed for the :class:`FileField` field, this will
          disable field generation:

.. note:: Just as Django's :class:`django.db.models.ImageField` requires the
          Python Imaging Library, this :class:`ImageField` requires it too.

.. code-block:: python

    class MyFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.MyModel

        the_image = factory.django.ImageField(color='blue')

.. code-block:: pycon

    >>> MyFactory(the_image__width=42).the_image.width
    42
    >>> MyFactory(the_image=None).the_image
    None


Disabling signals
"""""""""""""""""

Signals are often used to plug some custom code into external components code;
for instance to create ``Profile`` objects on-the-fly when a new ``User`` object is saved.

This may interfere with finely tuned :class:`factories <DjangoModelFactory>`, which would
create both using :class:`~factory.RelatedFactory`.

To work around this problem, use the :meth:`mute_signals()` decorator/context manager:

.. method:: mute_signals(signal1, ...)

    Disable the list of selected signals when calling the factory, and reactivate them upon leaving.

.. code-block:: python

    # foo/factories.py

    import factory
    import factory.django

    from . import models
    from . import signals

    @factory.django.mute_signals(signals.pre_save, signals.post_save)
    class FooFactory(factory.django.DjangoModelFactory):
        class Meta:
            model = models.Foo

        # ...

    def make_chain():
        with factory.django.mute_signals(signals.pre_save, signals.post_save):
            # pre_save/post_save won't be called here.
            return SomeFactory(), SomeOtherFactory()


Mogo
----

.. currentmodule:: factory.mogo

factory_boy supports `Mogo`_-style models, through the :class:`MogoFactory` class.

`Mogo`_ is a wrapper around the ``pymongo`` library for MongoDB.

.. _Mogo: https://github.com/joshmarshall/mogo

.. class:: MogoFactory(factory.Factory)

    Dedicated class for `Mogo`_ models.

    This class provides the following features:

    * :func:`~factory.Factory.build()` calls a model's ``new()`` method
    * :func:`~factory.Factory.create()` builds an instance through ``new()`` then
      saves it.


MongoEngine
-----------

.. currentmodule:: factory.mongoengine

factory_boy supports `MongoEngine`_-style models, through the :class:`MongoEngineFactory` class.

`mongoengine`_ is a wrapper around the ``pymongo`` library for MongoDB.

.. _mongoengine: http://mongoengine.org/

.. class:: MongoEngineFactory(factory.Factory)

    Dedicated class for `MongoEngine`_ models.

    This class provides the following features:

    * :func:`~factory.Factory.build()` calls a model's ``__init__`` method
    * :func:`~factory.Factory.create()` builds an instance through ``__init__`` then
      saves it.

    .. note:: If the :attr:`associated class <factory.FactoryOptions.model` is a :class:`mongoengine.EmbeddedDocument`,
              the :meth:`~MongoEngineFactory.create` function won't "save" it, since this wouldn't make sense.

              This feature makes it possible to use :class:`~factory.SubFactory` to create embedded document.

A minimalist example:

.. code-block:: python

    import mongoengine

    class Address(mongoengine.EmbeddedDocument):
        street = mongoengine.StringField()

    class Person(mongoengine.Document):
        name = mongoengine.StringField()
        address = mongoengine.EmbeddedDocumentField(Address)

    import factory

    class AddressFactory(factory.mongoengine.MongoEngineFactory):
        class Meta:
            model = Address

        street = factory.Sequence(lambda n: 'street%d' % n)

    class PersonFactory(factory.mongoengine.MongoEngineFactory):
        class Meta:
            model = Person

        name = factory.Sequence(lambda n: 'name%d' % n)
        address = factory.SubFactory(AddressFactory)


SQLAlchemy
----------

.. currentmodule:: factory.alchemy


Factoy_boy also supports `SQLAlchemy`_  models through the :class:`SQLAlchemyModelFactory` class.

To work, this class needs an `SQLAlchemy`_ session object affected to the :attr:`Meta.sqlalchemy_session <SQLAlchemyOptions.sqlalchemy_session>` attribute.

.. _SQLAlchemy: https://www.sqlalchemy.org/

.. class:: SQLAlchemyModelFactory(factory.Factory)

    Dedicated class for `SQLAlchemy`_ models.

    This class provides the following features:

    * :func:`~factory.Factory.create()` uses :meth:`sqlalchemy.orm.session.Session.add`


.. class:: SQLAlchemyOptions(factory.base.FactoryOptions)

    In addition to the usual parameters available in :class:`class Meta <factory.base.FactoryOptions>`,
    a :class:`SQLAlchemyModelFactory` also supports the following settings:

    .. attribute:: sqlalchemy_session

        SQLAlchemy session to use to communicate with the database when creating
        an object through this :class:`SQLAlchemyModelFactory`.

    .. attribute:: sqlalchemy_session_persistence

        Control the action taken by sqlalchemy session at the end of a create call.
        
        Valid values are:
        
        * ``None``: do nothing
        * ``'flush'``: perform a session :meth:`~sqlalchemy.orm.session.Session.flush`
        * ``'commit'``: perform a session :meth:`~sqlalchemy.orm.session.Session.commit`
        
        The default value is ``None``.
        
        If ``force_flush`` is set to ``True``, it overrides this option.

    .. attribute:: force_flush

        Force a session ``flush()`` at the end of :func:`~factory.alchemy.SQLAlchemyModelFactory._create()`.
        
        .. note::
        
            This option is deprecated. Use ``sqlalchemy_session_persistence`` instead.
    
A (very) simple example:

.. code-block:: python

    from sqlalchemy import Column, Integer, Unicode, create_engine
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import scoped_session, sessionmaker

    engine = create_engine('sqlite://')
    session = scoped_session(sessionmaker(bind=engine))
    Base = declarative_base()


    class User(Base):
        """ A SQLAlchemy simple model class who represents a user """
        __tablename__ = 'UserTable'

        id = Column(Integer(), primary_key=True)
        name = Column(Unicode(20))

    Base.metadata.create_all(engine)

    import factory

    class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
        class Meta:
            model = User
            sqlalchemy_session = session   # the SQLAlchemy session object

        id = factory.Sequence(lambda n: n)
        name = factory.Sequence(lambda n: u'User %d' % n)

.. code-block:: pycon

    >>> session.query(User).all()
    []
    >>> UserFactory()
    <User: User 1>
    >>> session.query(User).all()
    [<User: User 1>]


Managing sessions
"""""""""""""""""

Since `SQLAlchemy`_ is a general purpose library,
there is no "global" session management system.

The most common pattern when working with unit tests and ``factory_boy``
is to use `SQLAlchemy`_'s :class:`sqlalchemy.orm.scoping.scoped_session`:

* The test runner configures some project-wide :class:`~sqlalchemy.orm.scoping.scoped_session`
* Each :class:`~SQLAlchemyModelFactory` subclass uses this
  :class:`~sqlalchemy.orm.scoping.scoped_session` as its :attr:`~SQLAlchemyOptions.sqlalchemy_session`
* The :meth:`~unittest.TestCase.tearDown` method of tests calls
  :meth:`Session.remove <sqlalchemy.orm.scoping.scoped_session.remove>`
  to reset the session.

.. note:: See the excellent :ref:`SQLAlchemy guide on scoped_session <sqlalchemy:unitofwork_contextual>`
          for details of :class:`~sqlalchemy.orm.scoping.scoped_session`'s usage.

          The basic idea is that declarative parts of the code (including factories)
          need a simple way to access the "current session",
          but that session will only be created and configured at a later point.

          The :class:`~sqlalchemy.orm.scoping.scoped_session` handles this,
          by virtue of only creating the session when a query is sent to the database.


Here is an example layout:

- A global (test-only?) file holds the :class:`~sqlalchemy.orm.scoping.scoped_session`:

.. code-block:: python

    # myprojet/test/common.py

    from sqlalchemy import orm
    Session = orm.scoped_session(orm.sessionmaker())


- All factory access it:

.. code-block:: python

    # myproject/factories.py

    import factory
    import factory.alchemy

    from . import models
    from .test import common

    class UserFactory(factory.alchemy.SQLAlchemyModelFactory):
        class Meta:
            model = models.User

            # Use the not-so-global scoped_session
            # Warning: DO NOT USE common.Session()!
            sqlalchemy_session = common.Session

        name = factory.Sequence(lambda n: "User %d" % n)


- The test runner configures the :class:`~sqlalchemy.orm.scoping.scoped_session` when it starts:

.. code-block:: python

    # myproject/test/runtests.py

    import sqlalchemy

    from . import common

    def runtests():
        engine = sqlalchemy.create_engine('sqlite://')

        # It's a scoped_session, and now is the time to configure it.
        common.Session.configure(bind=engine)

        run_the_tests


- :class:`test cases <unittest.TestCase>` use this ``scoped_session``,
  and clear it after each test (for isolation):

.. code-block:: python

    # myproject/test/test_stuff.py

    import unittest

    from . import common

    class MyTest(unittest.TestCase):

        def setUp(self):
            # Prepare a new, clean session
            self.session = common.Session()

        def test_something(self):
            u = factories.UserFactory()
            self.assertEqual([u], self.session.query(User).all())

        def tearDown(self):
            # Rollback the session => no changes to the database
            self.session.rollback()
            # Remove it, so that the next test gets a new Session()
            common.Session.remove()
