summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/changelog.rst138
-rw-r--r--docs/conf.py8
-rw-r--r--docs/examples.rst18
-rw-r--r--docs/fuzzy.rst39
-rw-r--r--docs/ideas.rst5
-rw-r--r--docs/introduction.rst41
-rw-r--r--docs/orms.rst223
-rw-r--r--docs/recipes.rst173
-rw-r--r--docs/reference.rst335
9 files changed, 832 insertions, 148 deletions
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 47d1139..fa542f4 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,17 +1,149 @@
ChangeLog
=========
+.. _v2.6.0:
+
+2.6.0 (2015-10-20)
+------------------
+
+*New:*
+
+ - Add :attr:`factory.FactoryOptions.rename` to help handle conflicting names (:issue:`206`)
+ - Add support for random-yet-realistic values through `fake-factory <https://pypi.python.org/pypi/fake-factory>`_,
+ through the :class:`factory.Faker` class.
+ - :class:`factory.Iterator` no longer begins iteration of its argument at import time,
+ thus allowing to pass in a lazy iterator such as a Django queryset
+ (i.e ``factory.Iterator(models.MyThingy.objects.all())``).
+ - Simplify imports for ORM layers, now available through a simple ``factory`` import,
+ at ``factory.alchemy.SQLAlchemyModelFactory`` / ``factory.django.DjangoModelFactory`` / ``factory.mongoengine.MongoEngineFactory``.
+
+*Bugfix:*
+
+ - :issue:`201`: Properly handle custom Django managers when dealing with abstract Django models.
+ - :issue:`212`: Fix :meth:`factory.django.mute_signals` to handle Django's signal caching
+ - :issue:`228`: Don't load :func:`django.apps.apps.get_model()` until required
+ - :issue:`219`: Stop using :meth:`mogo.model.Model.new()`, deprecated 4 years ago.
+
+.. _v2.5.2:
+
+2.5.2 (2015-04-21)
+------------------
+
+*Bugfix:*
+
+ - Add support for Django 1.7/1.8
+ - Add support for mongoengine>=0.9.0 / pymongo>=2.1
+
+.. _v2.5.1:
+
+2.5.1 (2015-03-27)
+------------------
+
+*Bugfix:*
+
+ - Respect custom managers in :class:`~factory.django.DjangoModelFactory` (see :issue:`192`)
+ - Allow passing declarations (e.g :class:`~factory.Sequence`) as parameters to :class:`~factory.django.FileField`
+ and :class:`~factory.django.ImageField`.
+
+.. _v2.5.0:
+
+2.5.0 (2015-03-26)
+------------------
+
+*New:*
+
+ - Add support for getting/setting :mod:`factory.fuzzy`'s random state (see :issue:`175`, :issue:`185`).
+ - Support lazy evaluation of iterables in :class:`factory.fuzzy.FuzzyChoice` (see :issue:`184`).
+ - Support non-default databases at the factory level (see :issue:`171`)
+ - Make :class:`factory.django.FileField` and :class:`factory.django.ImageField` non-post_generation, i.e normal fields also available in ``save()`` (see :issue:`141`).
+
+*Bugfix:*
+
+ - Avoid issues when using :meth:`factory.django.mute_signals` on a base factory class (see :issue:`183`).
+ - Fix limitations of :class:`factory.StubFactory`, that can now use :class:`factory.SubFactory` and co (see :issue:`131`).
+
+
+*Deprecation:*
+
+ - Remove deprecated features from :ref:`v2.4.0`
+ - Remove the auto-magical sequence setup (based on the latest primary key value in the database) for Django and SQLAlchemy;
+ this relates to issues :issue:`170`, :issue:`153`, :issue:`111`, :issue:`103`, :issue:`92`, :issue:`78`. See https://github.com/rbarrois/factory_boy/commit/13d310f for technical details.
+
+.. warning:: Version 2.5.0 removes the 'auto-magical sequence setup' bug-and-feature.
+ This could trigger some bugs when tests expected a non-zero sequence reference.
+
+Upgrading
+"""""""""
+
+.. warning:: Version 2.5.0 removes features that were marked as deprecated in :ref:`v2.4.0 <v2.4.0>`.
+
+All ``FACTORY_*``-style attributes are now declared in a ``class Meta:`` section:
+
+.. code-block:: python
+
+ # Old-style, deprecated
+ class MyFactory(factory.Factory):
+ FACTORY_FOR = models.MyModel
+ FACTORY_HIDDEN_ARGS = ['a', 'b', 'c']
+
+ # New-style
+ class MyFactory(factory.Factory):
+ class Meta:
+ model = models.MyModel
+ exclude = ['a', 'b', 'c']
+
+A simple shell command to upgrade the code would be:
+
+.. code-block:: sh
+
+ # sed -i: inplace update
+ # grep -l: only file names, not matching lines
+ sed -i 's/FACTORY_FOR =/class Meta:\n model =/' $(grep -l FACTORY_FOR $(find . -name '*.py'))
+
+This takes care of all ``FACTORY_FOR`` occurences; the files containing other attributes to rename can be found with ``grep -R FACTORY .``
+
+
+.. _v2.4.1:
+
+2.4.1 (2014-06-23)
+------------------
+
+*Bugfix:*
+
+ - Fix overriding deeply inherited attributes (set in one factory, overridden in a subclass, used in a sub-sub-class).
.. _v2.4.0:
-2.4.0 (master)
---------------
+2.4.0 (2014-06-21)
+------------------
*New:*
- Add support for :attr:`factory.fuzzy.FuzzyInteger.step`, thanks to `ilya-pirogov <https://github.com/ilya-pirogov>`_ (:issue:`120`)
- - Add :meth:`~factory.django.mute_signals` decorator to temporarily disable some signals, thanks to `ilya-pirogov <https://github.com>`_ (:issue:`122`)
+ - Add :meth:`~factory.django.mute_signals` decorator to temporarily disable some signals, thanks to `ilya-pirogov <https://github.com/ilya-pirogov>`_ (:issue:`122`)
- Add :class:`~factory.fuzzy.FuzzyFloat` (:issue:`124`)
+ - Declare target model and other non-declaration fields in a ``class Meta`` section.
+
+*Deprecation:*
+
+ - Use of ``FACTORY_FOR`` and other ``FACTORY`` class-level attributes is deprecated and will be removed in 2.5.
+ Those attributes should now declared within the :class:`class Meta <factory.FactoryOptions>` attribute:
+
+ For :class:`factory.Factory`:
+
+ * Rename :attr:`~factory.Factory.FACTORY_FOR` to :attr:`~factory.FactoryOptions.model`
+ * Rename :attr:`~factory.Factory.ABSTRACT_FACTORY` to :attr:`~factory.FactoryOptions.abstract`
+ * Rename :attr:`~factory.Factory.FACTORY_STRATEGY` to :attr:`~factory.FactoryOptions.strategy`
+ * Rename :attr:`~factory.Factory.FACTORY_ARG_PARAMETERS` to :attr:`~factory.FactoryOptions.inline_args`
+ * Rename :attr:`~factory.Factory.FACTORY_HIDDEN_ARGS` to :attr:`~factory.FactoryOptions.exclude`
+
+ For :class:`factory.django.DjangoModelFactory`:
+
+ * Rename :attr:`~factory.django.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` to :attr:`~factory.django.DjangoOptions.django_get_or_create`
+
+ For :class:`factory.alchemy.SQLAlchemyModelFactory`:
+
+ * Rename :attr:`~factory.alchemy.SQLAlchemyModelFactory.FACTORY_SESSION` to :attr:`~factory.alchemy.SQLAlchemyOptions.sqlalchemy_session`
.. _v2.3.1:
diff --git a/docs/conf.py b/docs/conf.py
index 4f76d45..d5b86f4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -51,7 +51,7 @@ master_doc = 'index'
# General information about the project.
project = u'Factory Boy'
-copyright = u'2011-2013, Raphaël Barrois, Mark Sandstrom'
+copyright = u'2011-2015, Raphaël Barrois, Mark Sandstrom'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@@ -114,7 +114,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -247,7 +247,7 @@ intersphinx_mapping = {
'http://docs.djangoproject.com/en/dev/_objects/',
),
'sqlalchemy': (
- 'http://docs.sqlalchemy.org/en/rel_0_8/',
- 'http://docs.sqlalchemy.org/en/rel_0_8/objects.inv',
+ 'http://docs.sqlalchemy.org/en/rel_0_9/',
+ 'http://docs.sqlalchemy.org/en/rel_0_9/objects.inv',
),
}
diff --git a/docs/examples.rst b/docs/examples.rst
index aab990a..e7f6057 100644
--- a/docs/examples.rst
+++ b/docs/examples.rst
@@ -56,14 +56,16 @@ And now, we'll define the related factories:
class AccountFactory(factory.Factory):
- FACTORY_FOR = objects.Account
+ class Meta:
+ model = objects.Account
username = factory.Sequence(lambda n: 'john%s' % n)
email = factory.LazyAttribute(lambda o: '%s@example.org' % o.username)
class ProfileFactory(factory.Factory):
- FACTORY_FOR = objects.Profile
+ class Meta:
+ model = objects.Profile
account = factory.SubFactory(AccountFactory)
gender = factory.Iterator([objects.Profile.GENDER_MALE, objects.Profile.GENDER_FEMALE])
@@ -112,12 +114,9 @@ We can now use our factories, for tests:
def test_get_profile_stats(self):
profiles = []
- for _ in xrange(4):
- profiles.append(factories.ProfileFactory())
- for _ in xrange(2):
- profiles.append(factories.FemaleProfileFactory())
- for _ in xrange(2):
- profiles.append(factories.ProfileFactory(planet='Tatooine'))
+ profiles.extend(factories.ProfileFactory.create_batch(4))
+ profiles.extend(factories.FemaleProfileFactory.create_batch(2))
+ profiles.extend(factories.ProfileFactory.create_batch(2, planet="Tatooine"))
stats = business_logic.profile_stats(profiles)
self.assertEqual({'Earth': 6, 'Mars': 2}, stats.planets)
@@ -131,8 +130,7 @@ Or for fixtures:
from . import factories
def make_objects():
- for _ in xrange(50):
- factories.ProfileFactory()
+ factories.ProfileFactory.create_batch(size=50)
# Let's create a few, known objects.
factories.ProfileFactory(
diff --git a/docs/fuzzy.rst b/docs/fuzzy.rst
index 1480419..6b06608 100644
--- a/docs/fuzzy.rst
+++ b/docs/fuzzy.rst
@@ -8,6 +8,8 @@ Some tests may be interested in testing with fuzzy, random values.
This is handled by the :mod:`factory.fuzzy` module, which provides a few
random declarations.
+.. note:: Use ``import factory.fuzzy`` to load this module.
+
FuzzyAttribute
--------------
@@ -62,8 +64,11 @@ FuzzyChoice
The :class:`FuzzyChoice` fuzzer yields random choices from the given
iterable.
- .. note:: The passed in :attr:`choices` will be converted into a list at
- declaration time.
+ .. note:: The passed in :attr:`choices` will be converted into a list upon
+ first use, not at declaration time.
+
+ This allows passing in, for instance, a Django queryset that will
+ only hit the database during the database, not at import time.
.. attribute:: choices
@@ -338,3 +343,33 @@ They should inherit from the :class:`BaseFuzzyAttribute` class, and override its
The method responsible for generating random values.
*Must* be overridden in subclasses.
+
+
+Managing randomness
+-------------------
+
+Using :mod:`random` in factories allows to "fuzz" a program efficiently.
+However, it's sometimes required to *reproduce* a failing test.
+
+:mod:`factory.fuzzy` uses a separate instance of :class:`random.Random`,
+and provides a few helpers for this:
+
+.. method:: get_random_state()
+
+ Call :meth:`get_random_state` to retrieve the random generator's current
+ state.
+
+.. method:: set_random_state(state)
+
+ Use :meth:`set_random_state` to set a custom state into the random generator
+ (fetched from :meth:`get_random_state` in a previous run, for instance)
+
+.. method:: reseed_random(seed)
+
+ The :meth:`reseed_random` function allows to load a chosen seed into the random generator.
+
+
+Custom :class:`BaseFuzzyAttribute` subclasses **SHOULD**
+use :obj:`factory.fuzzy._random` as a randomness source; this ensures that
+data they generate can be regenerated using the simple state from
+:meth:`get_random_state`.
diff --git a/docs/ideas.rst b/docs/ideas.rst
index 914e640..6e3962d 100644
--- a/docs/ideas.rst
+++ b/docs/ideas.rst
@@ -4,5 +4,6 @@ Ideas
This is a list of future features that may be incorporated into factory_boy:
-* **A 'options' attribute**: instead of adding more class-level constants, use a django-style ``class Meta`` Factory attribute with all options there
-
+* When a :class:`Factory` is built or created, pass the calling context throughout the calling chain instead of custom solutions everywhere
+* Define a proper set of rules for the support of third-party ORMs
+* Properly evaluate nested declarations (e.g ``factory.fuzzy.FuzzyDate(start_date=factory.SelfAttribute('since'))``)
diff --git a/docs/introduction.rst b/docs/introduction.rst
index 86e2046..d00154d 100644
--- a/docs/introduction.rst
+++ b/docs/introduction.rst
@@ -18,10 +18,11 @@ Basic usage
-----------
-Factories declare a set of attributes used to instantiate an object, whose class is defined in the FACTORY_FOR attribute:
+Factories declare a set of attributes used to instantiate an object, whose class is defined in the ``class Meta``'s ``model`` attribute:
- Subclass ``factory.Factory`` (or a more suitable subclass)
-- Set its ``FACTORY_FOR`` attribute to the target class
+- Add a ``class Meta:`` block
+- Set its ``model`` attribute to the target class
- Add defaults for keyword args to pass to the associated class' ``__init__`` method
@@ -31,7 +32,8 @@ Factories declare a set of attributes used to instantiate an object, whose class
from . import base
class UserFactory(factory.Factory):
- FACTORY_FOR = base.User
+ class Meta:
+ model = base.User
firstname = "John"
lastname = "Doe"
@@ -56,7 +58,8 @@ A given class may be associated to many :class:`~factory.Factory` subclasses:
.. code-block:: python
class EnglishUserFactory(factory.Factory):
- FACTORY_FOR = base.User
+ class Meta:
+ model = base.User
firstname = "John"
lastname = "Doe"
@@ -64,7 +67,8 @@ A given class may be associated to many :class:`~factory.Factory` subclasses:
class FrenchUserFactory(factory.Factory):
- FACTORY_FOR = base.User
+ class Meta:
+ model = base.User
firstname = "Jean"
lastname = "Dupont"
@@ -88,7 +92,8 @@ This is achieved with the :class:`~factory.Sequence` declaration:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
username = factory.Sequence(lambda n: 'user%d' % n)
@@ -104,7 +109,8 @@ This is achieved with the :class:`~factory.Sequence` declaration:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
@factory.sequence
def username(n):
@@ -121,7 +127,8 @@ taking the object being built and returning the value for the field:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
username = factory.Sequence(lambda n: 'user%d' % n)
email = factory.LazyAttribute(lambda obj: '%s@example.com' % obj.username)
@@ -146,7 +153,8 @@ taking the object being built and returning the value for the field:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
username = factory.Sequence(lambda n: 'user%d' % n)
@@ -168,7 +176,8 @@ and update them with its own declarations:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = base.User
+ class Meta:
+ model = base.User
firstname = "John"
lastname = "Doe"
@@ -209,13 +218,14 @@ Non-kwarg arguments
Some classes take a few, non-kwarg arguments first.
-This is handled by the :data:`~factory.Factory.FACTORY_ARG_PARAMETERS` attribute:
+This is handled by the :data:`~factory.FactoryOptions.inline_args` attribute:
.. code-block:: python
class MyFactory(factory.Factory):
- FACTORY_FOR = MyClass
- FACTORY_ARG_PARAMETERS = ('x', 'y')
+ class Meta:
+ model = MyClass
+ inline_args = ('x', 'y')
x = 1
y = 2
@@ -251,7 +261,8 @@ Calling a :class:`~factory.Factory` subclass will provide an object through the
.. code-block:: python
class MyFactory(factory.Factory):
- FACTORY_FOR = MyClass
+ class Meta:
+ model = MyClass
.. code-block:: pycon
@@ -265,6 +276,6 @@ Calling a :class:`~factory.Factory` subclass will provide an object through the
<MyClass: X (saved)>
-The default strategy can be changed by setting the class-level :attr:`~factory.Factory.FACTORY_STRATEGY` attribute.
+The default strategy can be changed by setting the ``class Meta`` :attr:`~factory.FactoryOptions.strategy` attribute.
diff --git a/docs/orms.rst b/docs/orms.rst
index c893cac..af20917 100644
--- a/docs/orms.rst
+++ b/docs/orms.rst
@@ -15,7 +15,7 @@ Django
The first versions of factory_boy were designed specifically for Django,
-but the library has now evolved to be framework-independant.
+but the library has now evolved to be framework-independent.
Most features should thus feel quite familiar to Django users.
@@ -32,15 +32,36 @@ All factories for a Django :class:`~django.db.models.Model` should use the
This class provides the following features:
- * The :attr:`~factory.Factory.FACTORY_FOR` attribute also supports the ``'app.Model'``
+ * 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>`
- * :func:`~factory.Factory._setup_next_sequence()` selects the next unused primary key value
* 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.
- .. attribute:: FACTORY_DJANGO_GET_OR_CREATE
+
+.. 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>`
@@ -49,8 +70,9 @@ All factories for a Django :class:`~django.db.models.Model` should use the
.. code-block:: python
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = 'myapp.User' # Equivalent to ``FACTORY_FOR = myapp.models.User``
- FACTORY_DJANGO_GET_OR_CREATE = ('username',)
+ class Meta:
+ model = 'myapp.User' # Equivalent to ``model = myapp.models.User``
+ django_get_or_create = ('username',)
username = 'john'
@@ -80,11 +102,13 @@ All factories for a Django :class:`~django.db.models.Model` should use the
.. code-block:: python
class MyAbstractModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.MyAbstractModel
- ABSTRACT_FACTORY = True
+ class Meta:
+ model = models.MyAbstractModel
+ abstract = True
class MyConcreteModelFactory(MyAbstractModelFactory):
- FACTORY_FOR = models.MyConcreteModel
+ class Meta:
+ model = models.MyConcreteModel
Otherwise, factory_boy will try to get the 'next PK' counter from the abstract model.
@@ -102,7 +126,7 @@ Extra fields
: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
+ if available, unless ``filename`` is also provided.
:param bytes data: Use the provided bytes as file contents
:param str filename: The filename for the FileField
@@ -112,7 +136,8 @@ Extra fields
.. code-block:: python
class MyFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.MyModel
+ class Meta:
+ model = models.MyModel
the_file = factory.django.FileField(filename='the_file.dat')
@@ -149,7 +174,8 @@ Extra fields
.. code-block:: python
class MyFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.MyModel
+ class Meta:
+ model = models.MyModel
the_image = factory.django.ImageField(color='blue')
@@ -188,7 +214,8 @@ To work around this problem, use the :meth:`mute_signals()` decorator/context ma
@factory.django.mute_signals(signals.pre_save, signals.post_save)
class FooFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.Foo
+ class Meta:
+ model = models.Foo
# ...
@@ -241,11 +268,39 @@ factory_boy supports `MongoEngine`_-style models, through the :class:`MongoEngin
* :func:`~factory.Factory.create()` builds an instance through ``__init__`` then
saves it.
- .. note:: If the :attr:`associated class <factory.Factory.FACTORY_FOR>` is a :class:`mongoengine.EmbeddedDocument`,
+ .. 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
----------
@@ -255,7 +310,7 @@ SQLAlchemy
Factoy_boy also supports `SQLAlchemy`_ models through the :class:`SQLAlchemyModelFactory` class.
-To work, this class needs an `SQLAlchemy`_ session object affected to "FACTORY_SESSION" class attribute.
+To work, this class needs an `SQLAlchemy`_ session object affected to the :attr:`Meta.sqlalchemy_session <SQLAlchemyOptions.sqlalchemy_session>` attribute.
.. _SQLAlchemy: http://www.sqlalchemy.org/
@@ -266,13 +321,23 @@ To work, this class needs an `SQLAlchemy`_ session object affected to "FACTORY_S
This class provides the following features:
* :func:`~factory.Factory.create()` uses :meth:`sqlalchemy.orm.session.Session.add`
- * :func:`~factory.Factory._setup_next_sequence()` selects the next unused primary key value
- .. attribute:: FACTORY_SESSION
- Fields whose SQLAlchemy session object are passed will be used to communicate with the database
+.. 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
-A (very) simple exemple:
+ SQLAlchemy session to use to communicate with the database when creating
+ an object through this :class:`SQLAlchemyModelFactory`.
+
+ .. attribute:: force_flush
+
+ Force a session flush() at the end of :func:`~factory.alchemy.SQLAlchemyModelFactory._create()`.
+
+A (very) simple example:
.. code-block:: python
@@ -280,9 +345,8 @@ A (very) simple exemple:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
- session = scoped_session(sessionmaker())
engine = create_engine('sqlite://')
- session.configure(bind=engine)
+ session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
@@ -295,10 +359,12 @@ A (very) simple exemple:
Base.metadata.create_all(engine)
+ import factory
- class UserFactory(SQLAlchemyModelFactory):
- FACTORY_FOR = User
- FACTORY_SESSION = session # the SQLAlchemy session object
+ 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)
@@ -311,3 +377,112 @@ A (very) simple exemple:
<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()
diff --git a/docs/recipes.rst b/docs/recipes.rst
index 7a6bf23..df86bac 100644
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -26,12 +26,36 @@ use the :class:`~factory.SubFactory` declaration:
from . import models
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
first_name = factory.Sequence(lambda n: "Agent %03d" % n)
group = factory.SubFactory(GroupFactory)
+Choosing from a populated table
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the target of the :class:`~django.db.models.ForeignKey` should be
+chosen from a pre-populated table
+(e.g :class:`django.contrib.contenttypes.models.ContentType`),
+simply use a :class:`factory.Iterator` on the chosen queryset:
+
+.. code-block:: python
+
+ import factory, factory.django
+ from . import models
+
+ class UserFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = models.User
+
+ language = factory.Iterator(models.Language.objects.all())
+
+Here, ``models.Language.objects.all()`` won't be evaluated until the
+first call to ``UserFactory``; thus avoiding DB queries at import time.
+
+
Reverse dependencies (reverse ForeignKey)
-----------------------------------------
@@ -53,7 +77,8 @@ use a :class:`~factory.RelatedFactory` declaration:
# factories.py
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
log = factory.RelatedFactory(UserLogFactory, 'user', action=models.UserLog.ACTION_CREATE)
@@ -75,7 +100,8 @@ factory_boy allows to define attributes of such profiles dynamically when creati
.. code-block:: python
class ProfileFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = my_models.Profile
+ class Meta:
+ model = my_models.Profile
title = 'Dr'
# We pass in profile=None to prevent UserFactory from creating another profile
@@ -83,7 +109,8 @@ factory_boy allows to define attributes of such profiles dynamically when creati
user = factory.SubFactory('app.factories.UserFactory', profile=None)
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = auth_models.User
+ class Meta:
+ model = auth_models.User
username = factory.Sequence(lambda n: "user_%d" % n)
@@ -121,8 +148,8 @@ factory_boy related factories.
method.
-Simple ManyToMany
------------------
+Simple Many-to-many relationship
+--------------------------------
Building the adequate link between two models depends heavily on the use case;
factory_boy doesn't provide a "all in one tools" as for :class:`~factory.SubFactory`
@@ -140,17 +167,19 @@ hook:
class User(models.Model):
name = models.CharField()
- groups = models.ManyToMany(Group)
+ groups = models.ManyToManyField(Group)
# factories.py
class GroupFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.Group
+ class Meta:
+ model = models.Group
name = factory.Sequence(lambda n: "Group #%s" % n)
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
name = "John Doe"
@@ -175,8 +204,8 @@ the ``groups`` declaration will add passed in groups to the set of groups for th
user.
-ManyToMany with a 'through'
----------------------------
+Many-to-many relation with a 'through'
+--------------------------------------
If only one link is required, this can be simply performed with a :class:`RelatedFactory`.
@@ -190,7 +219,7 @@ If more links are needed, simply add more :class:`RelatedFactory` declarations:
class Group(models.Model):
name = models.CharField()
- members = models.ManyToMany(User, through='GroupLevel')
+ members = models.ManyToManyField(User, through='GroupLevel')
class GroupLevel(models.Model):
user = models.ForeignKey(User)
@@ -200,17 +229,20 @@ If more links are needed, simply add more :class:`RelatedFactory` declarations:
# factories.py
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
name = "John Doe"
class GroupFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.Group
+ class Meta:
+ model = models.Group
name = "Admins"
class GroupLevelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.GroupLevel
+ class Meta:
+ model = models.GroupLevel
user = factory.SubFactory(UserFactory)
group = factory.SubFactory(GroupFactory)
@@ -273,20 +305,23 @@ Here, we want:
# factories.py
class CountryFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.Country
+ class Meta:
+ model = models.Country
name = factory.Iterator(["France", "Italy", "Spain"])
lang = factory.Iterator(['fr', 'it', 'es'])
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
name = "John"
lang = factory.SelfAttribute('country.lang')
country = factory.SubFactory(CountryFactory)
class CompanyFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.Company
+ class Meta:
+ model = models.Company
name = "ACME, Inc."
country = factory.SubFactory(CountryFactory)
@@ -302,14 +337,110 @@ default :meth:`Model.objects.create() <django.db.models.query.QuerySet.create>`
.. code-block:: python
class UserFactory(factory.DjangoModelFactory):
- FACTORY_FOR = UserenaSignup
+ class Meta:
+ model = UserenaSignup
+
username = "l7d8s"
email = "my_name@example.com"
password = "my_password"
@classmethod
- def _create(cls, target_class, *args, **kwargs):
+ def _create(cls, model_class, *args, **kwargs):
"""Override the default ``_create`` with our custom call."""
- manager = cls._get_manager(target_class)
+ manager = cls._get_manager(model_class)
# The default would use ``manager.create(*args, **kwargs)``
return manager.create_user(*args, **kwargs)
+
+
+Forcing the sequence counter
+----------------------------
+
+A common pattern with factory_boy is to use a :class:`factory.Sequence` declaration
+to provide varying values to attributes declared as unique.
+
+However, it is sometimes useful to force a given value to the counter, for instance
+to ensure that tests are properly reproductible.
+
+factory_boy provides a few hooks for this:
+
+
+Forcing the value on a per-call basis
+ In order to force the counter for a specific :class:`~factory.Factory` instantiation,
+ just pass the value in the ``__sequence=42`` parameter:
+
+ .. code-block:: python
+
+ class AccountFactory(factory.Factory):
+ class Meta:
+ model = Account
+ uid = factory.Sequence(lambda n: n)
+ name = "Test"
+
+ .. code-block:: pycon
+
+ >>> obj1 = AccountFactory(name="John Doe", __sequence=10)
+ >>> obj1.uid # Taken from the __sequence counter
+ 10
+ >>> obj2 = AccountFactory(name="Jane Doe")
+ >>> obj2.uid # The base sequence counter hasn't changed
+ 1
+
+
+Resetting the counter globally
+ If all calls for a factory must start from a deterministic number,
+ use :meth:`factory.Factory.reset_sequence`; this will reset the counter
+ to its initial value (as defined by :meth:`factory.Factory._setup_next_sequence`).
+
+ .. code-block:: pycon
+
+ >>> AccountFactory().uid
+ 1
+ >>> AccountFactory().uid
+ 2
+ >>> AccountFactory.reset_sequence()
+ >>> AccountFactory().uid # Reset to the initial value
+ 1
+ >>> AccountFactory().uid
+ 2
+
+ It is also possible to reset the counter to a specific value:
+
+ .. code-block:: pycon
+
+ >>> AccountFactory.reset_sequence(10)
+ >>> AccountFactory().uid
+ 10
+ >>> AccountFactory().uid
+ 11
+
+ This recipe is most useful in a :class:`~unittest.TestCase`'s
+ :meth:`~unittest.TestCase.setUp` method.
+
+
+Forcing the initial value for all projects
+ The sequence counter of a :class:`~factory.Factory` can also be set
+ automatically upon the first call through the
+ :meth:`~factory.Factory._setup_next_sequence` method; this helps when the
+ objects's attributes mustn't conflict with pre-existing data.
+
+ A typical example is to ensure that running a Python script twice will create
+ non-conflicting objects, by setting up the counter to "max used value plus one":
+
+ .. code-block:: python
+
+ class AccountFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = models.Account
+
+ @classmethod
+ def _setup_next_sequence(cls):
+ try:
+ return models.Accounts.objects.latest('uid').uid + 1
+ except models.Account.DoesNotExist:
+ return 1
+
+ .. code-block:: pycon
+
+ >>> Account.objects.create(uid=42, name="Blah")
+ >>> AccountFactory.create() # Sets up the account number based on the latest uid
+ <Account uid=43, name=Test>
diff --git a/docs/reference.rst b/docs/reference.rst
index 53584a0..b5ccd16 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -11,37 +11,54 @@ For internals and customization points, please refer to the :doc:`internals` sec
The :class:`Factory` class
--------------------------
-.. class:: Factory
+.. class:: FactoryOptions
+
+ .. versionadded:: 2.4.0
+
+ A :class:`Factory`'s behaviour can be tuned through a few settings.
- The :class:`Factory` class is the base of factory_boy features.
+ For convenience, they are declared in a single ``class Meta`` attribute:
- It accepts a few specific attributes (must be specified on class declaration):
+ .. code-block:: python
+
+ class MyFactory(factory.Factory):
+ class Meta:
+ model = MyObject
+ abstract = False
- .. attribute:: FACTORY_FOR
+ .. attribute:: model
This optional attribute describes the class of objects to generate.
If unset, it will be inherited from parent :class:`Factory` subclasses.
- .. attribute:: ABSTRACT_FACTORY
+ .. versionadded:: 2.4.0
+
+ .. attribute:: abstract
This attribute indicates that the :class:`Factory` subclass should not
be used to generate objects, but instead provides some extra defaults.
It will be automatically set to ``True`` if neither the :class:`Factory`
- subclass nor its parents define the :attr:`~Factory.FACTORY_FOR` attribute.
+ subclass nor its parents define the :attr:`~FactoryOptions.model` attribute.
- .. attribute:: FACTORY_ARG_PARAMETERS
+ .. warning:: This flag is reset to ``False`` when a :class:`Factory` subclasses
+ another one if a :attr:`~FactoryOptions.model` is set.
+
+ .. versionadded:: 2.4.0
+
+ .. attribute:: inline_args
Some factories require non-keyword arguments to their :meth:`~object.__init__`.
- They should be listed, in order, in the :attr:`FACTORY_ARG_PARAMETERS`
+ They should be listed, in order, in the :attr:`inline_args`
attribute:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
- FACTORY_ARG_PARAMETERS = ('login', 'email')
+ class Meta:
+ model = User
+ inline_args = ('login', 'email')
login = 'john'
email = factory.LazyAttribute(lambda o: '%s@example.com' % o.login)
@@ -53,22 +70,25 @@ The :class:`Factory` class
<User: john>
>>> User('john', 'john@example.com', firstname="John") # actual call
- .. attribute:: FACTORY_HIDDEN_ARGS
+ .. versionadded:: 2.4.0
+
+ .. attribute:: exclude
While writing a :class:`Factory` for some object, it may be useful to
have general fields helping defining others, but that should not be
- passed to the target class; for instance, a field named 'now' that would
+ passed to the model class; for instance, a field named 'now' that would
hold a reference time used by other objects.
- Factory fields whose name are listed in :attr:`FACTORY_HIDDEN_ARGS` will
+ Factory fields whose name are listed in :attr:`exclude` will
be removed from the set of args/kwargs passed to the underlying class;
they can be any valid factory_boy declaration:
.. code-block:: python
class OrderFactory(factory.Factory):
- FACTORY_FOR = Order
- FACTORY_HIDDEN_ARGS = ('now',)
+ class Meta:
+ model = Order
+ exclude = ('now',)
now = factory.LazyAttribute(lambda o: datetime.datetime.utcnow())
started_at = factory.LazyAttribute(lambda o: o.now - datetime.timedelta(hours=1))
@@ -83,6 +103,60 @@ The :class:`Factory` class
>>> OrderFactory(now=datetime.datetime(2013, 4, 1, 10))
<Order: started 2013-04-01 09:00:00, paid 2013-04-01 09:10:00>
+ .. versionadded:: 2.4.0
+
+
+ .. attribute:: rename
+
+ Sometimes, a model expect a field with a name already used by one
+ of :class:`Factory`'s methods.
+
+ In this case, the :attr:`rename` attributes allows to define renaming
+ rules: the keys of the :attr:`rename` dict are those used in the
+ :class:`Factory` declarations, and their values the new name:
+
+ .. code-block:: python
+
+ class ImageFactory(factory.Factory):
+ # The model expects "attributes"
+ form_attributes = ['thumbnail', 'black-and-white']
+
+ class Meta:
+ model = Image
+ rename = {'form_attributes': 'attributes'}
+
+ .. versionadded: 2.6.0
+
+
+ .. attribute:: strategy
+
+ Use this attribute to change the strategy used by a :class:`Factory`.
+ The default is :data:`CREATE_STRATEGY`.
+
+
+
+.. class:: Factory
+
+
+ **Class-level attributes:**
+
+ .. attribute:: _meta
+
+ .. versionadded:: 2.4.0
+
+ The :class:`FactoryOptions` instance attached to a :class:`Factory` class is available
+ as a :attr:`_meta` attribute.
+
+ .. attribute:: _options_class
+
+ .. versionadded:: 2.4.0
+
+ If a :class:`Factory` subclass needs to define additional, extra options, it has to
+ provide a custom :class:`FactoryOptions` subclass.
+
+ A pointer to that custom class should be provided as :attr:`_options_class` so that
+ the :class:`Factory`-building metaclass can use it instead.
+
**Base functions:**
@@ -162,7 +236,7 @@ The :class:`Factory` class
The :meth:`_adjust_kwargs` extension point allows for late fields tuning.
It is called once keyword arguments have been resolved and post-generation
- items removed, but before the :attr:`FACTORY_ARG_PARAMETERS` extraction
+ items removed, but before the :attr:`~FactoryOptions.inline_args` extraction
phase.
.. code-block:: python
@@ -177,7 +251,6 @@ The :class:`Factory` class
.. OHAI_VIM**
-
.. classmethod:: _setup_next_sequence(cls)
This method will compute the first value to use for the sequence counter
@@ -189,19 +262,19 @@ The :class:`Factory` class
Subclasses may fetch the next free ID from the database, for instance.
- .. classmethod:: _build(cls, target_class, *args, **kwargs)
+ .. classmethod:: _build(cls, model_class, *args, **kwargs)
.. OHAI_VIM*
This class method is called whenever a new instance needs to be built.
- It receives the target class (provided to :attr:`FACTORY_FOR`), and
+ It receives the model class (provided to :attr:`~FactoryOptions.model`), and
the positional and keyword arguments to use for the class once all has
been computed.
Subclasses may override this for custom APIs.
- .. classmethod:: _create(cls, target_class, *args, **kwargs)
+ .. classmethod:: _create(cls, model_class, *args, **kwargs)
.. OHAI_VIM*
@@ -214,10 +287,11 @@ The :class:`Factory` class
.. code-block:: python
class BaseBackendFactory(factory.Factory):
- ABSTRACT_FACTORY = True # Optional
+ class Meta:
+ abstract = True # Optional
- def _create(cls, target_class, *args, **kwargs):
- obj = target_class(*args, **kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ obj = model_class(*args, **kwargs)
obj.save()
return obj
@@ -254,7 +328,7 @@ The :class:`Factory` class
>>> SomeFactory._next_sequence
4
- Since subclasses of a non-:attr:`abstract <factory.Factory.ABSTRACT_FACTORY>`
+ Since subclasses of a non-:attr:`abstract <factory.FactoryOptions.abstract>`
:class:`~factory.Factory` share the same sequence counter, special care needs
to be taken when resetting the counter of such a subclass.
@@ -293,7 +367,7 @@ factory_boy supports two main strategies for generating instances, plus stubs.
but not persisted to any datastore.
It is usually a simple call to the :meth:`~object.__init__` method of the
- :attr:`~Factory.FACTORY_FOR` class.
+ :attr:`~FactoryOptions.model` class.
.. data:: CREATE_STRATEGY
@@ -316,7 +390,7 @@ factory_boy supports two main strategies for generating instances, plus stubs.
when using the ``create`` strategy.
That policy will be used if the
- :attr:`associated class <Factory.FACTORY_FOR>` has an ``objects``
+ :attr:`associated class <FactoryOptions.model>` has an ``objects``
attribute *and* the :meth:`~Factory._create` classmethod of the
:class:`Factory` wasn't overridden.
@@ -337,7 +411,7 @@ factory_boy supports two main strategies for generating instances, plus stubs.
.. data:: STUB_STRATEGY
The 'stub' strategy is an exception in the factory_boy world: it doesn't return
- an instance of the :attr:`~Factory.FACTORY_FOR` class, and actually doesn't
+ an instance of the :attr:`~FactoryOptions.model` class, and actually doesn't
require one to be present.
Instead, it returns an instance of :class:`StubObject` whose attributes have been
@@ -359,7 +433,7 @@ factory_boy supports two main strategies for generating instances, plus stubs.
.. class:: StubFactory(Factory)
- An :attr:`abstract <Factory.ABSTRACT_FACTORY>` :class:`Factory`,
+ An :attr:`abstract <FactoryOptions.abstract>` :class:`Factory`,
with a default strategy set to :data:`STUB_STRATEGY`.
@@ -400,6 +474,83 @@ factory_boy supports two main strategies for generating instances, plus stubs.
Declarations
------------
+
+Faker
+"""""
+
+.. class:: Faker(provider, locale=None, **kwargs)
+
+ .. OHAIVIM**
+
+ In order to easily define realistic-looking factories,
+ use the :class:`Faker` attribute declaration.
+
+ This is a wrapper around `fake-factory <https://pypi.python.org/pypi/fake-factory>`_;
+ its argument is the name of a ``fake-factory`` provider:
+
+ .. code-block:: python
+
+ class UserFactory(factory.Factory):
+ class Meta:
+ model = User
+
+ name = factory.Faker('name')
+
+ .. code-block:: pycon
+
+ >>> user = UserFactory()
+ >>> user.name
+ 'Lucy Cechtelar'
+
+
+ .. attribute:: locale
+
+ If a custom locale is required for one specific field,
+ use the ``locale`` parameter:
+
+ .. code-block:: python
+
+ class UserFactory(factory.Factory):
+ class Meta:
+ model = User
+
+ name = factory.Faker('name', locale='fr_FR')
+
+ .. code-block:: pycon
+
+ >>> user = UserFactory()
+ >>> user.name
+ 'Jean Valjean'
+
+
+ .. classmethod:: override_default_locale(cls, locale)
+
+ If the locale needs to be overridden for a whole test,
+ use :meth:`~factory.Faker.override_default_locale`:
+
+ .. code-block:: pycon
+
+ >>> with factory.Faker.override_default_locale('de_DE'):
+ ... UserFactory()
+ <User: Johannes Brahms>
+
+ .. classmethod:: add_provider(cls, locale=None)
+
+ Some projects may need to fake fields beyond those provided by ``fake-factory``;
+ in such cases, use :meth:`factory.Faker.add_provider` to declare additional providers
+ for those fields:
+
+ .. code-block:: python
+
+ factory.Faker.add_provider(SmileyProvider)
+
+ class FaceFactory(factory.Factory):
+ class Meta:
+ model = Face
+
+ smiley = factory.Faker('smiley')
+
+
LazyAttribute
"""""""""""""
@@ -414,7 +565,8 @@ accept the object being built as sole argument, and return a value.
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
username = 'john'
email = factory.LazyAttribute(lambda o: '%s@example.com' % o.username)
@@ -449,7 +601,8 @@ return value of the method:
.. code-block:: python
class UserFactory(factory.Factory)
- FACTORY_FOR = User
+ class Meta:
+ model = User
name = u"Jean"
@@ -481,13 +634,14 @@ This declaration takes a single argument, a function accepting a single paramete
.. note:: An extra kwarg argument, ``type``, may be provided.
- This feature is deprecated in 1.3.0 and will be removed in 2.0.0.
+ This feature was deprecated in 1.3.0 and will be removed in 2.0.0.
.. code-block:: python
class UserFactory(factory.Factory)
- FACTORY_FOR = User
+ class Meta:
+ model = User
phone = factory.Sequence(lambda n: '123-555-%04d' % n)
@@ -512,7 +666,8 @@ be the sequence counter - this might be confusing:
.. code-block:: python
class UserFactory(factory.Factory)
- FACTORY_FOR = User
+ class Meta:
+ model = User
@factory.sequence
def phone(n):
@@ -537,7 +692,8 @@ The sequence counter is shared across all :class:`Sequence` attributes of the
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
phone = factory.Sequence(lambda n: '%04d' % n)
office = factory.Sequence(lambda n: 'A23-B%03d' % n)
@@ -561,7 +717,8 @@ sequence counter is shared:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
phone = factory.Sequence(lambda n: '123-555-%04d' % n)
@@ -596,7 +753,8 @@ class-level value.
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
uid = factory.Sequence(int)
@@ -631,7 +789,8 @@ It takes a single argument, a function whose two parameters are, in order:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
login = 'john'
email = factory.LazyAttributeSequence(lambda o, n: '%s@s%d.example.com' % (o.login, n))
@@ -655,7 +814,8 @@ handles more complex cases:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
login = 'john'
@@ -692,7 +852,8 @@ The :class:`SubFactory` attribute should be called with:
.. code-block:: python
class FooFactory(factory.Factory):
- FACTORY_FOR = Foo
+ class Meta:
+ model = Foo
bar = factory.SubFactory(BarFactory) # Not BarFactory()
@@ -705,7 +866,8 @@ Definition
# A standard factory
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
# Various fields
first_name = 'John'
@@ -714,7 +876,8 @@ Definition
# A factory for an object with a 'User' field
class CompanyFactory(factory.Factory):
- FACTORY_FOR = Company
+ class Meta:
+ model = Company
name = factory.Sequence(lambda n: 'FactoryBoyz' + 'z' * n)
@@ -794,13 +957,15 @@ This issue can be handled by passing the absolute import path to the target
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
username = 'john'
main_group = factory.SubFactory('users.factories.GroupFactory')
class GroupFactory(factory.Factory):
- FACTORY_FOR = Group
+ class Meta:
+ model = Group
name = "MyGroup"
owner = factory.SubFactory(UserFactory)
@@ -828,7 +993,8 @@ That declaration takes a single argument, a dot-delimited path to the attribute
.. code-block:: python
class UserFactory(factory.Factory)
- FACTORY_FOR = User
+ class Meta:
+ model = User
birthdate = factory.Sequence(lambda n: datetime.date(2000, 1, 1) + datetime.timedelta(days=n))
birthmonth = factory.SelfAttribute('birthdate.month')
@@ -854,13 +1020,15 @@ gains an "upward" semantic through the double-dot notation, as used in Python im
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
language = 'en'
class CompanyFactory(factory.Factory):
- FACTORY_FOR = Company
+ class Meta:
+ model = Company
country = factory.SubFactory(CountryFactory)
owner = factory.SubFactory(UserFactory, language=factory.SelfAttribute('..country.language'))
@@ -888,7 +1056,8 @@ through the :attr:`~containers.LazyStub.factory_parent` attribute of the passed-
.. code-block:: python
class CompanyFactory(factory.Factory):
- FACTORY_FOR = Company
+ class Meta:
+ model = Company
country = factory.SubFactory(CountryFactory)
owner = factory.SubFactory(UserFactory,
language=factory.LazyAttribute(lambda user: user.factory_parent.country.language),
@@ -966,7 +1135,8 @@ adequate value.
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
# CATEGORY_CHOICES is a list of (key, title) tuples
category = factory.Iterator(User.CATEGORY_CHOICES, getter=lambda c: c[0])
@@ -987,7 +1157,8 @@ use the :func:`iterator` decorator:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
@factory.iterator
def name():
@@ -1030,7 +1201,8 @@ with the :class:`Dict` and :class:`List` attributes:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
is_superuser = False
roles = factory.Dict({
@@ -1066,7 +1238,8 @@ with the :class:`Dict` and :class:`List` attributes:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
flags = factory.List([
'user',
@@ -1113,10 +1286,11 @@ For instance, a :class:`PostGeneration` hook is declared as ``post``:
.. code-block:: python
class SomeFactory(factory.Factory):
- FACTORY_FOR = SomeObject
+ class Meta:
+ model = SomeObject
@post_generation
- def post(self, create, extracted, **kwargs):
+ def post(obj, create, extracted, **kwargs):
obj.set_origin(create)
.. OHAI_VIM**
@@ -1128,7 +1302,7 @@ When calling the factory, some arguments will be extracted for this method:
- Any argument starting with ``post__XYZ`` will be extracted, its ``post__`` prefix
removed, and added to the kwargs passed to the post-generation hook.
-Extracted arguments won't be passed to the :attr:`~Factory.FACTORY_FOR` class.
+Extracted arguments won't be passed to the :attr:`~FactoryOptions.model` class.
Thus, in the following call:
@@ -1142,7 +1316,7 @@ Thus, in the following call:
)
The ``post`` hook will receive ``1`` as ``extracted`` and ``{'y': 3, 'z__t': 42}``
-as keyword arguments; ``{'post_x': 2}`` will be passed to ``SomeFactory.FACTORY_FOR``.
+as keyword arguments; ``{'post_x': 2}`` will be passed to ``SomeFactory._meta.model``.
RelatedFactory
@@ -1184,7 +1358,8 @@ RelatedFactory
.. code-block:: python
class FooFactory(factory.Factory):
- FACTORY_FOR = Foo
+ class Meta:
+ model = Foo
bar = factory.RelatedFactory(BarFactory) # Not BarFactory()
@@ -1192,13 +1367,15 @@ RelatedFactory
.. code-block:: python
class CityFactory(factory.Factory):
- FACTORY_FOR = City
+ class Meta:
+ model = City
capital_of = None
name = "Toronto"
class CountryFactory(factory.Factory):
- FACTORY_FOR = Country
+ class Meta:
+ model = Country
lang = 'fr'
capital_city = factory.RelatedFactory(CityFactory, 'capital_of', name="Paris")
@@ -1235,12 +1412,29 @@ If a value if passed for the :class:`RelatedFactory` attribute, this disables
1
+.. note:: The target of the :class:`RelatedFactory` is evaluated *after* the initial factory has been instantiated.
+ This means that calls to :class:`factory.SelfAttribute` cannot go higher than this :class:`RelatedFactory`:
+
+ .. code-block:: python
+
+ class CountryFactory(factory.Factory):
+ class Meta:
+ model = Country
+
+ lang = 'fr'
+ capital_city = factory.RelatedFactory(CityFactory, 'capital_of',
+ # factory.SelfAttribute('..lang') will crash, since the context of
+ # ``CountryFactory`` has already been evaluated.
+ main_lang=factory.SelfAttribute('capital_of.lang'),
+ )
+
+
PostGeneration
""""""""""""""
.. class:: PostGeneration(callable)
-The :class:`PostGeneration` declaration performs actions once the target object
+The :class:`PostGeneration` declaration performs actions once the model object
has been generated.
Its sole argument is a callable, that will be called once the base object has
@@ -1260,7 +1454,8 @@ as ``callable(obj, create, extracted, **kwargs)``, where:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
login = 'john'
make_mbox = factory.PostGeneration(
@@ -1280,7 +1475,8 @@ A decorator is also provided, decorating a single method accepting the same
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
login = 'john'
@@ -1316,7 +1512,7 @@ PostGenerationMethodCall
.. attribute:: method_name
- The name of the method to call on the :attr:`~Factory.FACTORY_FOR` object
+ The name of the method to call on the :attr:`~FactoryOptions.model` object
.. attribute:: args
@@ -1340,7 +1536,8 @@ attribute like below:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
username = 'user'
password = factory.PostGenerationMethodCall('set_password',
@@ -1390,7 +1587,8 @@ factory during instantiation.
.. code-block:: python
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
username = 'user'
password = factory.PostGenerationMethodCall('set_password',
@@ -1404,7 +1602,8 @@ example, if we declared the ``password`` attribute like the following,
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
username = 'user'
password = factory.PostGenerationMethodCall('set_password', '', 'sha1')
@@ -1467,7 +1666,8 @@ Lightweight factory declaration
# This is equivalent to:
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
login = 'john'
email = factory.LazyAttribute(lambda u: '%s@example.com' % u.login)
@@ -1486,7 +1686,8 @@ Lightweight factory declaration
# This is equivalent to:
class UserFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
login = 'john'
email = factory.LazyAttribute(lambda u: '%s@example.com' % u.login)