summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Goirand <thomas@goirand.fr>2014-07-03 15:13:48 +0800
committerThomas Goirand <thomas@goirand.fr>2014-07-03 15:13:48 +0800
commit45918ff19633304a0e2a4d389a958bee46c03eff (patch)
tree8fb81a4a9bb21ef113fa14eeaec41ee8ceb1c1cb
parent80628cd9bde3d8ffc989541a9171a569a5101dc0 (diff)
parent87f8cc0cc0d2f48f489c81b8c93e8ab6de6cff26 (diff)
downloadfactory-boy-45918ff19633304a0e2a4d389a958bee46c03eff.tar
factory-boy-45918ff19633304a0e2a4d389a958bee46c03eff.tar.gz
Merge tag '2.4.1' into debian/unstable
Release of factory_boy 2.4.1
-rw-r--r--README.rst82
-rw-r--r--dev_requirements.txt3
-rw-r--r--docs/changelog.rst44
-rw-r--r--docs/examples.rst18
-rw-r--r--docs/fuzzy.rst40
-rw-r--r--docs/ideas.rst3
-rw-r--r--docs/introduction.rst41
-rw-r--r--docs/orms.rst97
-rw-r--r--docs/recipes.rst62
-rw-r--r--docs/reference.rst243
-rw-r--r--factory/__init__.py4
-rw-r--r--factory/alchemy.py27
-rw-r--r--factory/base.py424
-rw-r--r--factory/containers.py69
-rw-r--r--factory/declarations.py4
-rw-r--r--factory/django.py130
-rw-r--r--factory/fuzzy.py22
-rw-r--r--factory/helpers.py5
-rw-r--r--factory/mogo.py11
-rw-r--r--factory/mongoengine.py12
-rwxr-xr-xsetup.py2
-rw-r--r--tests/__init__.py1
-rw-r--r--tests/alter_time.py2
-rw-r--r--tests/cyclic/bar.py3
-rw-r--r--tests/cyclic/foo.py3
-rw-r--r--tests/djapp/models.py13
-rw-r--r--tests/test_alchemy.py17
-rw-r--r--tests/test_base.py233
-rw-r--r--tests/test_containers.py103
-rw-r--r--tests/test_deprecation.py49
-rw-r--r--tests/test_django.py176
-rw-r--r--tests/test_fuzzy.py22
-rw-r--r--tests/test_mongoengine.py6
-rw-r--r--tests/test_using.py431
34 files changed, 1663 insertions, 739 deletions
diff --git a/README.rst b/README.rst
index 0371b28..32b93bd 100644
--- a/README.rst
+++ b/README.rst
@@ -6,20 +6,61 @@ factory_boy
factory_boy is a fixtures replacement based on thoughtbot's `factory_girl <http://github.com/thoughtbot/factory_girl>`_.
-Its features include:
+As a fixtures replacement tool, it aims to replace static, hard to maintain fixtures
+with easy-to-use factories for complex object.
-- Straightforward syntax
+Instead of building an exhaustive test setup with every possible combination of corner cases,
+``factory_boy`` allows you to use objects customized for the current test,
+while only declaring the test-specific fields:
+
+.. code-block:: python
+
+ class FooTests(unittest.TestCase):
+
+ def test_with_factory_boy(self):
+ # We need a 200€, paid order, shipping to australia, for a VIP customer
+ order = OrderFactory(
+ amount=200,
+ status='PAID',
+ customer__is_vip=True,
+ address__country='AU',
+ )
+ # Run the tests here
+
+ def test_without_factory_boy(self):
+ address = Address(
+ street="42 fubar street",
+ zipcode="42Z42",
+ city="Sydney",
+ country="AU",
+ )
+ customer = Customer(
+ first_name="John",
+ last_name="Doe",
+ phone="+1234",
+ email="john.doe@example.org",
+ active=True,
+ is_vip=True,
+ address=address,
+ )
+ # etc.
+
+factory_boy is designed to work well with various ORMs (Django, Mogo, SQLAlchemy),
+and can easily be extended for other libraries.
+
+Its main features include:
+
+- Straightforward declarative syntax
+- Chaining factory calls while retaining the global context
- Support for multiple build strategies (saved/unsaved instances, attribute dicts, stubbed objects)
-- Powerful helpers for common cases (sequences, sub-factories, reverse dependencies, circular factories, ...)
- Multiple factories per class support, including inheritance
-- Support for various ORMs (currently Django, Mogo, SQLAlchemy)
Links
-----
* Documentation: http://factoryboy.readthedocs.org/
-* Official repository: https://github.com/rbarrois/factory_boy
+* Repository: https://github.com/rbarrois/factory_boy
* Package: https://pypi.python.org/pypi/factory_boy/
factory_boy supports Python 2.6, 2.7, 3.2 and 3.3, as well as PyPy; it requires only the standard Python library.
@@ -53,7 +94,8 @@ Usage
Defining factories
""""""""""""""""""
-Factories declare a set of attributes used to instantiate an object. The class of the object must be defined in the FACTORY_FOR attribute:
+Factories declare a set of attributes used to instantiate an object.
+The class of the object must be defined in the ``model`` field of a ``class Meta:`` attribute:
.. code-block:: python
@@ -61,7 +103,8 @@ Factories declare a set of attributes used to instantiate an object. The class o
from . import models
class UserFactory(factory.Factory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
first_name = 'John'
last_name = 'Doe'
@@ -69,7 +112,8 @@ Factories declare a set of attributes used to instantiate an object. The class o
# Another, different, factory for the same object
class AdminFactory(factory.Factory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
first_name = 'Admin'
last_name = 'User'
@@ -111,6 +155,16 @@ No matter which strategy is used, it's possible to override the defined attribut
"Joe"
+It is also possible to create a bunch of objects in a single call:
+
+.. code-block:: pycon
+
+ >>> users = UserFactory.build(10, first_name="Joe")
+ >>> len(users)
+ 10
+ >>> [user.first_name for user in users]
+ ["Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe", "Joe"]
+
Lazy Attributes
"""""""""""""""
@@ -123,7 +177,9 @@ These "lazy" attributes can be added as follows:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
+
first_name = 'Joe'
last_name = 'Blow'
email = factory.LazyAttribute(lambda a: '{0}.{1}@example.com'.format(a.first_name, a.last_name).lower())
@@ -142,7 +198,9 @@ Unique values in a specific format (for example, e-mail addresses) can be genera
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = models.User
+ class Meta:
+ model = models.User
+
email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n))
>>> UserFactory().email
@@ -160,7 +218,9 @@ This is handled by the ``SubFactory`` helper:
.. code-block:: python
class PostFactory(factory.Factory):
- FACTORY_FOR = models.Post
+ class Meta:
+ model = models.Post
+
author = factory.SubFactory(UserFactory)
diff --git a/dev_requirements.txt b/dev_requirements.txt
index e828644..bdc23d0 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -2,4 +2,5 @@ coverage
Django
Pillow
sqlalchemy
-mongoengine \ No newline at end of file
+mongoengine
+mock
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 4917578..7d77f7f 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -2,6 +2,50 @@ ChangeLog
=========
+.. _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 (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 :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.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:
2.3.1 (2014-01-22)
diff --git a/docs/examples.rst b/docs/examples.rst
index aab990a..ee521e3 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.batch_create(4))
+ profiles.extend(factories.FemaleProfileFactory.batch_create(2))
+ profiles.extend(factories.ProfileFactory.batch_create(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.batch_create(size=50)
# Let's create a few, known objects.
factories.ProfileFactory(
diff --git a/docs/fuzzy.rst b/docs/fuzzy.rst
index b94dfa5..1480419 100644
--- a/docs/fuzzy.rst
+++ b/docs/fuzzy.rst
@@ -73,7 +73,7 @@ FuzzyChoice
FuzzyInteger
------------
-.. class:: FuzzyInteger(low[, high])
+.. class:: FuzzyInteger(low[, high[, step]])
The :class:`FuzzyInteger` fuzzer generates random integers within a given
inclusive range.
@@ -82,7 +82,7 @@ FuzzyInteger
.. code-block:: pycon
- >>> FuzzyInteger(0, 42)
+ >>> fi = FuzzyInteger(0, 42)
>>> fi.low, fi.high
0, 42
@@ -98,12 +98,18 @@ FuzzyInteger
int, the inclusive higher bound of generated integers
+ .. attribute:: step
+
+ int, the step between values in the range; for instance, a ``FuzzyInteger(0, 42, step=3)``
+ might only yield values from ``[0, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42]``.
+
+
FuzzyDecimal
------------
-.. class:: FuzzyDecimal(low[, high])
+.. class:: FuzzyDecimal(low[, high[, precision=2]])
- The :class:`FuzzyDecimal` fuzzer generates random integers within a given
+ The :class:`FuzzyDecimal` fuzzer generates random :class:`decimals <decimal.Decimal>` within a given
inclusive range.
The :attr:`low` bound may be omitted, in which case it defaults to 0:
@@ -134,6 +140,32 @@ FuzzyDecimal
int, the number of digits to generate after the dot. The default is 2 digits.
+FuzzyFloat
+----------
+
+.. class:: FuzzyFloat(low[, high])
+
+ The :class:`FuzzyFloat` fuzzer provides random :class:`float` objects within a given inclusive range.
+
+ .. code-block:: pycon
+
+ >>> FuzzyFloat(0.5, 42.7)
+ >>> fi.low, fi.high
+ 0.5, 42.7
+
+ >>> fi = FuzzyFloat(42.7)
+ >>> fi.low, fi.high
+ 0.0, 42.7
+
+
+ .. attribute:: low
+
+ decimal, the inclusive lower bound of generated floats
+
+ .. attribute:: high
+
+ decimal, the inclusive higher bound of generated floats
+
FuzzyDate
---------
diff --git a/docs/ideas.rst b/docs/ideas.rst
index 914e640..f3c9e62 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
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 e50e706..2aa27b2 100644
--- a/docs/orms.rst
+++ b/docs/orms.rst
@@ -32,7 +32,7 @@ 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
@@ -42,6 +42,18 @@ All factories for a Django :class:`~django.db.models.Model` should use the
.. attribute:: FACTORY_DJANGO_GET_OR_CREATE
+ .. deprecated:: 2.4.0
+ See :attr:`DjangoOptions.django_get_or_create`.
+
+
+.. class:: DjangoOptions(factory.base.FactoryOptions)
+
+ The ``class Meta`` on a :class:`~DjangoModelFactory` supports an extra parameter:
+
+ .. 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>`:
@@ -49,8 +61,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,15 +93,21 @@ 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.
+Extra fields
+""""""""""""
+
+
.. class:: FileField
Custom declarations for :class:`django.db.models.FileField`
@@ -108,7 +127,8 @@ All factories for a Django :class:`~django.db.models.Model` should use the
.. 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')
@@ -145,7 +165,8 @@ All factories for a Django :class:`~django.db.models.Model` should use the
.. code-block:: python
class MyFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.MyModel
+ class Meta:
+ model = models.MyModel
the_image = factory.django.ImageField(color='blue')
@@ -157,6 +178,44 @@ All factories for a Django :class:`~django.db.models.Model` should use the
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
----
@@ -200,7 +259,7 @@ 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.
@@ -214,7 +273,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/
@@ -229,7 +288,18 @@ To work, this class needs an `SQLAlchemy`_ session object affected to "FACTORY_S
.. attribute:: FACTORY_SESSION
- Fields whose SQLAlchemy session object are passed will be used to communicate with the database
+ .. deprecated:: 2.4.0
+ See :attr:`~SQLAlchemyOptions.sqlalchemy_session`.
+
+.. 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`.
A (very) simple exemple:
@@ -256,8 +326,9 @@ A (very) simple exemple:
class UserFactory(SQLAlchemyModelFactory):
- FACTORY_FOR = User
- FACTORY_SESSION = session # the SQLAlchemy session object
+ 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)
diff --git a/docs/recipes.rst b/docs/recipes.rst
index c1f3700..72dacef 100644
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -26,7 +26,8 @@ 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)
@@ -53,7 +54,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)
@@ -66,7 +68,7 @@ Example: Django's Profile
"""""""""""""""""""""""""
Django (<1.5) provided a mechanism to attach a ``Profile`` to a ``User`` instance,
-using a :class:`~django.db.models.ForeignKey` from the ``Profile`` to the ``User``.
+using a :class:`~django.db.models.OneToOneField` from the ``Profile`` to the ``User``.
A typical way to create those profiles was to hook a post-save signal to the ``User`` model.
@@ -75,7 +77,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 +86,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)
@@ -145,12 +149,14 @@ hook:
# 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"
@@ -200,17 +206,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,21 +282,48 @@ 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)
owner = factory.SubFactory(UserFactory, country=factory.SelfAttribute('..country'))
+
+
+Custom manager methods
+----------------------
+
+Sometimes you need a factory to call a specific manager method other then the
+default :meth:`Model.objects.create() <django.db.models.query.QuerySet.create>` method:
+
+.. code-block:: python
+
+ class UserFactory(factory.DjangoModelFactory):
+ class Meta:
+ model = UserenaSignup
+
+ username = "l7d8s"
+ email = "my_name@example.com"
+ password = "my_password"
+
+ @classmethod
+ def _create(cls, model_class, *args, **kwargs):
+ """Override the default ``_create`` with our custom call."""
+ manager = cls._get_manager(model_class)
+ # The default would use ``manager.create(*args, **kwargs)``
+ return manager.create_user(*args, **kwargs)
diff --git a/docs/reference.rst b/docs/reference.rst
index 53584a0..b0dda50 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:
+
+ .. code-block:: python
- It accepts a few specific attributes (must be specified on class declaration):
+ 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.
+
+ .. 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:: FACTORY_ARG_PARAMETERS
+ .. 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,67 @@ 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:: strategy
+
+ Use this attribute to change the strategy used by a :class:`Factory`.
+ The default is :data:`BUILD_STRATEGY`.
+
+
+
+.. class:: Factory
+
+ .. note:: In previous versions, the fields of :class:`class Meta <factory.FactoryOptions>` were
+ defined as class attributes on :class:`Factory`. This is now deprecated and will be removed
+ in 2.5.0.
+
+ .. attribute:: FACTORY_FOR
+
+ .. deprecated:: 2.4.0
+ See :attr:`FactoryOptions.model`.
+
+ .. attribute:: ABSTRACT_FACTORY
+
+ .. deprecated:: 2.4.0
+ See :attr:`FactoryOptions.abstract`.
+
+ .. attribute:: FACTORY_ARG_PARAMETERS
+
+ .. deprecated:: 2.4.0
+ See :attr:`FactoryOptions.inline_args`.
+
+ .. attribute:: FACTORY_HIDDEN_ARGS
+
+ .. deprecated:: 2.4.0
+ See :attr:`FactoryOptions.exclude`.
+
+ .. attribute:: FACTORY_STRATEGY
+
+ .. deprecated:: 2.4.0
+ See :attr:`FactoryOptions.strategy`.
+
+
+ **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 +243,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
@@ -189,19 +270,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 +295,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 +336,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 +375,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 +398,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 +419,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 +441,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`.
@@ -414,7 +496,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 +532,8 @@ return value of the method:
.. code-block:: python
class UserFactory(factory.Factory)
- FACTORY_FOR = User
+ class Meta:
+ model = User
name = u"Jean"
@@ -487,7 +571,8 @@ This declaration takes a single argument, a function accepting a single paramete
.. 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 +597,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 +623,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 +648,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 +684,8 @@ class-level value.
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
uid = factory.Sequence(int)
@@ -631,7 +720,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 +745,8 @@ handles more complex cases:
.. code-block:: python
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
login = 'john'
@@ -692,7 +783,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 +797,8 @@ Definition
# A standard factory
class UserFactory(factory.Factory):
- FACTORY_FOR = User
+ class Meta:
+ model = User
# Various fields
first_name = 'John'
@@ -714,7 +807,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 +888,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 +924,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 +951,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 +987,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 +1066,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 +1088,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 +1132,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 +1169,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,7 +1217,8 @@ 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):
@@ -1128,7 +1233,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 +1247,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 +1289,8 @@ RelatedFactory
.. code-block:: python
class FooFactory(factory.Factory):
- FACTORY_FOR = Foo
+ class Meta:
+ model = Foo
bar = factory.RelatedFactory(BarFactory) # Not BarFactory()
@@ -1192,13 +1298,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")
@@ -1240,7 +1348,7 @@ 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 +1368,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 +1389,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 +1426,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 +1450,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 +1501,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 +1516,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 +1580,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 +1600,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)
diff --git a/factory/__init__.py b/factory/__init__.py
index aa550e8..8fc8ef8 100644
--- a/factory/__init__.py
+++ b/factory/__init__.py
@@ -20,7 +20,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-__version__ = '2.3.1'
+__version__ = '2.4.1'
__author__ = 'Raphaël Barrois <raphael.barrois+fboy@polytechnique.org>'
@@ -32,6 +32,8 @@ from .base import (
ListFactory,
StubFactory,
+ FactoryError,
+
BUILD_STRATEGY,
CREATE_STRATEGY,
STUB_STRATEGY,
diff --git a/factory/alchemy.py b/factory/alchemy.py
index cec15c9..3c91411 100644
--- a/factory/alchemy.py
+++ b/factory/alchemy.py
@@ -24,17 +24,30 @@ from sqlalchemy.sql.functions import max
from . import base
+class SQLAlchemyOptions(base.FactoryOptions):
+ def _build_default_options(self):
+ return super(SQLAlchemyOptions, self)._build_default_options() + [
+ base.OptionDefault('sqlalchemy_session', None, inherit=True),
+ ]
+
+
class SQLAlchemyModelFactory(base.Factory):
"""Factory for SQLAlchemy models. """
- ABSTRACT_FACTORY = True
- FACTORY_SESSION = None
+ _options_class = SQLAlchemyOptions
+ class Meta:
+ abstract = True
+
+ _OLDSTYLE_ATTRIBUTES = base.Factory._OLDSTYLE_ATTRIBUTES.copy()
+ _OLDSTYLE_ATTRIBUTES.update({
+ 'FACTORY_SESSION': 'sqlalchemy_session',
+ })
@classmethod
def _setup_next_sequence(cls, *args, **kwargs):
"""Compute the next available PK, based on the 'pk' database field."""
- session = cls.FACTORY_SESSION
- model = cls.FACTORY_FOR
+ session = cls._meta.sqlalchemy_session
+ model = cls._meta.model
pk = getattr(model, model.__mapper__.primary_key[0].name)
max_pk = session.query(max(pk)).one()[0]
if isinstance(max_pk, int):
@@ -43,9 +56,9 @@ class SQLAlchemyModelFactory(base.Factory):
return 1
@classmethod
- def _create(cls, target_class, *args, **kwargs):
+ def _create(cls, model_class, *args, **kwargs):
"""Create an instance of the model, and save it to the database."""
- session = cls.FACTORY_SESSION
- obj = target_class(*args, **kwargs)
+ session = cls._meta.sqlalchemy_session
+ obj = model_class(*args, **kwargs)
session.add(obj)
return obj
diff --git a/factory/base.py b/factory/base.py
index 3c6571c..9e07899 100644
--- a/factory/base.py
+++ b/factory/base.py
@@ -21,8 +21,10 @@
# THE SOFTWARE.
import logging
+import warnings
from . import containers
+from . import declarations
from . import utils
logger = logging.getLogger('factory.generate')
@@ -33,22 +35,13 @@ CREATE_STRATEGY = 'create'
STUB_STRATEGY = 'stub'
-# Special declarations
-FACTORY_CLASS_DECLARATION = 'FACTORY_FOR'
-
-# Factory class attributes
-CLASS_ATTRIBUTE_DECLARATIONS = '_declarations'
-CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS = '_postgen_declarations'
-CLASS_ATTRIBUTE_ASSOCIATED_CLASS = '_associated_class'
-CLASS_ATTRIBUTE_IS_ABSTRACT = '_abstract_factory'
-
class FactoryError(Exception):
"""Any exception raised by factory_boy."""
class AssociatedClassError(FactoryError):
- """Exception for Factory subclasses lacking FACTORY_FOR."""
+ """Exception for Factory subclasses lacking Meta.model."""
class UnknownStrategy(FactoryError):
@@ -66,6 +59,14 @@ def get_factory_bases(bases):
return [b for b in bases if issubclass(b, BaseFactory)]
+def resolve_attribute(name, bases, default=None):
+ """Find the first definition of an attribute according to MRO order."""
+ for base in bases:
+ if hasattr(base, name):
+ return getattr(base, name)
+ return default
+
+
class FactoryMetaClass(type):
"""Factory metaclass for handling ordered declarations."""
@@ -75,80 +76,15 @@ class FactoryMetaClass(type):
Returns an instance of the associated class.
"""
- if cls.FACTORY_STRATEGY == BUILD_STRATEGY:
+ if cls._meta.strategy == BUILD_STRATEGY:
return cls.build(**kwargs)
- elif cls.FACTORY_STRATEGY == CREATE_STRATEGY:
+ elif cls._meta.strategy == CREATE_STRATEGY:
return cls.create(**kwargs)
- elif cls.FACTORY_STRATEGY == STUB_STRATEGY:
+ elif cls._meta.strategy == STUB_STRATEGY:
return cls.stub(**kwargs)
else:
- raise UnknownStrategy('Unknown FACTORY_STRATEGY: {0}'.format(
- cls.FACTORY_STRATEGY))
-
- @classmethod
- def _discover_associated_class(mcs, class_name, attrs, inherited=None):
- """Try to find the class associated with this factory.
-
- In order, the following tests will be performed:
- - Lookup the FACTORY_CLASS_DECLARATION attribute
- - If an inherited associated class was provided, use it.
-
- Args:
- class_name (str): the name of the factory class being created
- attrs (dict): the dict of attributes from the factory class
- definition
- inherited (class): the optional associated class inherited from a
- parent factory
-
- Returns:
- class: the class to associate with this factory
- """
- if FACTORY_CLASS_DECLARATION in attrs:
- return attrs[FACTORY_CLASS_DECLARATION]
-
- # No specific associated class was given, and one was defined for our
- # parent, use it.
- if inherited is not None:
- return inherited
-
- # Nothing found, return None.
- return None
-
- @classmethod
- def _extract_declarations(mcs, bases, attributes):
- """Extract declarations from a class definition.
-
- Args:
- bases (class list): parent Factory subclasses
- attributes (dict): attributes declared in the class definition
-
- Returns:
- dict: the original attributes, where declarations have been moved to
- _declarations and post-generation declarations to
- _postgen_declarations.
- """
- declarations = containers.DeclarationDict()
- postgen_declarations = containers.PostGenerationDeclarationDict()
-
- # Add parent declarations in reverse order.
- for base in reversed(bases):
- # Import parent PostGenerationDeclaration
- postgen_declarations.update_with_public(
- getattr(base, CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS, {}))
- # Import all 'public' attributes (avoid those starting with _)
- declarations.update_with_public(
- getattr(base, CLASS_ATTRIBUTE_DECLARATIONS, {}))
-
- # Import attributes from the class definition
- attributes = postgen_declarations.update_with_public(attributes)
- # Store protected/private attributes in 'non_factory_attrs'.
- attributes = declarations.update_with_public(attributes)
-
- # Store the DeclarationDict in the attributes of the newly created class
- attributes[CLASS_ATTRIBUTE_DECLARATIONS] = declarations
- attributes[CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS] = postgen_declarations
-
- return attributes
+ raise UnknownStrategy('Unknown Meta.strategy: {0}'.format(
+ cls._meta.strategy))
def __new__(mcs, class_name, bases, attrs):
"""Record attributes as a pattern for later instance construction.
@@ -166,51 +102,183 @@ class FactoryMetaClass(type):
A new class
"""
parent_factories = get_factory_bases(bases)
- if not parent_factories:
- return super(FactoryMetaClass, mcs).__new__(
- mcs, class_name, bases, attrs)
+ if parent_factories:
+ base_factory = parent_factories[0]
+ else:
+ base_factory = None
+
+ attrs_meta = attrs.pop('Meta', None)
+
+ oldstyle_attrs = {}
+ converted_attrs = {}
+ for old_name, new_name in base_factory._OLDSTYLE_ATTRIBUTES.items():
+ if old_name in attrs:
+ oldstyle_attrs[old_name] = new_name
+ converted_attrs[new_name] = attrs.pop(old_name)
+ if oldstyle_attrs:
+ warnings.warn(
+ "Declaring any of %s at class-level is deprecated"
+ " and will be removed in the future. Please set them"
+ " as %s attributes of a 'class Meta' attribute." % (
+ ', '.join(oldstyle_attrs.keys()),
+ ', '.join(oldstyle_attrs.values()),
+ ),
+ PendingDeprecationWarning, 2)
+ attrs_meta = type('Meta', (object,), converted_attrs)
+
+ base_meta = resolve_attribute('_meta', bases)
+ options_class = resolve_attribute('_options_class', bases, FactoryOptions)
+
+ meta = options_class()
+ attrs['_meta'] = meta
+
+ new_class = super(FactoryMetaClass, mcs).__new__(
+ mcs, class_name, bases, attrs)
+
+ meta.contribute_to_class(new_class,
+ meta=attrs_meta,
+ base_meta=base_meta,
+ base_factory=base_factory,
+ )
- extra_attrs = {}
+ return new_class
- is_abstract = attrs.pop('ABSTRACT_FACTORY', False)
+ def __str__(cls):
+ if cls._meta.abstract:
+ return '<%s (abstract)>' % cls.__name__
+ else:
+ return '<%s for %s>' % (cls.__name__, cls._meta.model)
- base = parent_factories[0]
- inherited_associated_class = base._get_target_class()
- associated_class = mcs._discover_associated_class(class_name, attrs,
- inherited_associated_class)
- # Invoke 'lazy-loading' hooks.
- associated_class = base._load_target_class(associated_class)
+class BaseMeta:
+ abstract = True
+ strategy = CREATE_STRATEGY
- if associated_class is None:
- is_abstract = True
- else:
- # If inheriting the factory from a parent, keep a link to it.
- # This allows to use the sequence counters from the parents.
- if (inherited_associated_class is not None
- and issubclass(associated_class, inherited_associated_class)):
- attrs['_base_factory'] = base
+class OptionDefault(object):
+ def __init__(self, name, value, inherit=False):
+ self.name = name
+ self.value = value
+ self.inherit = inherit
- # The CLASS_ATTRIBUTE_ASSOCIATED_CLASS must *not* be taken into
- # account when parsing the declared attributes of the new class.
- extra_attrs[CLASS_ATTRIBUTE_ASSOCIATED_CLASS] = associated_class
+ def apply(self, meta, base_meta):
+ value = self.value
+ if self.inherit and base_meta is not None:
+ value = getattr(base_meta, self.name, value)
+ if meta is not None:
+ value = getattr(meta, self.name, value)
+ return value
- extra_attrs[CLASS_ATTRIBUTE_IS_ABSTRACT] = is_abstract
+ def __str__(self):
+ return '%s(%r, %r, inherit=%r)' % (
+ self.__class__.__name__,
+ self.name, self.value, self.inherit)
- # Extract pre- and post-generation declarations
- attributes = mcs._extract_declarations(parent_factories, attrs)
- attributes.update(extra_attrs)
- return super(FactoryMetaClass, mcs).__new__(
- mcs, class_name, bases, attributes)
+class FactoryOptions(object):
+ def __init__(self):
+ self.factory = None
+ self.base_factory = None
+ self.declarations = {}
+ self.postgen_declarations = {}
- def __str__(cls):
- if cls._abstract_factory:
- return '<%s (abstract)>'
+ def _build_default_options(self):
+ """"Provide the default value for all allowed fields.
+
+ Custom FactoryOptions classes should override this method
+ to update() its return value.
+ """
+ return [
+ OptionDefault('model', None, inherit=True),
+ OptionDefault('abstract', False, inherit=False),
+ OptionDefault('strategy', CREATE_STRATEGY, inherit=True),
+ OptionDefault('inline_args', (), inherit=True),
+ OptionDefault('exclude', (), inherit=True),
+ ]
+
+ def _fill_from_meta(self, meta, base_meta):
+ # Exclude private/protected fields from the meta
+ if meta is None:
+ meta_attrs = {}
+ else:
+ meta_attrs = dict((k, v)
+ for (k, v) in vars(meta).items()
+ if not k.startswith('_')
+ )
+
+ for option in self._build_default_options():
+ assert not hasattr(self, option.name), "Can't override field %s." % option.name
+ value = option.apply(meta, base_meta)
+ meta_attrs.pop(option.name, None)
+ setattr(self, option.name, value)
+
+ if meta_attrs:
+ # Some attributes in the Meta aren't allowed here
+ raise TypeError("'class Meta' for %r got unknown attribute(s) %s"
+ % (self.factory, ','.join(sorted(meta_attrs.keys()))))
+
+ def contribute_to_class(self, factory,
+ meta=None, base_meta=None, base_factory=None):
+
+ self.factory = factory
+ self.base_factory = base_factory
+
+ self._fill_from_meta(meta=meta, base_meta=base_meta)
+
+ self.model = self.factory._load_model_class(self.model)
+ if self.model is None:
+ self.abstract = True
+
+ self.counter_reference = self._get_counter_reference()
+
+ for parent in reversed(self.factory.__mro__[1:]):
+ if not hasattr(parent, '_meta'):
+ continue
+ self.declarations.update(parent._meta.declarations)
+ self.postgen_declarations.update(parent._meta.postgen_declarations)
+
+ for k, v in vars(self.factory).items():
+ if self._is_declaration(k, v):
+ self.declarations[k] = v
+ if self._is_postgen_declaration(k, v):
+ self.postgen_declarations[k] = v
+
+ def _get_counter_reference(self):
+ """Identify which factory should be used for a shared counter."""
+
+ if (self.model is not None
+ and self.base_factory is not None
+ and self.base_factory._meta.model is not None
+ and issubclass(self.model, self.base_factory._meta.model)):
+ return self.base_factory
else:
- return '<%s for %s>' % (cls.__name__,
- getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS).__name__)
+ return self.factory
+
+ def _is_declaration(self, name, value):
+ """Determines if a class attribute is a field value declaration.
+
+ Based on the name and value of the class attribute, return ``True`` if
+ it looks like a declaration of a default field value, ``False`` if it
+ is private (name starts with '_') or a classmethod or staticmethod.
+
+ """
+ if isinstance(value, (classmethod, staticmethod)):
+ return False
+ elif isinstance(value, declarations.OrderedDeclaration):
+ return True
+ elif isinstance(value, declarations.PostGenerationDeclaration):
+ return False
+ return not name.startswith("_")
+
+ def _is_postgen_declaration(self, name, value):
+ """Captures instances of PostGenerationDeclaration."""
+ return isinstance(value, declarations.PostGenerationDeclaration)
+
+ def __str__(self):
+ return "<%s for %s>" % (self.__class__.__name__, self.factory.__class__.__name__)
+
+ def __repr__(self):
+ return str(self)
# Factory base classes
@@ -252,25 +320,18 @@ class BaseFactory(object):
"""Would be called if trying to instantiate the class."""
raise FactoryError('You cannot instantiate BaseFactory')
- # ID to use for the next 'declarations.Sequence' attribute.
- _counter = None
-
- # Base factory, if this class was inherited from another factory. This is
- # used for sharing the sequence _counter among factories for the same
- # class.
- _base_factory = None
-
- # Holds the target class, once resolved.
- _associated_class = None
+ _meta = FactoryOptions()
- # Whether this factory is considered "abstract", thus uncallable.
- _abstract_factory = False
+ _OLDSTYLE_ATTRIBUTES = {
+ 'FACTORY_FOR': 'model',
+ 'ABSTRACT_FACTORY': 'abstract',
+ 'FACTORY_STRATEGY': 'strategy',
+ 'FACTORY_ARG_PARAMETERS': 'inline_args',
+ 'FACTORY_HIDDEN_ARGS': 'exclude',
+ }
- # List of arguments that should be passed as *args instead of **kwargs
- FACTORY_ARG_PARAMETERS = ()
-
- # List of attributes that should not be passed to the underlying class
- FACTORY_HIDDEN_ARGS = ()
+ # ID to use for the next 'declarations.Sequence' attribute.
+ _counter = None
@classmethod
def reset_sequence(cls, value=None, force=False):
@@ -282,9 +343,9 @@ class BaseFactory(object):
force (bool): whether to force-reset parent sequence counters
in a factory inheritance chain.
"""
- if cls._base_factory:
+ if cls._meta.counter_reference is not cls:
if force:
- cls._base_factory.reset_sequence(value=value)
+ cls._meta.base_factory.reset_sequence(value=value)
else:
raise ValueError(
"Cannot reset the sequence of a factory subclass. "
@@ -330,9 +391,9 @@ class BaseFactory(object):
"""
# Rely upon our parents
- if cls._base_factory and not cls._base_factory._abstract_factory:
- logger.debug("%r: reusing sequence from %r", cls, cls._base_factory)
- return cls._base_factory._generate_next_sequence()
+ if cls._meta.counter_reference is not cls:
+ logger.debug("%r: reusing sequence from %r", cls, cls._meta.base_factory)
+ return cls._meta.base_factory._generate_next_sequence()
# Make sure _counter is initialized
cls._setup_counter()
@@ -373,7 +434,9 @@ class BaseFactory(object):
extra_defs (dict): additional definitions to insert into the
retrieved DeclarationDict.
"""
- return getattr(cls, CLASS_ATTRIBUTE_DECLARATIONS).copy(extra_defs)
+ decls = cls._meta.declarations.copy()
+ decls.update(extra_defs)
+ return decls
@classmethod
def _adjust_kwargs(cls, **kwargs):
@@ -381,8 +444,8 @@ class BaseFactory(object):
return kwargs
@classmethod
- def _load_target_class(cls, class_definition):
- """Extension point for loading target classes.
+ def _load_model_class(cls, class_definition):
+ """Extension point for loading model classes.
This can be overridden in framework-specific subclasses to hook into
existing model repositories, for instance.
@@ -390,10 +453,10 @@ class BaseFactory(object):
return class_definition
@classmethod
- def _get_target_class(cls):
- """Retrieve the actual, associated target class."""
- definition = getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS, None)
- return cls._load_target_class(definition)
+ def _get_model_class(cls):
+ """Retrieve the actual, associated model class."""
+ definition = cls._meta.model
+ return cls._load_model_class(definition)
@classmethod
def _prepare(cls, create, **kwargs):
@@ -403,15 +466,15 @@ class BaseFactory(object):
create: bool, whether to create or to build the object
**kwargs: arguments to pass to the creation function
"""
- target_class = cls._get_target_class()
+ model_class = cls._get_model_class()
kwargs = cls._adjust_kwargs(**kwargs)
# Remove 'hidden' arguments.
- for arg in cls.FACTORY_HIDDEN_ARGS:
+ for arg in cls._meta.exclude:
del kwargs[arg]
# Extract *args from **kwargs
- args = tuple(kwargs.pop(key) for key in cls.FACTORY_ARG_PARAMETERS)
+ args = tuple(kwargs.pop(key) for key in cls._meta.inline_args)
logger.debug('BaseFactory: Generating %s.%s(%s)',
cls.__module__,
@@ -419,9 +482,9 @@ class BaseFactory(object):
utils.log_pprint(args, kwargs),
)
if create:
- return cls._create(target_class, *args, **kwargs)
+ return cls._create(model_class, *args, **kwargs)
else:
- return cls._build(target_class, *args, **kwargs)
+ return cls._build(model_class, *args, **kwargs)
@classmethod
def _generate(cls, create, attrs):
@@ -431,15 +494,14 @@ class BaseFactory(object):
create (bool): whether to 'build' or 'create' the object
attrs (dict): attributes to use for generating the object
"""
- if cls._abstract_factory:
+ if cls._meta.abstract:
raise FactoryError(
"Cannot generate instances of abstract factory %(f)s; "
- "Ensure %(f)s.FACTORY_FOR is set and %(f)s.ABSTRACT_FACTORY "
- "is either not set or False." % dict(f=cls))
+ "Ensure %(f)s.Meta.model is set and %(f)s.Meta.abstract "
+ "is either not set or False." % dict(f=cls.__name__))
# Extract declarations used for post-generation
- postgen_declarations = getattr(cls,
- CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS)
+ postgen_declarations = cls._meta.postgen_declarations
postgen_attributes = {}
for name, decl in sorted(postgen_declarations.items()):
postgen_attributes[name] = decl.extract(name, attrs)
@@ -469,34 +531,34 @@ class BaseFactory(object):
pass
@classmethod
- def _build(cls, target_class, *args, **kwargs):
- """Actually build an instance of the target_class.
+ def _build(cls, model_class, *args, **kwargs):
+ """Actually build an instance of the model_class.
Customization point, will be called once the full set of args and kwargs
has been computed.
Args:
- target_class (type): the class for which an instance should be
+ model_class (type): the class for which an instance should be
built
args (tuple): arguments to use when building the class
kwargs (dict): keyword arguments to use when building the class
"""
- return target_class(*args, **kwargs)
+ return model_class(*args, **kwargs)
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- """Actually create an instance of the target_class.
+ def _create(cls, model_class, *args, **kwargs):
+ """Actually create an instance of the model_class.
Customization point, will be called once the full set of args and kwargs
has been computed.
Args:
- target_class (type): the class for which an instance should be
+ model_class (type): the class for which an instance should be
created
args (tuple): arguments to use when creating the class
kwargs (dict): keyword arguments to use when creating the class
"""
- return target_class(*args, **kwargs)
+ return model_class(*args, **kwargs)
@classmethod
def build(cls, **kwargs):
@@ -626,8 +688,7 @@ class BaseFactory(object):
Factory = FactoryMetaClass('Factory', (BaseFactory,), {
- 'ABSTRACT_FACTORY': True,
- 'FACTORY_STRATEGY': CREATE_STRATEGY,
+ 'Meta': BaseMeta,
'__doc__': """Factory base with build and create support.
This class has the ability to support multiple ORMs by using custom creation
@@ -642,8 +703,9 @@ Factory.AssociatedClassError = AssociatedClassError # pylint: disable=W0201
class StubFactory(Factory):
- FACTORY_STRATEGY = STUB_STRATEGY
- FACTORY_FOR = containers.StubObject
+ class Meta:
+ strategy = STUB_STRATEGY
+ model = containers.StubObject
@classmethod
def build(cls, **kwargs):
@@ -656,44 +718,48 @@ class StubFactory(Factory):
class BaseDictFactory(Factory):
"""Factory for dictionary-like classes."""
- ABSTRACT_FACTORY = True
+ class Meta:
+ abstract = True
@classmethod
- def _build(cls, target_class, *args, **kwargs):
+ def _build(cls, model_class, *args, **kwargs):
if args:
raise ValueError(
- "DictFactory %r does not support FACTORY_ARG_PARAMETERS.", cls)
- return target_class(**kwargs)
+ "DictFactory %r does not support Meta.inline_args.", cls)
+ return model_class(**kwargs)
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- return cls._build(target_class, *args, **kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ return cls._build(model_class, *args, **kwargs)
class DictFactory(BaseDictFactory):
- FACTORY_FOR = dict
+ class Meta:
+ model = dict
class BaseListFactory(Factory):
"""Factory for list-like classes."""
- ABSTRACT_FACTORY = True
+ class Meta:
+ abstract = True
@classmethod
- def _build(cls, target_class, *args, **kwargs):
+ def _build(cls, model_class, *args, **kwargs):
if args:
raise ValueError(
- "ListFactory %r does not support FACTORY_ARG_PARAMETERS.", cls)
+ "ListFactory %r does not support Meta.inline_args.", cls)
values = [v for k, v in sorted(kwargs.items())]
- return target_class(values)
+ return model_class(values)
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- return cls._build(target_class, *args, **kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ return cls._build(model_class, *args, **kwargs)
class ListFactory(BaseListFactory):
- FACTORY_FOR = list
+ class Meta:
+ model = list
def use_strategy(new_strategy):
@@ -702,6 +768,6 @@ def use_strategy(new_strategy):
This is an alternative to setting default_strategy in the class definition.
"""
def wrapped_class(klass):
- klass.FACTORY_STRATEGY = new_strategy
+ klass._meta.strategy = new_strategy
return klass
return wrapped_class
diff --git a/factory/containers.py b/factory/containers.py
index 4537e44..5116320 100644
--- a/factory/containers.py
+++ b/factory/containers.py
@@ -47,27 +47,27 @@ class LazyStub(object):
__containers (LazyStub list): "parents" of the LazyStub being built.
This allows to have the field of a field depend on the value of
another field
- __target_class (type): the target class to build.
+ __model_class (type): the model class to build.
"""
__initialized = False
- def __init__(self, attrs, containers=(), target_class=object, log_ctx=None):
+ def __init__(self, attrs, containers=(), model_class=object, log_ctx=None):
self.__attrs = attrs
self.__values = {}
self.__pending = []
self.__containers = containers
- self.__target_class = target_class
- self.__log_ctx = log_ctx or '%s.%s' % (target_class.__module__, target_class.__name__)
+ self.__model_class = model_class
+ self.__log_ctx = log_ctx or '%s.%s' % (model_class.__module__, model_class.__name__)
self.factory_parent = containers[0] if containers else None
self.__initialized = True
def __repr__(self):
- return '<LazyStub for %s.%s>' % (self.__target_class.__module__, self.__target_class.__name__)
+ return '<LazyStub for %s.%s>' % (self.__model_class.__module__, self.__model_class.__name__)
def __str__(self):
return '<LazyStub for %s with %s>' % (
- self.__target_class.__name__, list(self.__attrs.keys()))
+ self.__model_class.__name__, list(self.__attrs.keys()))
def __fill__(self):
"""Fill this LazyStub, computing values of all defined attributes.
@@ -121,61 +121,6 @@ class LazyStub(object):
raise AttributeError('Setting of object attributes is not allowed')
-class DeclarationDict(dict):
- """Slightly extended dict to work with OrderedDeclaration."""
-
- def is_declaration(self, name, value):
- """Determines if a class attribute is a field value declaration.
-
- Based on the name and value of the class attribute, return ``True`` if
- it looks like a declaration of a default field value, ``False`` if it
- is private (name starts with '_') or a classmethod or staticmethod.
-
- """
- if isinstance(value, (classmethod, staticmethod)):
- return False
- elif isinstance(value, declarations.OrderedDeclaration):
- return True
- return (not name.startswith("_") and not name.startswith("FACTORY_"))
-
- def update_with_public(self, d):
- """Updates the DeclarationDict from a class definition dict.
-
- Takes into account all public attributes and OrderedDeclaration
- instances; ignores all class/staticmethods and private attributes
- (starting with '_').
-
- Returns a dict containing all remaining elements.
- """
- remaining = {}
- for k, v in d.items():
- if self.is_declaration(k, v):
- self[k] = v
- else:
- remaining[k] = v
- return remaining
-
- def copy(self, extra=None):
- """Copy this DeclarationDict into another one, including extra values.
-
- Args:
- extra (dict): additional attributes to include in the copy.
- """
- new = self.__class__()
- new.update(self)
- if extra:
- new.update(extra)
- return new
-
-
-class PostGenerationDeclarationDict(DeclarationDict):
- """Alternate DeclarationDict for PostGenerationDeclaration."""
-
- def is_declaration(self, name, value):
- """Captures instances of PostGenerationDeclaration."""
- return isinstance(value, declarations.PostGenerationDeclaration)
-
-
class LazyValue(object):
"""Some kind of "lazy evaluating" object."""
@@ -279,7 +224,7 @@ class AttributeBuilder(object):
wrapped_attrs[k] = v
stub = LazyStub(wrapped_attrs, containers=self._containers,
- target_class=self.factory, log_ctx=self._log_ctx)
+ model_class=self.factory, log_ctx=self._log_ctx)
return stub.__fill__()
diff --git a/factory/declarations.py b/factory/declarations.py
index 037a679..5e7e734 100644
--- a/factory/declarations.py
+++ b/factory/declarations.py
@@ -50,7 +50,7 @@ class OrderedDeclaration(object):
attributes
containers (list of containers.LazyStub): The chain of SubFactory
which led to building this object.
- create (bool): whether the target class should be 'built' or
+ create (bool): whether the model class should be 'built' or
'created'
extra (DeclarationDict or None): extracted key/value extracted from
the attribute prefix
@@ -434,7 +434,7 @@ class ExtractionContext(object):
class PostGenerationDeclaration(object):
- """Declarations to be called once the target object has been generated."""
+ """Declarations to be called once the model object has been generated."""
def extract(self, name, attrs):
"""Extract relevant attributes from a dict.
diff --git a/factory/django.py b/factory/django.py
index fee8e52..2b6c463 100644
--- a/factory/django.py
+++ b/factory/django.py
@@ -25,6 +25,9 @@ from __future__ import absolute_import
from __future__ import unicode_literals
import os
+import types
+import logging
+import functools
"""factory_boy extensions for use with the Django framework."""
@@ -39,6 +42,9 @@ from . import base
from . import declarations
from .compat import BytesIO, is_string
+logger = logging.getLogger('factory.generate')
+
+
def require_django():
"""Simple helper to ensure Django is available."""
@@ -46,6 +52,25 @@ def require_django():
raise import_failure
+class DjangoOptions(base.FactoryOptions):
+ def _build_default_options(self):
+ return super(DjangoOptions, self)._build_default_options() + [
+ base.OptionDefault('django_get_or_create', (), inherit=True),
+ ]
+
+ def _get_counter_reference(self):
+ counter_reference = super(DjangoOptions, self)._get_counter_reference()
+ if (counter_reference == self.base_factory
+ and self.base_factory._meta.model is not None
+ and self.base_factory._meta.model._meta.abstract
+ and self.model is not None
+ and not self.model._meta.abstract):
+ # Target factory is for an abstract model, yet we're for another,
+ # concrete subclass => don't reuse the counter.
+ return self.factory
+ return counter_reference
+
+
class DjangoModelFactory(base.Factory):
"""Factory for Django models.
@@ -55,11 +80,17 @@ class DjangoModelFactory(base.Factory):
handle those for non-numerical primary keys.
"""
- ABSTRACT_FACTORY = True # Optional, but explicit.
- FACTORY_DJANGO_GET_OR_CREATE = ()
+ _options_class = DjangoOptions
+ class Meta:
+ abstract = True # Optional, but explicit.
+
+ _OLDSTYLE_ATTRIBUTES = base.Factory._OLDSTYLE_ATTRIBUTES.copy()
+ _OLDSTYLE_ATTRIBUTES.update({
+ 'FACTORY_DJANGO_GET_OR_CREATE': 'django_get_or_create',
+ })
@classmethod
- def _load_target_class(cls, definition):
+ def _load_model_class(cls, definition):
if is_string(definition) and '.' in definition:
app, model = definition.split('.', 1)
@@ -69,17 +100,20 @@ class DjangoModelFactory(base.Factory):
return definition
@classmethod
- def _get_manager(cls, target_class):
+ def _get_manager(cls, model_class):
+ if model_class is None:
+ raise base.AssociatedClassError("No model set on %s.%s.Meta"
+ % (cls.__module__, cls.__name__))
try:
- return target_class._default_manager # pylint: disable=W0212
+ return model_class._default_manager # pylint: disable=W0212
except AttributeError:
- return target_class.objects
+ return model_class.objects
@classmethod
def _setup_next_sequence(cls):
"""Compute the next available PK, based on the 'pk' database field."""
- model = cls._get_target_class() # pylint: disable=E1101
+ model = cls._get_model_class() # pylint: disable=E1101
manager = cls._get_manager(model)
try:
@@ -91,17 +125,17 @@ class DjangoModelFactory(base.Factory):
return 1
@classmethod
- def _get_or_create(cls, target_class, *args, **kwargs):
+ def _get_or_create(cls, model_class, *args, **kwargs):
"""Create an instance of the model through objects.get_or_create."""
- manager = cls._get_manager(target_class)
+ manager = cls._get_manager(model_class)
- assert 'defaults' not in cls.FACTORY_DJANGO_GET_OR_CREATE, (
+ assert 'defaults' not in cls._meta.django_get_or_create, (
"'defaults' is a reserved keyword for get_or_create "
- "(in %s.FACTORY_DJANGO_GET_OR_CREATE=%r)"
- % (cls, cls.FACTORY_DJANGO_GET_OR_CREATE))
+ "(in %s._meta.django_get_or_create=%r)"
+ % (cls, cls._meta.django_get_or_create))
key_fields = {}
- for field in cls.FACTORY_DJANGO_GET_OR_CREATE:
+ for field in cls._meta.django_get_or_create:
key_fields[field] = kwargs.pop(field)
key_fields['defaults'] = kwargs
@@ -109,12 +143,12 @@ class DjangoModelFactory(base.Factory):
return obj
@classmethod
- def _create(cls, target_class, *args, **kwargs):
+ def _create(cls, model_class, *args, **kwargs):
"""Create an instance of the model, and save it to the database."""
- manager = cls._get_manager(target_class)
+ manager = cls._get_manager(model_class)
- if cls.FACTORY_DJANGO_GET_OR_CREATE:
- return cls._get_or_create(target_class, *args, **kwargs)
+ if cls._meta.django_get_or_create:
+ return cls._get_or_create(model_class, *args, **kwargs)
return manager.create(*args, **kwargs)
@@ -214,3 +248,65 @@ class ImageField(FileField):
thumb.save(thumb_io, format=image_format)
return thumb_io.getvalue()
+
+class mute_signals(object):
+ """Temporarily disables and then restores any django signals.
+
+ Args:
+ *signals (django.dispatch.dispatcher.Signal): any django signals
+
+ Examples:
+ with mute_signals(pre_init):
+ user = UserFactory.build()
+ ...
+
+ @mute_signals(pre_save, post_save)
+ class UserFactory(factory.Factory):
+ ...
+
+ @mute_signals(post_save)
+ def generate_users():
+ UserFactory.create_batch(10)
+ """
+
+ def __init__(self, *signals):
+ self.signals = signals
+ self.paused = {}
+
+ def __enter__(self):
+ for signal in self.signals:
+ logger.debug('mute_signals: Disabling signal handlers %r',
+ signal.receivers)
+
+ self.paused[signal] = signal.receivers
+ signal.receivers = []
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ for signal, receivers in self.paused.items():
+ logger.debug('mute_signals: Restoring signal handlers %r',
+ receivers)
+
+ signal.receivers = receivers
+ self.paused = {}
+
+ def __call__(self, callable_obj):
+ if isinstance(callable_obj, base.FactoryMetaClass):
+ # Retrieve __func__, the *actual* callable object.
+ generate_method = callable_obj._generate.__func__
+
+ @classmethod
+ @functools.wraps(generate_method)
+ def wrapped_generate(*args, **kwargs):
+ with self:
+ return generate_method(*args, **kwargs)
+
+ callable_obj._generate = wrapped_generate
+ return callable_obj
+
+ else:
+ @functools.wraps(callable_obj)
+ def wrapper(*args, **kwargs):
+ with self:
+ return callable_obj(*args, **kwargs)
+ return wrapper
+
diff --git a/factory/fuzzy.py b/factory/fuzzy.py
index 34949c5..94599b7 100644
--- a/factory/fuzzy.py
+++ b/factory/fuzzy.py
@@ -107,18 +107,19 @@ class FuzzyChoice(BaseFuzzyAttribute):
class FuzzyInteger(BaseFuzzyAttribute):
"""Random integer within a given range."""
- def __init__(self, low, high=None, **kwargs):
+ def __init__(self, low, high=None, step=1, **kwargs):
if high is None:
high = low
low = 0
self.low = low
self.high = high
+ self.step = step
super(FuzzyInteger, self).__init__(**kwargs)
def fuzz(self):
- return random.randint(self.low, self.high)
+ return random.randrange(self.low, self.high + 1, self.step)
class FuzzyDecimal(BaseFuzzyAttribute):
@@ -140,6 +141,23 @@ class FuzzyDecimal(BaseFuzzyAttribute):
return base.quantize(decimal.Decimal(10) ** -self.precision)
+class FuzzyFloat(BaseFuzzyAttribute):
+ """Random float within a given range."""
+
+ def __init__(self, low, high=None, **kwargs):
+ if high is None:
+ high = low
+ low = 0
+
+ self.low = low
+ self.high = high
+
+ super(FuzzyFloat, self).__init__(**kwargs)
+
+ def fuzz(self):
+ return random.uniform(self.low, self.high)
+
+
class FuzzyDate(BaseFuzzyAttribute):
"""Random date within a given date range."""
diff --git a/factory/helpers.py b/factory/helpers.py
index 37b41bf..19431df 100644
--- a/factory/helpers.py
+++ b/factory/helpers.py
@@ -28,6 +28,7 @@ import logging
from . import base
from . import declarations
+from . import django
@contextlib.contextmanager
@@ -49,7 +50,9 @@ def debug(logger='factory', stream=None):
def make_factory(klass, **kwargs):
"""Create a new, simple factory for the given class."""
factory_name = '%sFactory' % klass.__name__
- kwargs[base.FACTORY_CLASS_DECLARATION] = klass
+ class Meta:
+ model = klass
+ kwargs['Meta'] = Meta
base_class = kwargs.pop('FACTORY_CLASS', base.Factory)
factory_class = type(base.Factory).__new__(
diff --git a/factory/mogo.py b/factory/mogo.py
index 48d9677..5541043 100644
--- a/factory/mogo.py
+++ b/factory/mogo.py
@@ -32,14 +32,15 @@ from . import base
class MogoFactory(base.Factory):
"""Factory for mogo objects."""
- ABSTRACT_FACTORY = True
+ class Meta:
+ abstract = True
@classmethod
- def _build(cls, target_class, *args, **kwargs):
- return target_class.new(*args, **kwargs)
+ def _build(cls, model_class, *args, **kwargs):
+ return model_class.new(*args, **kwargs)
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- instance = target_class.new(*args, **kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ instance = model_class.new(*args, **kwargs)
instance.save()
return instance
diff --git a/factory/mongoengine.py b/factory/mongoengine.py
index 462f5f2..e3ab99c 100644
--- a/factory/mongoengine.py
+++ b/factory/mongoengine.py
@@ -32,15 +32,17 @@ from . import base
class MongoEngineFactory(base.Factory):
"""Factory for mongoengine objects."""
- ABSTRACT_FACTORY = True
+
+ class Meta:
+ abstract = True
@classmethod
- def _build(cls, target_class, *args, **kwargs):
- return target_class(*args, **kwargs)
+ def _build(cls, model_class, *args, **kwargs):
+ return model_class(*args, **kwargs)
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- instance = target_class(*args, **kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ instance = model_class(*args, **kwargs)
if instance._is_document:
instance.save()
return instance
diff --git a/setup.py b/setup.py
index 54e4caa..f637a48 100755
--- a/setup.py
+++ b/setup.py
@@ -48,7 +48,7 @@ setup(
'setuptools>=0.8',
],
tests_require=[
- 'mock',
+ #'mock',
],
classifiers=[
"Development Status :: 5 - Production/Stable",
diff --git a/tests/__init__.py b/tests/__init__.py
index 5b6fc55..855beea 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -4,6 +4,7 @@
from .test_base import *
from .test_containers import *
from .test_declarations import *
+from .test_deprecation import *
from .test_django import *
from .test_fuzzy import *
from .test_helpers import *
diff --git a/tests/alter_time.py b/tests/alter_time.py
index db0a611..aa2db3b 100644
--- a/tests/alter_time.py
+++ b/tests/alter_time.py
@@ -7,7 +7,7 @@
from __future__ import print_function
import datetime
-import mock
+from .compat import mock
real_datetime_class = datetime.datetime
diff --git a/tests/cyclic/bar.py b/tests/cyclic/bar.py
index fed0602..a5e6bf1 100644
--- a/tests/cyclic/bar.py
+++ b/tests/cyclic/bar.py
@@ -30,7 +30,8 @@ class Bar(object):
class BarFactory(factory.Factory):
- FACTORY_FOR = Bar
+ class Meta:
+ model = Bar
y = 13
foo = factory.SubFactory('cyclic.foo.FooFactory')
diff --git a/tests/cyclic/foo.py b/tests/cyclic/foo.py
index e584ed1..18de362 100644
--- a/tests/cyclic/foo.py
+++ b/tests/cyclic/foo.py
@@ -32,7 +32,8 @@ class Foo(object):
class FooFactory(factory.Factory):
- FACTORY_FOR = Foo
+ class Meta:
+ model = Foo
x = 42
bar = factory.SubFactory(bar_mod.BarFactory)
diff --git a/tests/djapp/models.py b/tests/djapp/models.py
index e98279d..9b21181 100644
--- a/tests/djapp/models.py
+++ b/tests/djapp/models.py
@@ -55,6 +55,15 @@ class ConcreteSon(AbstractBase):
pass
+class AbstractSon(AbstractBase):
+ class Meta:
+ abstract = True
+
+
+class ConcreteGrandSon(AbstractSon):
+ pass
+
+
class StandardSon(StandardModel):
pass
@@ -74,3 +83,7 @@ if Image is not None: # PIL is available
else:
class WithImage(models.Model):
pass
+
+
+class WithSignals(models.Model):
+ foo = models.CharField(max_length=20)
diff --git a/tests/test_alchemy.py b/tests/test_alchemy.py
index 4255417..b9222eb 100644
--- a/tests/test_alchemy.py
+++ b/tests/test_alchemy.py
@@ -36,7 +36,8 @@ if sqlalchemy:
else:
class Fake(object):
- FACTORY_SESSION = None
+ class Meta:
+ sqlalchemy_session = None
models = Fake()
models.StandardModel = Fake()
@@ -46,16 +47,18 @@ else:
class StandardFactory(SQLAlchemyModelFactory):
- FACTORY_FOR = models.StandardModel
- FACTORY_SESSION = models.session
+ class Meta:
+ model = models.StandardModel
+ sqlalchemy_session = models.session
id = factory.Sequence(lambda n: n)
foo = factory.Sequence(lambda n: 'foo%d' % n)
class NonIntegerPkFactory(SQLAlchemyModelFactory):
- FACTORY_FOR = models.NonIntegerPk
- FACTORY_SESSION = models.session
+ class Meta:
+ model = models.NonIntegerPk
+ sqlalchemy_session = models.session
id = factory.Sequence(lambda n: 'foo%d' % n)
@@ -66,7 +69,7 @@ class SQLAlchemyPkSequenceTestCase(unittest.TestCase):
def setUp(self):
super(SQLAlchemyPkSequenceTestCase, self).setUp()
StandardFactory.reset_sequence(1)
- NonIntegerPkFactory.FACTORY_SESSION.rollback()
+ NonIntegerPkFactory._meta.sqlalchemy_session.rollback()
def test_pk_first(self):
std = StandardFactory.build()
@@ -104,7 +107,7 @@ class SQLAlchemyNonIntegerPkTestCase(unittest.TestCase):
def setUp(self):
super(SQLAlchemyNonIntegerPkTestCase, self).setUp()
NonIntegerPkFactory.reset_sequence()
- NonIntegerPkFactory.FACTORY_SESSION.rollback()
+ NonIntegerPkFactory._meta.sqlalchemy_session.rollback()
def test_first(self):
nonint = NonIntegerPkFactory.build()
diff --git a/tests/test_base.py b/tests/test_base.py
index 8cea6fc..d1df58e 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -49,11 +49,12 @@ class FakeDjangoModel(object):
class FakeModelFactory(base.Factory):
- ABSTRACT_FACTORY = True
+ class Meta:
+ abstract = True
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- return target_class.create(**kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ return model_class.create(**kwargs)
class TestModel(FakeDjangoModel):
@@ -67,18 +68,21 @@ class SafetyTestCase(unittest.TestCase):
class AbstractFactoryTestCase(unittest.TestCase):
def test_factory_for_optional(self):
- """Ensure that FACTORY_FOR is optional for ABSTRACT_FACTORY."""
+ """Ensure that model= is optional for abstract=True."""
class TestObjectFactory(base.Factory):
- ABSTRACT_FACTORY = True
+ class Meta:
+ abstract = True
- # Passed
+ self.assertTrue(TestObjectFactory._meta.abstract)
+ self.assertIsNone(TestObjectFactory._meta.model)
def test_factory_for_and_abstract_factory_optional(self):
- """Ensure that ABSTRACT_FACTORY is optional."""
+ """Ensure that Meta.abstract is optional."""
class TestObjectFactory(base.Factory):
pass
- # passed
+ self.assertTrue(TestObjectFactory._meta.abstract)
+ self.assertIsNone(TestObjectFactory._meta.model)
def test_abstract_factory_cannot_be_called(self):
class TestObjectFactory(base.Factory):
@@ -87,26 +91,155 @@ class AbstractFactoryTestCase(unittest.TestCase):
self.assertRaises(base.FactoryError, TestObjectFactory.build)
self.assertRaises(base.FactoryError, TestObjectFactory.create)
+ def test_abstract_factory_not_inherited(self):
+ """abstract=True isn't propagated to child classes."""
+
+ class TestObjectFactory(base.Factory):
+ class Meta:
+ abstract = True
+ model = TestObject
+
+ class TestObjectChildFactory(TestObjectFactory):
+ pass
+
+ self.assertFalse(TestObjectChildFactory._meta.abstract)
+
+ def test_abstract_or_model_is_required(self):
+ class TestObjectFactory(base.Factory):
+ class Meta:
+ abstract = False
+ model = None
+
+ self.assertRaises(base.FactoryError, TestObjectFactory.build)
+ self.assertRaises(base.FactoryError, TestObjectFactory.create)
+
+
+class OptionsTests(unittest.TestCase):
+ def test_base_attrs(self):
+ class AbstractFactory(base.Factory):
+ pass
+
+ # Declarative attributes
+ self.assertTrue(AbstractFactory._meta.abstract)
+ self.assertIsNone(AbstractFactory._meta.model)
+ self.assertEqual((), AbstractFactory._meta.inline_args)
+ self.assertEqual((), AbstractFactory._meta.exclude)
+ self.assertEqual(base.CREATE_STRATEGY, AbstractFactory._meta.strategy)
+
+ # Non-declarative attributes
+ self.assertEqual({}, AbstractFactory._meta.declarations)
+ self.assertEqual({}, AbstractFactory._meta.postgen_declarations)
+ self.assertEqual(AbstractFactory, AbstractFactory._meta.factory)
+ self.assertEqual(base.Factory, AbstractFactory._meta.base_factory)
+ self.assertEqual(AbstractFactory, AbstractFactory._meta.counter_reference)
+
+ def test_declaration_collecting(self):
+ lazy = declarations.LazyAttribute(lambda _o: 1)
+ postgen = declarations.PostGenerationDeclaration()
+
+ class AbstractFactory(base.Factory):
+ x = 1
+ y = lazy
+ z = postgen
+
+ # Declarations aren't removed
+ self.assertEqual(1, AbstractFactory.x)
+ self.assertEqual(lazy, AbstractFactory.y)
+ self.assertEqual(postgen, AbstractFactory.z)
+
+ # And are available in class Meta
+ self.assertEqual({'x': 1, 'y': lazy}, AbstractFactory._meta.declarations)
+ self.assertEqual({'z': postgen}, AbstractFactory._meta.postgen_declarations)
+
+ def test_inherited_declaration_collecting(self):
+ lazy = declarations.LazyAttribute(lambda _o: 1)
+ lazy2 = declarations.LazyAttribute(lambda _o: 2)
+ postgen = declarations.PostGenerationDeclaration()
+ postgen2 = declarations.PostGenerationDeclaration()
+
+ class AbstractFactory(base.Factory):
+ x = 1
+ y = lazy
+ z = postgen
+
+ class OtherFactory(AbstractFactory):
+ a = lazy2
+ b = postgen2
+
+ # Declarations aren't removed
+ self.assertEqual(lazy2, OtherFactory.a)
+ self.assertEqual(postgen2, OtherFactory.b)
+ self.assertEqual(1, OtherFactory.x)
+ self.assertEqual(lazy, OtherFactory.y)
+ self.assertEqual(postgen, OtherFactory.z)
+
+ # And are available in class Meta
+ self.assertEqual({'x': 1, 'y': lazy, 'a': lazy2}, OtherFactory._meta.declarations)
+ self.assertEqual({'z': postgen, 'b': postgen2}, OtherFactory._meta.postgen_declarations)
+
+ def test_inherited_declaration_shadowing(self):
+ lazy = declarations.LazyAttribute(lambda _o: 1)
+ lazy2 = declarations.LazyAttribute(lambda _o: 2)
+ postgen = declarations.PostGenerationDeclaration()
+ postgen2 = declarations.PostGenerationDeclaration()
+
+ class AbstractFactory(base.Factory):
+ x = 1
+ y = lazy
+ z = postgen
+
+ class OtherFactory(AbstractFactory):
+ y = lazy2
+ z = postgen2
+
+ # Declarations aren't removed
+ self.assertEqual(1, OtherFactory.x)
+ self.assertEqual(lazy2, OtherFactory.y)
+ self.assertEqual(postgen2, OtherFactory.z)
+
+ # And are available in class Meta
+ self.assertEqual({'x': 1, 'y': lazy2}, OtherFactory._meta.declarations)
+ self.assertEqual({'z': postgen2}, OtherFactory._meta.postgen_declarations)
+
+
+class DeclarationParsingTests(unittest.TestCase):
+ def test_classmethod(self):
+ class TestObjectFactory(base.Factory):
+ class Meta:
+ model = TestObject
+
+ @classmethod
+ def some_classmethod(cls):
+ return cls.create()
+
+ self.assertTrue(hasattr(TestObjectFactory, 'some_classmethod'))
+ obj = TestObjectFactory.some_classmethod()
+ self.assertEqual(TestObject, obj.__class__)
+
class FactoryTestCase(unittest.TestCase):
- def test_factory_for(self):
+ def test_magic_happens(self):
+ """Calling a FooFactory doesn't yield a FooFactory instance."""
class TestObjectFactory(base.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
- self.assertEqual(TestObject, TestObjectFactory.FACTORY_FOR)
+ self.assertEqual(TestObject, TestObjectFactory._meta.model)
obj = TestObjectFactory.build()
- self.assertFalse(hasattr(obj, 'FACTORY_FOR'))
+ self.assertFalse(hasattr(obj, '_meta'))
def test_display(self):
class TestObjectFactory(base.Factory):
- FACTORY_FOR = FakeDjangoModel
+ class Meta:
+ model = FakeDjangoModel
self.assertIn('TestObjectFactory', str(TestObjectFactory))
self.assertIn('FakeDjangoModel', str(TestObjectFactory))
def test_lazy_attribute_non_existent_param(self):
class TestObjectFactory(base.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = declarations.LazyAttribute(lambda a: a.does_not_exist )
@@ -115,12 +248,14 @@ class FactoryTestCase(unittest.TestCase):
def test_inheritance_with_sequence(self):
"""Tests that sequence IDs are shared between parent and son."""
class TestObjectFactory(base.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = declarations.Sequence(lambda a: a)
class TestSubFactory(TestObjectFactory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
pass
@@ -137,7 +272,8 @@ class FactorySequenceTestCase(unittest.TestCase):
super(FactorySequenceTestCase, self).setUp()
class TestObjectFactory(base.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = declarations.Sequence(lambda n: n)
self.TestObjectFactory = TestObjectFactory
@@ -212,16 +348,17 @@ class FactorySequenceTestCase(unittest.TestCase):
class FactoryDefaultStrategyTestCase(unittest.TestCase):
def setUp(self):
- self.default_strategy = base.Factory.FACTORY_STRATEGY
+ self.default_strategy = base.Factory._meta.strategy
def tearDown(self):
- base.Factory.FACTORY_STRATEGY = self.default_strategy
+ base.Factory._meta.strategy = self.default_strategy
def test_build_strategy(self):
- base.Factory.FACTORY_STRATEGY = base.BUILD_STRATEGY
+ base.Factory._meta.strategy = base.BUILD_STRATEGY
class TestModelFactory(base.Factory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -230,10 +367,11 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase):
self.assertFalse(test_model.id)
def test_create_strategy(self):
- # Default FACTORY_STRATEGY
+ # Default Meta.strategy
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -242,10 +380,11 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase):
self.assertTrue(test_model.id)
def test_stub_strategy(self):
- base.Factory.FACTORY_STRATEGY = base.STUB_STRATEGY
+ base.Factory._meta.strategy = base.STUB_STRATEGY
class TestModelFactory(base.Factory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -254,10 +393,11 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase):
self.assertFalse(hasattr(test_model, 'id')) # We should have a plain old object
def test_unknown_strategy(self):
- base.Factory.FACTORY_STRATEGY = 'unknown'
+ base.Factory._meta.strategy = 'unknown'
class TestModelFactory(base.Factory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -265,31 +405,34 @@ class FactoryDefaultStrategyTestCase(unittest.TestCase):
def test_stub_with_non_stub_strategy(self):
class TestModelFactory(base.StubFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
- TestModelFactory.FACTORY_STRATEGY = base.CREATE_STRATEGY
+ TestModelFactory._meta.strategy = base.CREATE_STRATEGY
self.assertRaises(base.StubFactory.UnsupportedStrategy, TestModelFactory)
- TestModelFactory.FACTORY_STRATEGY = base.BUILD_STRATEGY
+ TestModelFactory._meta.strategy = base.BUILD_STRATEGY
self.assertRaises(base.StubFactory.UnsupportedStrategy, TestModelFactory)
def test_change_strategy(self):
@base.use_strategy(base.CREATE_STRATEGY)
class TestModelFactory(base.StubFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
- self.assertEqual(base.CREATE_STRATEGY, TestModelFactory.FACTORY_STRATEGY)
+ self.assertEqual(base.CREATE_STRATEGY, TestModelFactory._meta.strategy)
class FactoryCreationTestCase(unittest.TestCase):
def test_factory_for(self):
class TestFactory(base.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
self.assertTrue(isinstance(TestFactory.build(), TestObject))
@@ -297,22 +440,24 @@ class FactoryCreationTestCase(unittest.TestCase):
class TestFactory(base.StubFactory):
pass
- self.assertEqual(TestFactory.FACTORY_STRATEGY, base.STUB_STRATEGY)
+ self.assertEqual(TestFactory._meta.strategy, base.STUB_STRATEGY)
def test_inheritance_with_stub(self):
class TestObjectFactory(base.StubFactory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
pass
class TestFactory(TestObjectFactory):
pass
- self.assertEqual(TestFactory.FACTORY_STRATEGY, base.STUB_STRATEGY)
+ self.assertEqual(TestFactory._meta.strategy, base.STUB_STRATEGY)
def test_custom_creation(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
@classmethod
def _prepare(cls, create, **kwargs):
@@ -335,28 +480,30 @@ class FactoryCreationTestCase(unittest.TestCase):
class Test(base.Factory):
pass
- self.assertTrue(Test._abstract_factory)
+ self.assertTrue(Test._meta.abstract)
class PostGenerationParsingTestCase(unittest.TestCase):
def test_extraction(self):
class TestObjectFactory(base.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
foo = declarations.PostGenerationDeclaration()
- self.assertIn('foo', TestObjectFactory._postgen_declarations)
+ self.assertIn('foo', TestObjectFactory._meta.postgen_declarations)
def test_classlevel_extraction(self):
class TestObjectFactory(base.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
foo = declarations.PostGenerationDeclaration()
foo__bar = 42
- self.assertIn('foo', TestObjectFactory._postgen_declarations)
- self.assertIn('foo__bar', TestObjectFactory._declarations)
+ self.assertIn('foo', TestObjectFactory._meta.postgen_declarations)
+ self.assertIn('foo__bar', TestObjectFactory._meta.declarations)
diff --git a/tests/test_containers.py b/tests/test_containers.py
index 8b78dc7..bd7019e 100644
--- a/tests/test_containers.py
+++ b/tests/test_containers.py
@@ -94,111 +94,12 @@ class LazyStubTestCase(unittest.TestCase):
class RandomObj(object):
pass
- stub = containers.LazyStub({'one': 1, 'two': 2}, target_class=RandomObj)
+ stub = containers.LazyStub({'one': 1, 'two': 2}, model_class=RandomObj)
self.assertIn('RandomObj', repr(stub))
self.assertIn('RandomObj', str(stub))
self.assertIn('one', str(stub))
-class OrderedDeclarationMock(declarations.OrderedDeclaration):
- pass
-
-
-class DeclarationDictTestCase(unittest.TestCase):
- def test_basics(self):
- one = OrderedDeclarationMock()
- two = 2
- three = OrderedDeclarationMock()
-
- d = containers.DeclarationDict(dict(one=one, two=two, three=three))
-
- self.assertTrue('one' in d)
- self.assertTrue('two' in d)
- self.assertTrue('three' in d)
-
- self.assertEqual(one, d['one'])
- self.assertEqual(two, d['two'])
- self.assertEqual(three, d['three'])
-
- self.assertEqual(one, d.pop('one'))
- self.assertFalse('one' in d)
-
- d['one'] = one
- self.assertTrue('one' in d)
- self.assertEqual(one, d['one'])
-
- self.assertEqual(set(['one', 'two', 'three']),
- set(d))
-
- def test_insert(self):
- one = OrderedDeclarationMock()
- two = 2
- three = OrderedDeclarationMock()
- four = OrderedDeclarationMock()
-
- d = containers.DeclarationDict(dict(one=one, two=two, four=four))
-
- self.assertEqual(set(['two', 'one', 'four']), set(d))
-
- d['three'] = three
- self.assertEqual(set(['two', 'one', 'three', 'four']), set(d))
-
- def test_replace(self):
- one = OrderedDeclarationMock()
- two = 2
- three = OrderedDeclarationMock()
- four = OrderedDeclarationMock()
-
- d = containers.DeclarationDict(dict(one=one, two=two, three=three))
-
- self.assertEqual(set(['two', 'one', 'three']), set(d))
-
- d['three'] = four
- self.assertEqual(set(['two', 'one', 'three']), set(d))
- self.assertEqual(set([two, one, four]), set(d.values()))
-
- def test_copy(self):
- one = OrderedDeclarationMock()
- two = 2
- three = OrderedDeclarationMock()
- four = OrderedDeclarationMock()
-
- d = containers.DeclarationDict(dict(one=one, two=two, three=three))
- d2 = d.copy({'five': 5})
-
- self.assertEqual(5, d2['five'])
- self.assertFalse('five' in d)
-
- d.pop('one')
- self.assertEqual(one, d2['one'])
-
- d2['two'] = four
- self.assertEqual(four, d2['two'])
- self.assertEqual(two, d['two'])
-
- def test_update_with_public(self):
- d = containers.DeclarationDict()
- d.update_with_public({
- 'one': 1,
- '_two': 2,
- 'three': 3,
- 'classmethod': classmethod(lambda c: 1),
- 'staticmethod': staticmethod(lambda: 1),
- })
- self.assertEqual(set(['one', 'three']), set(d))
- self.assertEqual(set([1, 3]), set(d.values()))
-
- def test_update_with_public_ignores_factory_attributes(self):
- """Ensure that a DeclarationDict ignores FACTORY_ keys."""
- d = containers.DeclarationDict()
- d.update_with_public({
- 'one': 1,
- 'FACTORY_FOR': 2,
- 'FACTORY_ARG_PARAMETERS': 3,
- })
- self.assertEqual(['one'], list(d))
- self.assertEqual([1], list(d.values()))
-
class AttributeBuilderTestCase(unittest.TestCase):
def test_empty(self):
@@ -320,7 +221,7 @@ class AttributeBuilderTestCase(unittest.TestCase):
class FakeFactory(object):
@classmethod
def declarations(cls, extra):
- d = containers.DeclarationDict({'one': 1, 'two': la})
+ d = {'one': 1, 'two': la}
d.update(extra)
return d
diff --git a/tests/test_deprecation.py b/tests/test_deprecation.py
new file mode 100644
index 0000000..a07cbf3
--- /dev/null
+++ b/tests/test_deprecation.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2010 Mark Sandstrom
+# Copyright (c) 2011-2013 Raphaël Barrois
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+"""Tests for deprecated features."""
+
+import warnings
+
+import factory
+
+from .compat import mock, unittest
+from . import tools
+
+
+class DeprecationTests(unittest.TestCase):
+ def test_factory_for(self):
+ class Foo(object):
+ pass
+
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter('always')
+ class FooFactory(factory.Factory):
+ FACTORY_FOR = Foo
+
+ self.assertEqual(1, len(w))
+ warning = w[0]
+ # Message is indeed related to the current file
+ # This is to ensure error messages are readable by end users.
+ self.assertIn(warning.filename, __file__)
+ self.assertIn('FACTORY_FOR', str(warning.message))
+ self.assertIn('model', str(warning.message))
diff --git a/tests/test_django.py b/tests/test_django.py
index e4bbc2b..41a26cf 100644
--- a/tests/test_django.py
+++ b/tests/test_django.py
@@ -42,7 +42,7 @@ except ImportError: # pragma: no cover
Image = None
-from .compat import is_python2, unittest
+from .compat import is_python2, unittest, mock
from . import testdata
from . import tools
@@ -55,6 +55,7 @@ if django is not None:
from django.db import models as django_models
from django.test import simple as django_test_simple
from django.test import utils as django_test_utils
+ from django.db.models import signals
from .djapp import models
else: # pragma: no cover
django_test = unittest
@@ -70,6 +71,7 @@ else: # pragma: no cover
models.NonIntegerPk = Fake
models.WithFile = Fake
models.WithImage = Fake
+ models.WithSignals = Fake
test_state = {}
@@ -97,51 +99,82 @@ def tearDownModule():
class StandardFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.StandardModel
+ class Meta:
+ model = models.StandardModel
foo = factory.Sequence(lambda n: "foo%d" % n)
class StandardFactoryWithPKField(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.StandardModel
- FACTORY_DJANGO_GET_OR_CREATE = ('pk',)
+ class Meta:
+ model = models.StandardModel
+ django_get_or_create = ('pk',)
foo = factory.Sequence(lambda n: "foo%d" % n)
pk = None
class NonIntegerPkFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.NonIntegerPk
+ class Meta:
+ model = models.NonIntegerPk
foo = factory.Sequence(lambda n: "foo%d" % n)
bar = ''
class AbstractBaseFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.AbstractBase
- ABSTRACT_FACTORY = True
+ class Meta:
+ model = models.AbstractBase
+ abstract = True
foo = factory.Sequence(lambda n: "foo%d" % n)
class ConcreteSonFactory(AbstractBaseFactory):
- FACTORY_FOR = models.ConcreteSon
+ class Meta:
+ model = models.ConcreteSon
+
+
+class AbstractSonFactory(AbstractBaseFactory):
+ class Meta:
+ model = models.AbstractSon
+
+
+class ConcreteGrandSonFactory(AbstractBaseFactory):
+ class Meta:
+ model = models.ConcreteGrandSon
class WithFileFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.WithFile
+ class Meta:
+ model = models.WithFile
if django is not None:
afile = factory.django.FileField()
class WithImageFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = models.WithImage
+ class Meta:
+ model = models.WithImage
if django is not None:
animage = factory.django.ImageField()
+class WithSignalsFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = models.WithSignals
+
+
+@unittest.skipIf(django is None, "Django not installed.")
+class ModelTests(django_test.TestCase):
+ def test_unset_model(self):
+ class UnsetModelFactory(factory.django.DjangoModelFactory):
+ pass
+
+ self.assertRaises(factory.FactoryError, UnsetModelFactory.create)
+
+
@unittest.skipIf(django is None, "Django not installed.")
class DjangoPkSequenceTestCase(django_test.TestCase):
def setUp(self):
@@ -206,17 +239,20 @@ class DjangoPkForceTestCase(django_test.TestCase):
@unittest.skipIf(django is None, "Django not installed.")
class DjangoModelLoadingTestCase(django_test.TestCase):
- """Tests FACTORY_FOR = 'app.Model' pattern."""
+ """Tests class Meta:
+ model = 'app.Model' pattern."""
def test_loading(self):
class ExampleFactory(factory.DjangoModelFactory):
- FACTORY_FOR = 'djapp.StandardModel'
+ class Meta:
+ model = 'djapp.StandardModel'
- self.assertEqual(models.StandardModel, ExampleFactory._get_target_class())
+ self.assertEqual(models.StandardModel, ExampleFactory._get_model_class())
def test_building(self):
class ExampleFactory(factory.DjangoModelFactory):
- FACTORY_FOR = 'djapp.StandardModel'
+ class Meta:
+ model = 'djapp.StandardModel'
e = ExampleFactory.build()
self.assertEqual(models.StandardModel, e.__class__)
@@ -227,7 +263,8 @@ class DjangoModelLoadingTestCase(django_test.TestCase):
See https://github.com/rbarrois/factory_boy/issues/109.
"""
class ExampleFactory(factory.DjangoModelFactory):
- FACTORY_FOR = 'djapp.StandardModel'
+ class Meta:
+ model = 'djapp.StandardModel'
class Example2Factory(ExampleFactory):
pass
@@ -241,14 +278,16 @@ class DjangoModelLoadingTestCase(django_test.TestCase):
See https://github.com/rbarrois/factory_boy/issues/109.
"""
class ExampleFactory(factory.DjangoModelFactory):
- FACTORY_FOR = 'djapp.StandardModel'
+ class Meta:
+ model = 'djapp.StandardModel'
foo = factory.Sequence(lambda n: n)
class Example2Factory(ExampleFactory):
- FACTORY_FOR = 'djapp.StandardSon'
+ class Meta:
+ model = 'djapp.StandardSon'
- self.assertEqual(models.StandardSon, Example2Factory._get_target_class())
+ self.assertEqual(models.StandardSon, Example2Factory._get_model_class())
e1 = ExampleFactory.build()
e2 = Example2Factory.build()
@@ -301,8 +340,13 @@ class DjangoNonIntegerPkTestCase(django_test.TestCase):
@unittest.skipIf(django is None, "Django not installed.")
class DjangoAbstractBaseSequenceTestCase(django_test.TestCase):
def test_auto_sequence(self):
- with factory.debug():
- obj = ConcreteSonFactory()
+ """The sequence of the concrete son of an abstract model should be autonomous."""
+ obj = ConcreteSonFactory()
+ self.assertEqual(1, obj.pk)
+
+ def test_auto_sequence(self):
+ """The sequence of the concrete grandson of an abstract model should be autonomous."""
+ obj = ConcreteGrandSonFactory()
self.assertEqual(1, obj.pk)
@@ -439,9 +483,9 @@ class DjangoImageFieldTestCase(unittest.TestCase):
self.assertEqual('django/example.jpg', o.animage.name)
i = Image.open(os.path.join(settings.MEDIA_ROOT, o.animage.name))
- colors = i.getcolors()
- # 169 pixels with color 190 from the GIF palette
- self.assertEqual([(169, 190)], colors)
+ colors = i.convert('RGB').getcolors()
+ # 169 pixels with rgb(0, 0, 255)
+ self.assertEqual([(169, (0, 0, 255))], colors)
self.assertEqual('GIF', i.format)
def test_with_file(self):
@@ -511,5 +555,91 @@ class DjangoImageFieldTestCase(unittest.TestCase):
self.assertFalse(o.animage)
+@unittest.skipIf(django is None, "Django not installed.")
+class PreventSignalsTestCase(unittest.TestCase):
+ def setUp(self):
+ self.handlers = mock.MagicMock()
+
+ signals.pre_init.connect(self.handlers.pre_init)
+ signals.pre_save.connect(self.handlers.pre_save)
+ signals.post_save.connect(self.handlers.post_save)
+
+ def tearDown(self):
+ signals.pre_init.disconnect(self.handlers.pre_init)
+ signals.pre_save.disconnect(self.handlers.pre_save)
+ signals.post_save.disconnect(self.handlers.post_save)
+
+ def assertSignalsReactivated(self):
+ WithSignalsFactory()
+
+ self.assertEqual(self.handlers.pre_save.call_count, 1)
+ self.assertEqual(self.handlers.post_save.call_count, 1)
+
+ def test_context_manager(self):
+ with factory.django.mute_signals(signals.pre_save, signals.post_save):
+ WithSignalsFactory()
+
+ self.assertEqual(self.handlers.pre_init.call_count, 1)
+ self.assertFalse(self.handlers.pre_save.called)
+ self.assertFalse(self.handlers.post_save.called)
+
+ self.assertSignalsReactivated()
+
+ def test_class_decorator(self):
+ @factory.django.mute_signals(signals.pre_save, signals.post_save)
+ class WithSignalsDecoratedFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = models.WithSignals
+
+ WithSignalsDecoratedFactory()
+
+ self.assertEqual(self.handlers.pre_init.call_count, 1)
+ self.assertFalse(self.handlers.pre_save.called)
+ self.assertFalse(self.handlers.post_save.called)
+
+ self.assertSignalsReactivated()
+
+ def test_class_decorator_build(self):
+ @factory.django.mute_signals(signals.pre_save, signals.post_save)
+ class WithSignalsDecoratedFactory(factory.django.DjangoModelFactory):
+ class Meta:
+ model = models.WithSignals
+
+ WithSignalsDecoratedFactory.build()
+
+ self.assertEqual(self.handlers.pre_init.call_count, 1)
+ self.assertFalse(self.handlers.pre_save.called)
+ self.assertFalse(self.handlers.post_save.called)
+
+ self.assertSignalsReactivated()
+
+ def test_function_decorator(self):
+ @factory.django.mute_signals(signals.pre_save, signals.post_save)
+ def foo():
+ WithSignalsFactory()
+
+ foo()
+
+ self.assertEqual(self.handlers.pre_init.call_count, 1)
+ self.assertFalse(self.handlers.pre_save.called)
+ self.assertFalse(self.handlers.post_save.called)
+
+ self.assertSignalsReactivated()
+
+ def test_classmethod_decorator(self):
+ class Foo(object):
+ @classmethod
+ @factory.django.mute_signals(signals.pre_save, signals.post_save)
+ def generate(cls):
+ WithSignalsFactory()
+
+ Foo.generate()
+
+ self.assertEqual(self.handlers.pre_init.call_count, 1)
+ self.assertFalse(self.handlers.pre_save.called)
+ self.assertFalse(self.handlers.post_save.called)
+
+ self.assertSignalsReactivated()
+
if __name__ == '__main__': # pragma: no cover
unittest.main()
diff --git a/tests/test_fuzzy.py b/tests/test_fuzzy.py
index d6f33bb..1caeb0a 100644
--- a/tests/test_fuzzy.py
+++ b/tests/test_fuzzy.py
@@ -89,24 +89,34 @@ class FuzzyIntegerTestCase(unittest.TestCase):
self.assertIn(res, [0, 1, 2, 3, 4])
def test_biased(self):
- fake_randint = lambda low, high: low + high
+ fake_randrange = lambda low, high, step: (low + high) * step
fuzz = fuzzy.FuzzyInteger(2, 8)
- with mock.patch('random.randint', fake_randint):
+ with mock.patch('random.randrange', fake_randrange):
res = fuzz.evaluate(2, None, False)
- self.assertEqual(10, res)
+ self.assertEqual((2 + 8 + 1) * 1, res)
def test_biased_high_only(self):
- fake_randint = lambda low, high: low + high
+ fake_randrange = lambda low, high, step: (low + high) * step
fuzz = fuzzy.FuzzyInteger(8)
- with mock.patch('random.randint', fake_randint):
+ with mock.patch('random.randrange', fake_randrange):
+ res = fuzz.evaluate(2, None, False)
+
+ self.assertEqual((0 + 8 + 1) * 1, res)
+
+ def test_biased_with_step(self):
+ fake_randrange = lambda low, high, step: (low + high) * step
+
+ fuzz = fuzzy.FuzzyInteger(5, 8, 3)
+
+ with mock.patch('random.randrange', fake_randrange):
res = fuzz.evaluate(2, None, False)
- self.assertEqual(8, res)
+ self.assertEqual((5 + 8 + 1) * 3, res)
class FuzzyDecimalTestCase(unittest.TestCase):
diff --git a/tests/test_mongoengine.py b/tests/test_mongoengine.py
index 803607a..988c179 100644
--- a/tests/test_mongoengine.py
+++ b/tests/test_mongoengine.py
@@ -42,12 +42,14 @@ if mongoengine:
address = mongoengine.EmbeddedDocumentField(Address)
class AddressFactory(MongoEngineFactory):
- FACTORY_FOR = Address
+ class Meta:
+ model = Address
street = factory.Sequence(lambda n: 'street%d' % n)
class PersonFactory(MongoEngineFactory):
- FACTORY_FOR = Person
+ class Meta:
+ model = Person
name = factory.Sequence(lambda n: 'name%d' % n)
address = factory.SubFactory(AddressFactory)
diff --git a/tests/test_using.py b/tests/test_using.py
index 3979cd0..f18df4d 100644
--- a/tests/test_using.py
+++ b/tests/test_using.py
@@ -78,11 +78,12 @@ class FakeModel(object):
class FakeModelFactory(factory.Factory):
- ABSTRACT_FACTORY = True
+ class Meta:
+ abstract = True
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- return target_class.create(**kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ return model_class.create(**kwargs)
class TestModel(FakeModel):
@@ -292,17 +293,19 @@ class SimpleBuildTestCase(unittest.TestCase):
class UsingFactoryTestCase(unittest.TestCase):
def test_attribute(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 'one'
test_object = TestObjectFactory.build()
self.assertEqual(test_object.one, 'one')
- def test_inheriting_target_class(self):
+ def test_inheriting_model_class(self):
@factory.use_strategy(factory.BUILD_STRATEGY)
class TestObjectFactory(factory.Factory, TestObject):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 'one'
@@ -311,18 +314,22 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_abstract(self):
class SomeAbstractFactory(factory.Factory):
- ABSTRACT_FACTORY = True
+ class Meta:
+ abstract = True
+
one = 'one'
class InheritedFactory(SomeAbstractFactory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
test_object = InheritedFactory.build()
self.assertEqual(test_object.one, 'one')
def test_sequence(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Sequence(lambda n: 'one%d' % n)
two = factory.Sequence(lambda n: 'two%d' % n)
@@ -337,7 +344,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_sequence_custom_begin(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
@classmethod
def _setup_next_sequence(cls):
@@ -356,7 +364,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_sequence_override(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Sequence(lambda n: 'one%d' % n)
@@ -372,13 +381,14 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_custom_create(self):
class TestModelFactory(factory.Factory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
two = 2
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- obj = target_class.create(**kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ obj = model_class.create(**kwargs)
obj.properly_created = True
return obj
@@ -395,7 +405,8 @@ class UsingFactoryTestCase(unittest.TestCase):
self.y = y
class NonDjangoFactory(factory.Factory):
- FACTORY_FOR = NonDjango
+ class Meta:
+ model = NonDjango
x = 3
@@ -405,7 +416,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_sequence_batch(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Sequence(lambda n: 'one%d' % n)
two = factory.Sequence(lambda n: 'two%d' % n)
@@ -420,7 +432,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_lazy_attribute(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.LazyAttribute(lambda a: 'abc' )
two = factory.LazyAttribute(lambda a: a.one + ' xyz')
@@ -431,7 +444,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_lazy_attribute_sequence(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.LazyAttributeSequence(lambda a, n: 'abc%d' % n)
two = factory.LazyAttributeSequence(lambda a, n: a.one + ' xyz%d' % n)
@@ -446,7 +460,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_lazy_attribute_decorator(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
@factory.lazy_attribute
def one(a):
@@ -460,7 +475,8 @@ class UsingFactoryTestCase(unittest.TestCase):
n = 3
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 'xx'
two = factory.SelfAttribute('one')
@@ -479,12 +495,14 @@ class UsingFactoryTestCase(unittest.TestCase):
pass
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 3
three = factory.SelfAttribute('..bar')
class TestModel2Factory(FakeModelFactory):
- FACTORY_FOR = TestModel2
+ class Meta:
+ model = TestModel2
bar = 4
two = factory.SubFactory(TestModelFactory, one=1)
@@ -493,7 +511,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_sequence_decorator(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
@factory.sequence
def one(n):
@@ -504,7 +523,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_lazy_attribute_sequence_decorator(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
@factory.lazy_attribute_sequence
def one(a, n):
@@ -519,7 +539,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_build_with_parameters(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Sequence(lambda n: 'one%d' % n)
two = factory.Sequence(lambda n: 'two%d' % n)
@@ -535,7 +556,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_create(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -545,7 +567,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_create_batch(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -561,7 +584,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_generate_build(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -571,7 +595,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_generate_create(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -581,7 +606,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_generate_stub(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -591,7 +617,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_generate_batch_build(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -607,7 +634,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_generate_batch_create(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -623,7 +651,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_generate_batch_stub(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -639,7 +668,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_simple_generate_build(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -649,7 +679,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_simple_generate_create(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -659,7 +690,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_simple_generate_batch_build(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -675,7 +707,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_simple_generate_batch_create(self):
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 'one'
@@ -691,7 +724,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_stub_batch(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 'one'
two = factory.LazyAttribute(lambda a: a.one + ' two')
@@ -710,13 +744,15 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_inheritance(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 'one'
two = factory.LazyAttribute(lambda a: a.one + ' two')
class TestObjectFactory2(TestObjectFactory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
three = 'three'
four = factory.LazyAttribute(lambda a: a.three + ' four')
@@ -730,15 +766,48 @@ class UsingFactoryTestCase(unittest.TestCase):
test_object_alt = TestObjectFactory.build()
self.assertEqual(None, test_object_alt.three)
+ def test_override_inherited(self):
+ """Overriding inherited declarations"""
+ class TestObjectFactory(factory.Factory):
+ class Meta:
+ model = TestObject
+
+ one = 'one'
+
+ class TestObjectFactory2(TestObjectFactory):
+ one = 'two'
+
+ test_object = TestObjectFactory2.build()
+ self.assertEqual('two', test_object.one)
+
+ def test_override_inherited_deep(self):
+ """Overriding inherited declarations"""
+ class TestObjectFactory(factory.Factory):
+ class Meta:
+ model = TestObject
+
+ one = 'one'
+
+ class TestObjectFactory2(TestObjectFactory):
+ one = 'two'
+
+ class TestObjectFactory3(TestObjectFactory2):
+ pass
+
+ test_object = TestObjectFactory3.build()
+ self.assertEqual('two', test_object.one)
+
def test_inheritance_and_sequences(self):
"""Sequence counters should be kept within an inheritance chain."""
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Sequence(lambda n: n)
class TestObjectFactory2(TestObjectFactory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
to1a = TestObjectFactory()
self.assertEqual(0, to1a.one)
@@ -755,12 +824,14 @@ class UsingFactoryTestCase(unittest.TestCase):
pass
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Sequence(lambda n: n)
class TestObjectFactory2(TestObjectFactory):
- FACTORY_FOR = TestObject2
+ class Meta:
+ model = TestObject2
to1a = TestObjectFactory()
self.assertEqual(0, to1a.one)
@@ -785,12 +856,14 @@ class UsingFactoryTestCase(unittest.TestCase):
self.one = one
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Sequence(lambda n: n)
class TestObjectFactory2(TestObjectFactory):
- FACTORY_FOR = TestObject2
+ class Meta:
+ model = TestObject2
to1a = TestObjectFactory()
self.assertEqual(0, to1a.one)
@@ -804,7 +877,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_inheritance_with_inherited_class(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 'one'
two = factory.LazyAttribute(lambda a: a.one + ' two')
@@ -821,12 +895,14 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_dual_inheritance(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 'one'
class TestOtherFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
two = 'two'
four = 'four'
@@ -841,7 +917,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_class_method_accessible(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
@classmethod
def alt_create(cls, **kwargs):
@@ -851,7 +928,8 @@ class UsingFactoryTestCase(unittest.TestCase):
def test_static_method_accessible(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
@staticmethod
def alt_create(**kwargs):
@@ -859,15 +937,16 @@ class UsingFactoryTestCase(unittest.TestCase):
self.assertEqual(TestObjectFactory.alt_create(foo=1), {"foo": 1})
- def test_arg_parameters(self):
+ def test_inline_args(self):
class TestObject(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
- FACTORY_ARG_PARAMETERS = ('x', 'y')
+ class Meta:
+ model = TestObject
+ inline_args = ('x', 'y')
x = 1
y = 2
@@ -878,15 +957,16 @@ class UsingFactoryTestCase(unittest.TestCase):
self.assertEqual((42, 2), obj.args)
self.assertEqual({'z': 5, 't': 4}, obj.kwargs)
- def test_hidden_args(self):
+ def test_exclude(self):
class TestObject(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
- FACTORY_HIDDEN_ARGS = ('x', 'z')
+ class Meta:
+ model = TestObject
+ exclude = ('x', 'z')
x = 1
y = 2
@@ -897,16 +977,17 @@ class UsingFactoryTestCase(unittest.TestCase):
self.assertEqual((), obj.args)
self.assertEqual({'y': 2, 't': 4}, obj.kwargs)
- def test_hidden_args_and_arg_parameters(self):
+ def test_exclude_and_inline_args(self):
class TestObject(object):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
- FACTORY_HIDDEN_ARGS = ('x', 'z')
- FACTORY_ARG_PARAMETERS = ('y',)
+ class Meta:
+ model = TestObject
+ exclude = ('x', 'z')
+ inline_args = ('y',)
x = 1
y = 2
@@ -927,8 +1008,9 @@ class NonKwargParametersTestCase(unittest.TestCase):
self.kwargs = kwargs
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
- FACTORY_ARG_PARAMETERS = ('one', 'two',)
+ class Meta:
+ model = TestObject
+ inline_args = ('one', 'two',)
one = 1
two = 2
@@ -952,16 +1034,17 @@ class NonKwargParametersTestCase(unittest.TestCase):
return inst
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
- FACTORY_ARG_PARAMETERS = ('one', 'two')
+ class Meta:
+ model = TestObject
+ inline_args = ('one', 'two')
one = 1
two = 2
three = 3
@classmethod
- def _create(cls, target_class, *args, **kwargs):
- return target_class.create(*args, **kwargs)
+ def _create(cls, model_class, *args, **kwargs):
+ return model_class.create(*args, **kwargs)
obj = TestObjectFactory.create()
self.assertEqual((1, 2), obj.args)
@@ -978,7 +1061,8 @@ class KwargAdjustTestCase(unittest.TestCase):
self.kwargs = kwargs
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
@classmethod
def _adjust_kwargs(cls, **kwargs):
@@ -996,11 +1080,13 @@ class SubFactoryTestCase(unittest.TestCase):
pass
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 3
class TestModel2Factory(FakeModelFactory):
- FACTORY_FOR = TestModel2
+ class Meta:
+ model = TestModel2
two = factory.SubFactory(TestModelFactory, one=1)
test_model = TestModel2Factory(two__one=4)
@@ -1013,10 +1099,12 @@ class SubFactoryTestCase(unittest.TestCase):
pass
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
class TestModel2Factory(FakeModelFactory):
- FACTORY_FOR = TestModel2
+ class Meta:
+ model = TestModel2
two = factory.SubFactory(TestModelFactory,
one=factory.Sequence(lambda n: 'x%dx' % n),
two=factory.LazyAttribute(lambda o: '%s%s' % (o.one, o.one)),
@@ -1033,12 +1121,14 @@ class SubFactoryTestCase(unittest.TestCase):
setattr(self, k, v)
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Sequence(lambda n: int(n))
class WrappingTestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
wrapped = factory.SubFactory(TestObjectFactory)
@@ -1054,7 +1144,8 @@ class SubFactoryTestCase(unittest.TestCase):
setattr(self, k, v)
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
class OtherTestObject(object):
@@ -1063,7 +1154,8 @@ class SubFactoryTestCase(unittest.TestCase):
setattr(self, k, v)
class WrappingTestObjectFactory(factory.Factory):
- FACTORY_FOR = OtherTestObject
+ class Meta:
+ model = OtherTestObject
wrapped = factory.SubFactory(TestObjectFactory, two=2, four=4)
wrapped__two = 4
@@ -1083,16 +1175,19 @@ class SubFactoryTestCase(unittest.TestCase):
setattr(self, k, v)
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
class WrappingTestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
wrapped = factory.SubFactory(TestObjectFactory)
wrapped_bis = factory.SubFactory(TestObjectFactory, one=1)
class OuterWrappingTestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
wrap = factory.SubFactory(WrappingTestObjectFactory, wrapped__two=2)
@@ -1109,17 +1204,20 @@ class SubFactoryTestCase(unittest.TestCase):
setattr(self, k, v)
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
two = 'two'
class WrappingTestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
wrapped = factory.SubFactory(TestObjectFactory)
friend = factory.LazyAttribute(lambda o: o.wrapped.two.four + 1)
class OuterWrappingTestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
wrap = factory.SubFactory(WrappingTestObjectFactory,
wrapped__two=factory.SubFactory(TestObjectFactory, four=4))
@@ -1139,12 +1237,14 @@ class SubFactoryTestCase(unittest.TestCase):
# Innermost factory
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
two = 'two'
# Intermediary factory
class WrappingTestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
wrapped = factory.SubFactory(TestObjectFactory)
wrapped__two = 'three'
@@ -1162,11 +1262,13 @@ class SubFactoryTestCase(unittest.TestCase):
setattr(self, k, v)
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
two = 'two'
class WrappingTestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
wrapped = factory.SubFactory(TestObjectFactory)
friend = factory.LazyAttribute(lambda o: o.wrapped.two + 1)
@@ -1200,20 +1302,24 @@ class SubFactoryTestCase(unittest.TestCase):
self.side_b = side_b
class InnerMostFactory(factory.Factory):
- FACTORY_FOR = InnerMost
+ class Meta:
+ model = InnerMost
a = 15
b = 20
class SideAFactory(factory.Factory):
- FACTORY_FOR = SideA
+ class Meta:
+ model = SideA
inner_from_a = factory.SubFactory(InnerMostFactory, a=20)
class SideBFactory(factory.Factory):
- FACTORY_FOR = SideB
+ class Meta:
+ model = SideB
inner_from_b = factory.SubFactory(InnerMostFactory, b=15)
class OuterMostFactory(factory.Factory):
- FACTORY_FOR = OuterMost
+ class Meta:
+ model = OuterMost
foo = 30
side_a = factory.SubFactory(SideAFactory,
@@ -1238,12 +1344,14 @@ class SubFactoryTestCase(unittest.TestCase):
pass
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 3
two = factory.ContainerAttribute(lambda obj, containers: len(containers or []), strict=False)
class TestModel2Factory(FakeModelFactory):
- FACTORY_FOR = TestModel2
+ class Meta:
+ model = TestModel2
one = 1
two = factory.SubFactory(TestModelFactory, one=1)
@@ -1261,12 +1369,14 @@ class SubFactoryTestCase(unittest.TestCase):
pass
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 3
two = factory.ContainerAttribute(lambda obj, containers: len(containers or []), strict=True)
class TestModel2Factory(FakeModelFactory):
- FACTORY_FOR = TestModel2
+ class Meta:
+ model = TestModel2
one = 1
two = factory.SubFactory(TestModelFactory, one=1)
@@ -1282,7 +1392,8 @@ class SubFactoryTestCase(unittest.TestCase):
pass
class TestModelFactory(FakeModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
one = 3
@factory.container_attribute
@@ -1292,7 +1403,8 @@ class SubFactoryTestCase(unittest.TestCase):
return 42
class TestModel2Factory(FakeModelFactory):
- FACTORY_FOR = TestModel2
+ class Meta:
+ model = TestModel2
one = 1
two = factory.SubFactory(TestModelFactory, one=1)
@@ -1310,7 +1422,8 @@ class IteratorTestCase(unittest.TestCase):
def test_iterator(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Iterator(range(10, 30))
@@ -1323,7 +1436,8 @@ class IteratorTestCase(unittest.TestCase):
@tools.disable_warnings
def test_iterator_list_comprehension_scope_bleeding(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Iterator([j * 3 for j in range(5)])
@@ -1334,7 +1448,8 @@ class IteratorTestCase(unittest.TestCase):
@tools.disable_warnings
def test_iterator_list_comprehension_protected(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Iterator([_j * 3 for _j in range(5)])
@@ -1347,7 +1462,8 @@ class IteratorTestCase(unittest.TestCase):
def test_iterator_decorator(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
@factory.iterator
def one():
@@ -1397,7 +1513,8 @@ class BetterFakeModel(object):
class DjangoModelFactoryTestCase(unittest.TestCase):
def test_simple(self):
class FakeModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = FakeModel
+ class Meta:
+ model = FakeModel
obj = FakeModelFactory(one=1)
self.assertEqual(1, obj.one)
@@ -1411,8 +1528,9 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
objects = BetterFakeModelManager({'x': 1}, prev)
class MyFakeModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = MyFakeModel
- FACTORY_DJANGO_GET_OR_CREATE = ('x',)
+ class Meta:
+ model = MyFakeModel
+ django_get_or_create = ('x',)
x = 1
y = 4
z = 6
@@ -1432,8 +1550,9 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
objects = BetterFakeModelManager({'x': 1, 'y': 2, 'z': 3}, prev)
class MyFakeModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = MyFakeModel
- FACTORY_DJANGO_GET_OR_CREATE = ('x', 'y', 'z')
+ class Meta:
+ model = MyFakeModel
+ django_get_or_create = ('x', 'y', 'z')
x = 1
y = 4
z = 6
@@ -1453,8 +1572,9 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
objects = BetterFakeModelManager({'x': 1}, prev)
class MyFakeModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = MyFakeModel
- FACTORY_DJANGO_GET_OR_CREATE = ('x',)
+ class Meta:
+ model = MyFakeModel
+ django_get_or_create = ('x',)
x = 1
y = 4
z = 6
@@ -1474,8 +1594,9 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
objects = BetterFakeModelManager({'x': 1, 'y': 2, 'z': 3}, prev)
class MyFakeModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = MyFakeModel
- FACTORY_DJANGO_GET_OR_CREATE = ('x', 'y', 'z')
+ class Meta:
+ model = MyFakeModel
+ django_get_or_create = ('x', 'y', 'z')
x = 1
y = 4
z = 6
@@ -1489,7 +1610,8 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
def test_sequence(self):
class TestModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
a = factory.Sequence(lambda n: 'foo_%s' % n)
@@ -1507,7 +1629,8 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
def test_no_get_or_create(self):
class TestModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = TestModel
+ class Meta:
+ model = TestModel
a = factory.Sequence(lambda n: 'foo_%s' % n)
@@ -1518,8 +1641,9 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
def test_get_or_create(self):
class TestModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = TestModel
- FACTORY_DJANGO_GET_OR_CREATE = ('a', 'b')
+ class Meta:
+ model = TestModel
+ django_get_or_create = ('a', 'b')
a = factory.Sequence(lambda n: 'foo_%s' % n)
b = 2
@@ -1537,8 +1661,9 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
def test_full_get_or_create(self):
"""Test a DjangoModelFactory with all fields in get_or_create."""
class TestModelFactory(factory.django.DjangoModelFactory):
- FACTORY_FOR = TestModel
- FACTORY_DJANGO_GET_OR_CREATE = ('a', 'b', 'c', 'd')
+ class Meta:
+ model = TestModel
+ django_get_or_create = ('a', 'b', 'c', 'd')
a = factory.Sequence(lambda n: 'foo_%s' % n)
b = 2
@@ -1557,7 +1682,8 @@ class DjangoModelFactoryTestCase(unittest.TestCase):
class PostGenerationTestCase(unittest.TestCase):
def test_post_generation(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 1
@@ -1575,7 +1701,8 @@ class PostGenerationTestCase(unittest.TestCase):
def test_post_generation_hook(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 1
@@ -1596,7 +1723,8 @@ class PostGenerationTestCase(unittest.TestCase):
def test_post_generation_extraction(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 1
@@ -1621,7 +1749,8 @@ class PostGenerationTestCase(unittest.TestCase):
self.assertEqual(kwargs, {'foo': 13})
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
bar = factory.PostGeneration(my_lambda)
@@ -1640,7 +1769,8 @@ class PostGenerationTestCase(unittest.TestCase):
self.extra = (args, kwargs)
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 3
two = 2
post_call = factory.PostGenerationMethodCall('call', one=1)
@@ -1664,12 +1794,14 @@ class PostGenerationTestCase(unittest.TestCase):
self.three = obj
class TestRelatedObjectFactory(factory.Factory):
- FACTORY_FOR = TestRelatedObject
+ class Meta:
+ model = TestRelatedObject
one = 1
two = factory.LazyAttribute(lambda o: o.one + 1)
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 3
two = 2
three = factory.RelatedFactory(TestRelatedObjectFactory, name='obj')
@@ -1709,12 +1841,14 @@ class PostGenerationTestCase(unittest.TestCase):
self.three = obj
class TestRelatedObjectFactory(factory.Factory):
- FACTORY_FOR = TestRelatedObject
+ class Meta:
+ model = TestRelatedObject
one = 1
two = factory.LazyAttribute(lambda o: o.one + 1)
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 3
two = 2
three = factory.RelatedFactory(TestRelatedObjectFactory)
@@ -1755,10 +1889,12 @@ class RelatedFactoryExtractionTestCase(unittest.TestCase):
obj.related = subself
class TestRelatedObjectFactory(factory.Factory):
- FACTORY_FOR = TestRelatedObject
+ class Meta:
+ model = TestRelatedObject
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.RelatedFactory(TestRelatedObjectFactory, 'obj')
self.TestRelatedObject = TestRelatedObject
@@ -1805,7 +1941,8 @@ class CircularTestCase(unittest.TestCase):
class DictTestCase(unittest.TestCase):
def test_empty_dict(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Dict({})
o = TestObjectFactory()
@@ -1813,7 +1950,8 @@ class DictTestCase(unittest.TestCase):
def test_naive_dict(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Dict({'a': 1})
o = TestObjectFactory()
@@ -1821,7 +1959,8 @@ class DictTestCase(unittest.TestCase):
def test_sequence_dict(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Dict({'a': factory.Sequence(lambda n: n + 2)})
o1 = TestObjectFactory()
@@ -1832,7 +1971,8 @@ class DictTestCase(unittest.TestCase):
def test_dict_override(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Dict({'a': 1})
o = TestObjectFactory(one__a=2)
@@ -1840,7 +1980,8 @@ class DictTestCase(unittest.TestCase):
def test_dict_extra_key(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.Dict({'a': 1})
o = TestObjectFactory(one__b=2)
@@ -1848,7 +1989,8 @@ class DictTestCase(unittest.TestCase):
def test_dict_merged_fields(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
two = 13
one = factory.Dict({
'one': 1,
@@ -1861,7 +2003,8 @@ class DictTestCase(unittest.TestCase):
def test_nested_dicts(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = 1
two = factory.Dict({
'one': 3,
@@ -1889,7 +2032,8 @@ class DictTestCase(unittest.TestCase):
class ListTestCase(unittest.TestCase):
def test_empty_list(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.List([])
o = TestObjectFactory()
@@ -1897,7 +2041,8 @@ class ListTestCase(unittest.TestCase):
def test_naive_list(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.List([1])
o = TestObjectFactory()
@@ -1905,7 +2050,8 @@ class ListTestCase(unittest.TestCase):
def test_sequence_list(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.List([factory.Sequence(lambda n: n + 2)])
o1 = TestObjectFactory()
@@ -1916,7 +2062,8 @@ class ListTestCase(unittest.TestCase):
def test_list_override(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.List([1])
o = TestObjectFactory(one__0=2)
@@ -1924,7 +2071,8 @@ class ListTestCase(unittest.TestCase):
def test_list_extra_key(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
one = factory.List([1])
o = TestObjectFactory(one__1=2)
@@ -1932,7 +2080,8 @@ class ListTestCase(unittest.TestCase):
def test_list_merged_fields(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
two = 13
one = factory.List([
1,
@@ -1945,7 +2094,9 @@ class ListTestCase(unittest.TestCase):
def test_nested_lists(self):
class TestObjectFactory(factory.Factory):
- FACTORY_FOR = TestObject
+ class Meta:
+ model = TestObject
+
one = 1
two = factory.List([
3,