
Using factory_boy with ORMs
***************************

factory_boy provides custom "Factory" subclasses for various ORMs,
adding dedicated features.


Django
======

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

Most features should thus feel quite familiar to Django users.


The "DjangoModelFactory" subclass
---------------------------------

All factories for a Django "Model" should use the "DjangoModelFactory"
base class.

class class factory.django.DjangoModelFactory(factory.Factory)

   Dedicated class for Django "Model" factories.

   This class provides the following features:

   * The "model" attribute also supports the "'app.Model'" syntax

   * "create()" uses "Model.objects.create()"

   * When using "RelatedFactory" or "PostGeneration" attributes, the
     base object will be "saved" 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 "SubFactory"
  pointing to another model: Django refused to set a "ForeignKey" to
  an unsaved "Model" instance.See
  https://code.djangoproject.com/ticket/10811 and
  https://code.djangoproject.com/ticket/25160 for details.

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

   The "class Meta" on a "DjangoModelFactory" supports extra
   parameters:

   database

      New in version 2.5.0.

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

   django_get_or_create

      New in version 2.4.0.

      Fields whose name are passed in this list will be used to
      perform a "Model.objects.get_or_create()" instead of the usual
      "Model.objects.create()":

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

             username = 'john'

         >>> 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>]

Note: If a "DjangoModelFactory" relates to an "abstract" model, be
  sure to declare the "DjangoModelFactory" as abstract:

     class MyAbstractModelFactory(factory.django.DjangoModelFactory):
         class Meta:
             model = models.MyAbstractModel
             abstract = True

     class MyConcreteModelFactory(MyAbstractModelFactory):
         class Meta:
             model = models.MyConcreteModel

  Otherwise, factory_boy will try to get the 'next PK' counter from
  the abstract model.


Extra fields
------------

class class factory.django.FileField

   Custom declarations for "django.db.models.FileField"

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

      Parameters:
         * **from_path** (*str*) -- Use data from the file located
           at "from_path", and keep its filename

         * **from_file** (*file*) -- Use the contents of the
           provided file object; use its filename if available

         * **data** (*bytes*) -- Use the provided bytes as file
           contents

         * **filename** (*str*) -- The filename for the FileField

Note: If the value "None" was passed for the "FileField" field, this
  will disable field generation:

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

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

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

class class factory.django.ImageField

   Custom declarations for "django.db.models.ImageField"

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

      Parameters:
         * **from_path** (*str*) -- Use data from the file located
           at "from_path", and keep its filename

         * **from_file** (*file*) -- Use the contents of the
           provided file object; use its filename if available

         * **filename** (*str*) -- The filename for the ImageField

         * **width** (*int*) -- The width of the generated image
           (default: "100")

         * **height** (*int*) -- The height of the generated image
           (default: "100")

         * **color** (*str*) -- The color of the generated image
           (default: "'green'")

         * **format** (*str*) -- The image format (as supported by
           PIL) (default: "'JPEG'")

Note: If the value "None" was passed for the "FileField" field, this
  will disable field generation:

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

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

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

   >>> 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 "factories", which would create
both using "RelatedFactory".

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

factory.django.mute_signals(signal1, ...)

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

   # 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
====

factory_boy supports Mogo-style models, through the "MogoFactory"
class.

Mogo is a wrapper around the "pymongo" library for MongoDB.

class class factory.mogo.MogoFactory(factory.Factory)

   Dedicated class for Mogo models.

   This class provides the following features:

   * "build()" calls a model's "new()" method

   * "create()" builds an instance through "new()" then saves it.


MongoEngine
===========

factory_boy supports MongoEngine-style models, through the
"MongoEngineFactory" class.

mongoengine is a wrapper around the "pymongo" library for MongoDB.

class class factory.mongoengine.MongoEngineFactory(factory.Factory)

   Dedicated class for MongoEngine models.

   This class provides the following features:

   * "build()" calls a model's "__init__" method

   * "create()" builds an instance through "__init__" then saves it.

   Note: If the "associated class <factory.FactoryOptions.model" is
     a "mongoengine.EmbeddedDocument", the "create()" function won't
     "save" it, since this wouldn't make sense.This feature makes it
     possible to use "SubFactory" to create embedded document.

A minimalist example:

   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
==========

Factoy_boy also supports SQLAlchemy  models through the
"SQLAlchemyModelFactory" class.

To work, this class needs an SQLAlchemy session object affected to the
"Meta.sqlalchemy_session" attribute.

class class factory.alchemy.SQLAlchemyModelFactory(factory.Factory)

   Dedicated class for SQLAlchemy models.

   This class provides the following features:

   * "create()" uses "sqlalchemy.orm.session.Session.add()"

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

   In addition to the usual parameters available in "class Meta", a
   "SQLAlchemyModelFactory" also supports the following settings:

   sqlalchemy_session

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

A (very) simple example:

   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)

   >>> 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 "sqlalchemy.orm.scoping.scoped_session":

* The test runner configures some project-wide "scoped_session"

* Each "SQLAlchemyModelFactory" subclass uses this "scoped_session"
  as its "sqlalchemy_session"

* The "tearDown()" method of tests calls "Session.remove" to reset
  the session.

Note: See the excellent *SQLAlchemy guide on scoped_session* for
  details of "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 "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 "scoped_session":

   # myprojet/test/common.py

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

* All factory access it:

   # 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 "scoped_session" when it starts:

   # 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

* "test cases" use this "scoped_session", and clear it after each
  test (for isolation):

   # 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()
