summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorThomas Goirand <thomas@goirand.fr>2014-05-03 22:57:46 +0800
committerThomas Goirand <thomas@goirand.fr>2014-05-03 22:57:46 +0800
commit1073f2ae50fb0999712b6744b082f7424e4490c3 (patch)
tree26943f3545ce1fc1ae54e0398fbbd78df641f54d /docs
parentc9f01d77941527b62ca67b1064bd3fb849b6a064 (diff)
parent90db123ada9739a19f3b408b50e006700923f651 (diff)
downloadfactory-boy-1073f2ae50fb0999712b6744b082f7424e4490c3.tar
factory-boy-1073f2ae50fb0999712b6744b082f7424e4490c3.tar.gz
Merge tag '2.3.1' into debian/unstable
Release of factory_boy 2.3.1
Diffstat (limited to 'docs')
-rw-r--r--docs/changelog.rst167
-rw-r--r--docs/conf.py15
-rw-r--r--docs/fuzzy.rst220
-rw-r--r--docs/introduction.rst22
-rw-r--r--docs/orms.rst212
-rw-r--r--docs/recipes.rst79
-rw-r--r--docs/reference.rst185
7 files changed, 860 insertions, 40 deletions
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 173c40f..4917578 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -1,24 +1,139 @@
ChangeLog
=========
+
+.. _v2.3.1:
+
+2.3.1 (2014-01-22)
+------------------
+
+*Bugfix:*
+
+ - Fix badly written assert containing state-changing code, spotted by `chsigi <https://github.com/chsigi>`_ (:issue:`126`)
+ - Don't crash when handling objects whose __repr__ is non-pure-ascii bytes on Py2,
+ discovered by `mbertheau <https://github.com/mbertheau>`_ (:issue:`123`) and `strycore <https://github.com/strycore>`_ (:issue:`127`)
+
+.. _v2.3.0:
+
+2.3.0 (2013-12-25)
+------------------
+
+*New:*
+
+ - Add :class:`~factory.fuzzy.FuzzyText`, thanks to `jdufresne <https://github.com/jdufresne>`_ (:issue:`97`)
+ - Add :class:`~factory.fuzzy.FuzzyDecimal`, thanks to `thedrow <https://github.com/thedrow>`_ (:issue:`94`)
+ - Add support for :class:`~mongoengine.EmbeddedDocument`, thanks to `imiric <https://github.com/imiric>`_ (:issue:`100`)
+
+.. _v2.2.1:
+
+2.2.1 (2013-09-24)
+------------------
+
+*Bugfix:*
+
+ - Fixed sequence counter for :class:`~factory.django.DjangoModelFactory` when a factory
+ inherits from another factory relating to an abstract model.
+
+.. _v2.2.0:
+
+2.2.0 (2013-09-24)
+------------------
+
+*Bugfix:*
+
+ - Removed duplicated :class:`~factory.alchemy.SQLAlchemyModelFactory` lurking in :mod:`factory`
+ (:issue:`83`)
+ - Properly handle sequences within object inheritance chains.
+ If FactoryA inherits from FactoryB, and their associated classes share the same link,
+ sequence counters will be shared (:issue:`93`)
+ - Properly handle nested :class:`~factory.SubFactory` overrides
+
+*New:*
+
+ - The :class:`~factory.django.DjangoModelFactory` now supports the ``FACTORY_FOR = 'myapp.MyModel'``
+ syntax, making it easier to shove all factories in a single module (:issue:`66`).
+ - Add :meth:`factory.debug()` helper for easier backtrace analysis
+ - Adding factory support for mongoengine with :class:`~factory.mongoengine.MongoEngineFactory`.
+
+.. _v2.1.2:
+
+2.1.2 (2013-08-14)
+------------------
+
+*New:*
+
+ - The :class:`~factory.Factory.ABSTRACT_FACTORY` keyword is now optional, and automatically set
+ to ``True`` if neither the :class:`~factory.Factory` subclass nor its parent declare the
+ :class:`~factory.Factory.FACTORY_FOR` attribute (:issue:`74`)
+
+
+.. _v2.1.1:
+
+2.1.1 (2013-07-02)
+------------------
+
+*Bugfix:*
+
+ - Properly retrieve the ``color`` keyword argument passed to :class:`~factory.django.ImageField`
+
+.. _v2.1.0:
+
+2.1.0 (2013-06-26)
+------------------
+
+*New:*
+
+ - Add :class:`~factory.fuzzy.FuzzyDate` thanks to `saulshanabrook <https://github.com/saulshanabrook>`_
+ - Add :class:`~factory.fuzzy.FuzzyDateTime` and :class:`~factory.fuzzy.FuzzyNaiveDateTime`.
+ - Add a :attr:`~factory.containers.LazyStub.factory_parent` attribute to the
+ :class:`~factory.containers.LazyStub` passed to :class:`~factory.LazyAttribute`, in order to access
+ fields defined in wrapping factories.
+ - Move :class:`~factory.django.DjangoModelFactory` and :class:`~factory.mogo.MogoFactory`
+ to their own modules (:mod:`factory.django` and :mod:`factory.mogo`)
+ - Add the :meth:`~factory.Factory.reset_sequence` classmethod to :class:`~factory.Factory`
+ to ease resetting the sequence counter for a given factory.
+ - Add debug messages to ``factory`` logger.
+ - Add a :meth:`~factory.Iterator.reset` method to :class:`~factory.Iterator` (:issue:`63`)
+ - Add support for the SQLAlchemy ORM through :class:`~factory.alchemy.SQLAlchemyModelFactory`
+ (:issue:`64`, thanks to `Romain Commandé <https://github.com/rcommande>`_)
+ - Add :class:`factory.django.FileField` and :class:`factory.django.ImageField` hooks for
+ related Django model fields (:issue:`52`)
+
+*Bugfix*
+
+ - Properly handle non-integer pks in :class:`~factory.django.DjangoModelFactory` (:issue:`57`).
+ - Disable :class:`~factory.RelatedFactory` generation when a specific value was
+ passed (:issue:`62`, thanks to `Gabe Koscky <https://github.com/dhekke>`_)
+
+*Deprecation:*
+
+ - Rename :class:`~factory.RelatedFactory`'s ``name`` argument to ``factory_related_name`` (See :issue:`58`)
+
+
+.. _v2.0.2:
+
2.0.2 (2013-04-16)
------------------
*New:*
- - When :attr:`~factory.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` is
+ - When :attr:`~factory.django.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` is
empty, use ``Model.objects.create()`` instead of ``Model.objects.get_or_create``.
+.. _v2.0.1:
+
2.0.1 (2013-04-16)
------------------
*New:*
- Don't push ``defaults`` to ``get_or_create`` when
- :attr:`~factory.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` is not set.
+ :attr:`~factory.django.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE` is not set.
+.. _v2.0.0:
+
2.0.0 (2013-04-15)
------------------
@@ -29,11 +144,11 @@ ChangeLog
- The default :attr:`~factory.Sequence.type` for :class:`~factory.Sequence` is now :obj:`int`
- Fields listed in :attr:`~factory.Factory.FACTORY_HIDDEN_ARGS` won't be passed to
the associated class' constructor
- - Add support for ``get_or_create`` in :class:`~factory.DjangoModelFactory`,
- through :attr:`~factory.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE`.
+ - Add support for ``get_or_create`` in :class:`~factory.django.DjangoModelFactory`,
+ through :attr:`~factory.django.DjangoModelFactory.FACTORY_DJANGO_GET_OR_CREATE`.
- Add support for :mod:`~factory.fuzzy` attribute definitions.
- The :class:`Sequence` counter can be overridden when calling a generating function
- - Add :class:`~factory.Dict` and :class:`~factory.List` declarations (Closes #18).
+ - Add :class:`~factory.Dict` and :class:`~factory.List` declarations (Closes :issue:`18`).
*Removed:*
@@ -46,6 +161,8 @@ ChangeLog
- Remove :meth:`~factory.Factory.set_building_function` / :meth:`~factory.Factory.set_creation_function`
+.. _v1.3.0:
+
1.3.0 (2013-03-11)
------------------
@@ -60,7 +177,7 @@ New
- **Global:**
- Rewrite the whole documentation
- - Provide a dedicated :class:`~factory.MogoFactory` subclass of :class:`~factory.Factory`
+ - Provide a dedicated :class:`~factory.mogo.MogoFactory` subclass of :class:`~factory.Factory`
- **The Factory class:**
- Better creation/building customization hooks at :meth:`factory.Factory._build` and :meth:`factory.Factory.create`
@@ -77,7 +194,7 @@ New
its :attr:`~factory.Iterator.cycle` argument to ``False``
- Allow overriding default arguments in a :class:`~factory.PostGenerationMethodCall`
when generating an instance of the factory
- - An object created by a :class:`~factory.DjangoModelFactory` will be saved
+ - An object created by a :class:`~factory.django.DjangoModelFactory` will be saved
again after :class:`~factory.PostGeneration` hooks execution
@@ -112,7 +229,7 @@ In order to upgrade client code, apply the following rules:
:class:`~factory.Factory`, instead of relying on automagic associated class
discovery
- When using factory_boy for Django models, have each factory inherit from
- :class:`~factory.DjangoModelFactory`
+ :class:`~factory.django.DjangoModelFactory`
- Replace ``factory.CircularSubFactory('some.module', 'Symbol')`` with
``factory.SubFactory('some.module.Symbol')``
- Replace ``factory.InfiniteIterator(iterable)`` with ``factory.Iterator(iterable)``
@@ -124,6 +241,8 @@ In order to upgrade client code, apply the following rules:
+.. _v1.2.0:
+
1.2.0 (2012-09-08)
------------------
@@ -131,6 +250,9 @@ In order to upgrade client code, apply the following rules:
- Add :class:`~factory.CircularSubFactory` to solve circular dependencies between factories
+
+.. _v1.1.5:
+
1.1.5 (2012-07-09)
------------------
@@ -138,6 +260,9 @@ In order to upgrade client code, apply the following rules:
- Fix :class:`~factory.PostGenerationDeclaration` and derived classes.
+
+.. _v1.1.4:
+
1.1.4 (2012-06-19)
------------------
@@ -149,6 +274,9 @@ In order to upgrade client code, apply the following rules:
- Introduce :class:`~factory.PostGeneration` and
:class:`~factory.RelatedFactory`
+
+.. _v1.1.3:
+
1.1.3 (2012-03-09)
------------------
@@ -156,6 +284,9 @@ In order to upgrade client code, apply the following rules:
- Fix packaging rules
+
+.. _v1.1.2:
+
1.1.2 (2012-02-25)
------------------
@@ -165,6 +296,9 @@ In order to upgrade client code, apply the following rules:
- Provide :func:`~factory.Factory.generate` and :func:`~factory.Factory.simple_generate`, that allow specifying the instantiation strategy directly.
Also provides :func:`~factory.Factory.generate_batch` and :func:`~factory.Factory.simple_generate_batch`.
+
+.. _v1.1.1:
+
1.1.1 (2012-02-24)
------------------
@@ -172,6 +306,9 @@ In order to upgrade client code, apply the following rules:
- Add :func:`~factory.Factory.build_batch`, :func:`~factory.Factory.create_batch` and :func:`~factory.Factory.stub_batch`, to instantiate factories in batch
+
+.. _v1.1.0:
+
1.1.0 (2012-02-24)
------------------
@@ -190,6 +327,9 @@ In order to upgrade client code, apply the following rules:
- Auto-discovery of :attr:`~factory.Factory.FACTORY_FOR` based on class name is now deprecated
+
+.. _v1.0.4:
+
1.0.4 (2011-12-21)
------------------
@@ -201,7 +341,7 @@ In order to upgrade client code, apply the following rules:
- Introduce :data:`~factory.MOGO_BUILD` build function
- Add support for inheriting from multiple :class:`~factory.Factory`
- Base :class:`~factory.Factory` classes can now be declared :attr:`abstract <factory.Factory.ABSTRACT_FACTORY>`.
- - Provide :class:`~factory.DjangoModelFactory`, whose :class:`~factory.Sequence` counter starts at the next free database id
+ - Provide :class:`~factory.django.DjangoModelFactory`, whose :class:`~factory.Sequence` counter starts at the next free database id
- Introduce :class:`~factory.SelfAttribute`, a shortcut for ``factory.LazyAttribute(lambda o: o.foo.bar.baz``.
*Bugfix:*
@@ -210,6 +350,9 @@ In order to upgrade client code, apply the following rules:
- Share sequence counter between parent and subclasses
- Fix :class:`~factory.SubFactory` / :class:`~factory.Sequence` interferences
+
+.. _v1.0.2:
+
1.0.2 (2011-05-16)
------------------
@@ -217,6 +360,9 @@ In order to upgrade client code, apply the following rules:
- Introduce :class:`~factory.SubFactory`
+
+.. _v1.0.1:
+
1.0.1 (2011-05-13)
------------------
@@ -229,6 +375,9 @@ In order to upgrade client code, apply the following rules:
- Fix concurrency between :class:`~factory.LazyAttribute` and :class:`~factory.Sequence`
+
+.. _v1.0.0:
+
1.0.0 (2010-08-22)
------------------
diff --git a/docs/conf.py b/docs/conf.py
index 0ccaf29..4f76d45 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -26,7 +26,16 @@ sys.path.insert(0, os.path.dirname(os.path.abspath('.')))
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode']
+extensions = [
+ 'sphinx.ext.autodoc',
+ 'sphinx.ext.extlinks',
+ 'sphinx.ext.intersphinx',
+ 'sphinx.ext.viewcode',
+]
+
+extlinks = {
+ 'issue': ('https://github.com/rbarrois/factory_boy/issues/%s', 'issue #'),
+}
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -237,4 +246,8 @@ intersphinx_mapping = {
'http://docs.djangoproject.com/en/dev/',
'http://docs.djangoproject.com/en/dev/_objects/',
),
+ 'sqlalchemy': (
+ 'http://docs.sqlalchemy.org/en/rel_0_8/',
+ 'http://docs.sqlalchemy.org/en/rel_0_8/objects.inv',
+ ),
}
diff --git a/docs/fuzzy.rst b/docs/fuzzy.rst
index f1f4085..b94dfa5 100644
--- a/docs/fuzzy.rst
+++ b/docs/fuzzy.rst
@@ -24,6 +24,35 @@ FuzzyAttribute
The callable that generates random values
+FuzzyText
+---------
+
+
+.. class:: FuzzyText(length=12, chars=string.ascii_letters, prefix='')
+
+ The :class:`FuzzyText` fuzzer yields random strings beginning with
+ the given :attr:`prefix`, followed by :attr:`length` charactes chosen
+ from the :attr:`chars` character set,
+ and ending with the given :attr:`suffix`.
+
+ .. attribute:: length
+
+ int, the length of the random part
+
+ .. attribute:: prefix
+
+ text, an optional prefix to prepend to the random part
+
+ .. attribute:: suffix
+
+ text, an optional suffix to append to the random part
+
+ .. attribute:: chars
+
+ char iterable, the chars to choose from; defaults to the list of ascii
+ letters and numbers.
+
+
FuzzyChoice
-----------
@@ -69,6 +98,197 @@ FuzzyInteger
int, the inclusive higher bound of generated integers
+FuzzyDecimal
+------------
+
+.. class:: FuzzyDecimal(low[, high])
+
+ The :class:`FuzzyDecimal` fuzzer generates random integers within a given
+ inclusive range.
+
+ The :attr:`low` bound may be omitted, in which case it defaults to 0:
+
+ .. code-block:: pycon
+
+ >>> FuzzyDecimal(0.5, 42.7)
+ >>> fi.low, fi.high
+ 0.5, 42.7
+
+ >>> fi = FuzzyDecimal(42.7)
+ >>> fi.low, fi.high
+ 0.0, 42.7
+
+ >>> fi = FuzzyDecimal(0.5, 42.7, 3)
+ >>> fi.low, fi.high, fi.precision
+ 0.5, 42.7, 3
+
+ .. attribute:: low
+
+ decimal, the inclusive lower bound of generated decimals
+
+ .. attribute:: high
+
+ decimal, the inclusive higher bound of generated decimals
+
+ .. attribute:: precision
+ int, the number of digits to generate after the dot. The default is 2 digits.
+
+
+FuzzyDate
+---------
+
+.. class:: FuzzyDate(start_date[, end_date])
+
+ The :class:`FuzzyDate` fuzzer generates random dates within a given
+ inclusive range.
+
+ The :attr:`end_date` bound may be omitted, in which case it defaults to the current date:
+
+ .. code-block:: pycon
+
+ >>> fd = FuzzyDate(datetime.date(2008, 1, 1))
+ >>> fd.start_date, fd.end_date
+ datetime.date(2008, 1, 1), datetime.date(2013, 4, 16)
+
+ .. attribute:: start_date
+
+ :class:`datetime.date`, the inclusive lower bound of generated dates
+
+ .. attribute:: end_date
+
+ :class:`datetime.date`, the inclusive higher bound of generated dates
+
+
+FuzzyDateTime
+-------------
+
+.. class:: FuzzyDateTime(start_dt[, end_dt], tz=UTC, force_year=None, force_month=None, force_day=None, force_hour=None, force_minute=None, force_second=None, force_microsecond=None)
+
+ The :class:`FuzzyDateTime` fuzzer generates random timezone-aware datetime within a given
+ inclusive range.
+
+ The :attr:`end_dt` bound may be omitted, in which case it defaults to ``datetime.datetime.now()``
+ localized into the UTC timezone.
+
+ .. code-block:: pycon
+
+ >>> fdt = FuzzyDateTime(datetime.datetime(2008, 1, 1, tzinfo=UTC))
+ >>> fdt.start_dt, fdt.end_dt
+ datetime.datetime(2008, 1, 1, tzinfo=UTC), datetime.datetime(2013, 4, 21, 19, 13, 32, 458487, tzinfo=UTC)
+
+
+ The ``force_XXX`` keyword arguments force the related value of generated datetimes:
+
+ .. code-block:: pycon
+
+ >>> fdt = FuzzyDateTime(datetime.datetime(2008, 1, 1, tzinfo=UTC), datetime.datetime(2009, 1, 1, tzinfo=UTC),
+ ... force_day=3, force_second=42)
+ >>> fdt.evaluate(2, None, False) # Actual code used by ``SomeFactory.build()``
+ datetime.datetime(2008, 5, 3, 12, 13, 42, 124848, tzinfo=UTC)
+
+
+ .. attribute:: start_dt
+
+ :class:`datetime.datetime`, the inclusive lower bound of generated datetimes
+
+ .. attribute:: end_dt
+
+ :class:`datetime.datetime`, the inclusive upper bound of generated datetimes
+
+
+ .. attribute:: force_year
+
+ int or None; if set, forces the :attr:`~datetime.datetime.year` of generated datetime.
+
+ .. attribute:: force_month
+
+ int or None; if set, forces the :attr:`~datetime.datetime.month` of generated datetime.
+
+ .. attribute:: force_day
+
+ int or None; if set, forces the :attr:`~datetime.datetime.day` of generated datetime.
+
+ .. attribute:: force_hour
+
+ int or None; if set, forces the :attr:`~datetime.datetime.hour` of generated datetime.
+
+ .. attribute:: force_minute
+
+ int or None; if set, forces the :attr:`~datetime.datetime.minute` of generated datetime.
+
+ .. attribute:: force_second
+
+ int or None; if set, forces the :attr:`~datetime.datetime.second` of generated datetime.
+
+ .. attribute:: force_microsecond
+
+ int or None; if set, forces the :attr:`~datetime.datetime.microsecond` of generated datetime.
+
+
+FuzzyNaiveDateTime
+------------------
+
+.. class:: FuzzyNaiveDateTime(start_dt[, end_dt], force_year=None, force_month=None, force_day=None, force_hour=None, force_minute=None, force_second=None, force_microsecond=None)
+
+ The :class:`FuzzyNaiveDateTime` fuzzer generates random naive datetime within a given
+ inclusive range.
+
+ The :attr:`end_dt` bound may be omitted, in which case it defaults to ``datetime.datetime.now()``:
+
+ .. code-block:: pycon
+
+ >>> fdt = FuzzyNaiveDateTime(datetime.datetime(2008, 1, 1))
+ >>> fdt.start_dt, fdt.end_dt
+ datetime.datetime(2008, 1, 1), datetime.datetime(2013, 4, 21, 19, 13, 32, 458487)
+
+
+ The ``force_XXX`` keyword arguments force the related value of generated datetimes:
+
+ .. code-block:: pycon
+
+ >>> fdt = FuzzyNaiveDateTime(datetime.datetime(2008, 1, 1), datetime.datetime(2009, 1, 1),
+ ... force_day=3, force_second=42)
+ >>> fdt.evaluate(2, None, False) # Actual code used by ``SomeFactory.build()``
+ datetime.datetime(2008, 5, 3, 12, 13, 42, 124848)
+
+
+ .. attribute:: start_dt
+
+ :class:`datetime.datetime`, the inclusive lower bound of generated datetimes
+
+ .. attribute:: end_dt
+
+ :class:`datetime.datetime`, the inclusive upper bound of generated datetimes
+
+
+ .. attribute:: force_year
+
+ int or None; if set, forces the :attr:`~datetime.datetime.year` of generated datetime.
+
+ .. attribute:: force_month
+
+ int or None; if set, forces the :attr:`~datetime.datetime.month` of generated datetime.
+
+ .. attribute:: force_day
+
+ int or None; if set, forces the :attr:`~datetime.datetime.day` of generated datetime.
+
+ .. attribute:: force_hour
+
+ int or None; if set, forces the :attr:`~datetime.datetime.hour` of generated datetime.
+
+ .. attribute:: force_minute
+
+ int or None; if set, forces the :attr:`~datetime.datetime.minute` of generated datetime.
+
+ .. attribute:: force_second
+
+ int or None; if set, forces the :attr:`~datetime.datetime.second` of generated datetime.
+
+ .. attribute:: force_microsecond
+
+ int or None; if set, forces the :attr:`~datetime.datetime.microsecond` of generated datetime.
+
Custom fuzzy fields
-------------------
diff --git a/docs/introduction.rst b/docs/introduction.rst
index 8bbb10c..86e2046 100644
--- a/docs/introduction.rst
+++ b/docs/introduction.rst
@@ -99,7 +99,7 @@ This is achieved with the :class:`~factory.Sequence` declaration:
>>> UserFactory()
<User: user2>
-.. note:: For more complex situations, you may also use the :meth:`~factory.@sequence` decorator:
+.. note:: For more complex situations, you may also use the :meth:`~factory.@sequence` decorator (note that ``self`` is not added as first parameter):
.. code-block:: python
@@ -107,7 +107,7 @@ This is achieved with the :class:`~factory.Sequence` declaration:
FACTORY_FOR = models.User
@factory.sequence
- def username(self, n):
+ def username(n):
return 'user%d' % n
@@ -140,7 +140,19 @@ taking the object being built and returning the value for the field:
<User: user3 (doe@example.com)>
-.. note:: As for :class:`~factory.Sequence`, a :meth:`~factory.@lazy_attribute` decorator is available.
+.. note:: As for :class:`~factory.Sequence`, a :meth:`~factory.@lazy_attribute` decorator is available:
+
+
+.. code-block:: python
+
+ class UserFactory(factory.Factory):
+ FACTORY_FOR = models.User
+
+ username = factory.Sequence(lambda n: 'user%d' % n)
+
+ @factory.lazy_attribute
+ def email(self):
+ return '%s@example.com' % self.username
Inheritance
@@ -227,7 +239,7 @@ All factories support two built-in strategies:
as for a Django model.
Starting from 2.0, :meth:`factory.Factory.create` simply calls ``AssociatedClass(**kwargs)``.
- You should use :class:`~factory.DjangoModelFactory` for Django models.
+ You should use :class:`~factory.django.DjangoModelFactory` for Django models.
When a :class:`~factory.Factory` includes related fields (:class:`~factory.SubFactory`, :class:`~factory.RelatedFactory`),
@@ -253,6 +265,6 @@ Calling a :class:`~factory.Factory` subclass will provide an object through the
<MyClass: X (saved)>
-The default strategy can ba changed by setting the class-level :attr:`~factory.Factory.FACTROY_STRATEGY` attribute.
+The default strategy can be changed by setting the class-level :attr:`~factory.Factory.FACTORY_STRATEGY` attribute.
diff --git a/docs/orms.rst b/docs/orms.rst
index 8e5b6f6..e50e706 100644
--- a/docs/orms.rst
+++ b/docs/orms.rst
@@ -11,6 +11,8 @@ adding dedicated features.
Django
------
+.. currentmodule:: factory.django
+
The first versions of factory_boy were designed specifically for Django,
but the library has now evolved to be framework-independant.
@@ -24,14 +26,16 @@ All factories for a Django :class:`~django.db.models.Model` should use the
:class:`DjangoModelFactory` base class.
-.. class:: DjangoModelFactory(Factory)
+.. class:: DjangoModelFactory(factory.Factory)
Dedicated class for Django :class:`~django.db.models.Model` factories.
This class provides the following features:
- * :func:`~Factory.create()` uses :meth:`Model.objects.create() <django.db.models.query.QuerySet.create>`
- * :func:`~Factory._setup_next_sequence()` selects the next unused primary key value
+ * The :attr:`~factory.Factory.FACTORY_FOR` attribute also supports the ``'app.Model'``
+ syntax
+ * :func:`~factory.Factory.create()` uses :meth:`Model.objects.create() <django.db.models.query.QuerySet.create>`
+ * :func:`~factory.Factory._setup_next_sequence()` selects the next unused primary key value
* When using :class:`~factory.RelatedFactory` or :class:`~factory.PostGeneration`
attributes, the base object will be :meth:`saved <django.db.models.Model.save>`
once all post-generation hooks have run.
@@ -44,8 +48,8 @@ All factories for a Django :class:`~django.db.models.Model` should use the
.. code-block:: python
- class UserFactory(factory.DjangoModelFactory):
- FACTORY_FOR = models.User
+ class UserFactory(factory.django.DjangoModelFactory):
+ FACTORY_FOR = 'myapp.User' # Equivalent to ``FACTORY_FOR = myapp.models.User``
FACTORY_DJANGO_GET_OR_CREATE = ('username',)
username = 'john'
@@ -68,3 +72,201 @@ All factories for a Django :class:`~django.db.models.Model` should use the
<User: jack>
>>> User.objects.all()
[<User: john>, <User: jack>]
+
+
+.. note:: If a :class:`DjangoModelFactory` relates to an :obj:`~django.db.models.Options.abstract`
+ model, be sure to declare the :class:`DjangoModelFactory` as abstract:
+
+ .. code-block:: python
+
+ class MyAbstractModelFactory(factory.django.DjangoModelFactory):
+ FACTORY_FOR = models.MyAbstractModel
+ ABSTRACT_FACTORY = True
+
+ class MyConcreteModelFactory(MyAbstractModelFactory):
+ FACTORY_FOR = models.MyConcreteModel
+
+ Otherwise, factory_boy will try to get the 'next PK' counter from the abstract model.
+
+
+.. class:: FileField
+
+ Custom declarations for :class:`django.db.models.FileField`
+
+ .. method:: __init__(self, from_path='', from_file='', data=b'', filename='example.dat')
+
+ :param str from_path: Use data from the file located at ``from_path``,
+ and keep its filename
+ :param file from_file: Use the contents of the provided file object; use its filename
+ if available
+ :param bytes data: Use the provided bytes as file contents
+ :param str filename: The filename for the FileField
+
+.. note:: If the value ``None`` was passed for the :class:`FileField` field, this will
+ disable field generation:
+
+.. code-block:: python
+
+ class MyFactory(factory.django.DjangoModelFactory):
+ FACTORY_FOR = models.MyModel
+
+ the_file = factory.django.FileField(filename='the_file.dat')
+
+.. code-block:: pycon
+
+ >>> MyFactory(the_file__data=b'uhuh').the_file.read()
+ b'uhuh'
+ >>> MyFactory(the_file=None).the_file
+ None
+
+
+.. class:: ImageField
+
+ Custom declarations for :class:`django.db.models.ImageField`
+
+ .. method:: __init__(self, from_path='', from_file='', filename='example.jpg', width=100, height=100, color='green', format='JPEG')
+
+ :param str from_path: Use data from the file located at ``from_path``,
+ and keep its filename
+ :param file from_file: Use the contents of the provided file object; use its filename
+ if available
+ :param str filename: The filename for the ImageField
+ :param int width: The width of the generated image (default: ``100``)
+ :param int height: The height of the generated image (default: ``100``)
+ :param str color: The color of the generated image (default: ``'green'``)
+ :param str format: The image format (as supported by PIL) (default: ``'JPEG'``)
+
+.. note:: If the value ``None`` was passed for the :class:`FileField` field, this will
+ disable field generation:
+
+.. note:: Just as Django's :class:`django.db.models.ImageField` requires the
+ Python Imaging Library, this :class:`ImageField` requires it too.
+
+.. code-block:: python
+
+ class MyFactory(factory.django.DjangoModelFactory):
+ FACTORY_FOR = models.MyModel
+
+ the_image = factory.django.ImageField(color='blue')
+
+.. code-block:: pycon
+
+ >>> MyFactory(the_image__width=42).the_image.width
+ 42
+ >>> MyFactory(the_image=None).the_image
+ None
+
+
+Mogo
+----
+
+.. currentmodule:: factory.mogo
+
+factory_boy supports `Mogo`_-style models, through the :class:`MogoFactory` class.
+
+`Mogo`_ is a wrapper around the ``pymongo`` library for MongoDB.
+
+.. _Mogo: https://github.com/joshmarshall/mogo
+
+.. class:: MogoFactory(factory.Factory)
+
+ Dedicated class for `Mogo`_ models.
+
+ This class provides the following features:
+
+ * :func:`~factory.Factory.build()` calls a model's ``new()`` method
+ * :func:`~factory.Factory.create()` builds an instance through ``new()`` then
+ saves it.
+
+
+MongoEngine
+-----------
+
+.. currentmodule:: factory.mongoengine
+
+factory_boy supports `MongoEngine`_-style models, through the :class:`MongoEngineFactory` class.
+
+`mongoengine`_ is a wrapper around the ``pymongo`` library for MongoDB.
+
+.. _mongoengine: http://mongoengine.org/
+
+.. class:: MongoEngineFactory(factory.Factory)
+
+ Dedicated class for `MongoEngine`_ models.
+
+ This class provides the following features:
+
+ * :func:`~factory.Factory.build()` calls a model's ``__init__`` method
+ * :func:`~factory.Factory.create()` builds an instance through ``__init__`` then
+ saves it.
+
+ .. note:: If the :attr:`associated class <factory.Factory.FACTORY_FOR>` 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.
+
+
+SQLAlchemy
+----------
+
+.. currentmodule:: factory.alchemy
+
+
+Factoy_boy also supports `SQLAlchemy`_ models through the :class:`SQLAlchemyModelFactory` class.
+
+To work, this class needs an `SQLAlchemy`_ session object affected to "FACTORY_SESSION" class attribute.
+
+.. _SQLAlchemy: http://www.sqlalchemy.org/
+
+.. class:: SQLAlchemyModelFactory(factory.Factory)
+
+ Dedicated class for `SQLAlchemy`_ models.
+
+ This class provides the following features:
+
+ * :func:`~factory.Factory.create()` uses :meth:`sqlalchemy.orm.session.Session.add`
+ * :func:`~factory.Factory._setup_next_sequence()` selects the next unused primary key value
+
+ .. attribute:: FACTORY_SESSION
+
+ Fields whose SQLAlchemy session object are passed will be used to communicate with the database
+
+A (very) simple exemple:
+
+.. code-block:: python
+
+ from sqlalchemy import Column, Integer, Unicode, create_engine
+ from sqlalchemy.ext.declarative import declarative_base
+ from sqlalchemy.orm import scoped_session, sessionmaker
+
+ session = scoped_session(sessionmaker())
+ engine = create_engine('sqlite://')
+ session.configure(bind=engine)
+ Base = declarative_base()
+
+
+ class User(Base):
+ """ A SQLAlchemy simple model class who represents a user """
+ __tablename__ = 'UserTable'
+
+ id = Column(Integer(), primary_key=True)
+ name = Column(Unicode(20))
+
+ Base.metadata.create_all(engine)
+
+
+ class UserFactory(SQLAlchemyModelFactory):
+ FACTORY_FOR = User
+ FACTORY_SESSION = session # the SQLAlchemy session object
+
+ id = factory.Sequence(lambda n: n)
+ name = factory.Sequence(lambda n: u'User %d' % n)
+
+.. code-block:: pycon
+
+ >>> session.query(User).all()
+ []
+ >>> UserFactory()
+ <User: User 1>
+ >>> session.query(User).all()
+ [<User: User 1>]
diff --git a/docs/recipes.rst b/docs/recipes.rst
index e226732..c1f3700 100644
--- a/docs/recipes.rst
+++ b/docs/recipes.rst
@@ -25,7 +25,7 @@ use the :class:`~factory.SubFactory` declaration:
import factory
from . import models
- class UserFactory(factory.DjangoModelFactory):
+ class UserFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.User
first_name = factory.Sequence(lambda n: "Agent %03d" % n)
@@ -52,7 +52,7 @@ use a :class:`~factory.RelatedFactory` declaration:
# factories.py
- class UserFactory(factory.DjangoModelFactory):
+ class UserFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.User
log = factory.RelatedFactory(UserLogFactory, 'user', action=models.UserLog.ACTION_CREATE)
@@ -62,6 +62,65 @@ When a :class:`UserFactory` is instantiated, factory_boy will call
``UserLogFactory(user=that_user, action=...)`` just before returning the created ``User``.
+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``.
+
+A typical way to create those profiles was to hook a post-save signal to the ``User`` model.
+
+factory_boy allows to define attributes of such profiles dynamically when creating a ``User``:
+
+.. code-block:: python
+
+ class ProfileFactory(factory.django.DjangoModelFactory):
+ FACTORY_FOR = my_models.Profile
+
+ title = 'Dr'
+ # We pass in profile=None to prevent UserFactory from creating another profile
+ # (this disables the RelatedFactory)
+ user = factory.SubFactory('app.factories.UserFactory', profile=None)
+
+ class UserFactory(factory.django.DjangoModelFactory):
+ FACTORY_FOR = auth_models.User
+
+ username = factory.Sequence(lambda n: "user_%d" % n)
+
+ # We pass in 'user' to link the generated Profile to our just-generated User
+ # This will call ProfileFactory(user=our_new_user), thus skipping the SubFactory.
+ profile = factory.RelatedFactory(ProfileFactory, 'user')
+
+ @classmethod
+ def _generate(cls, create, attrs):
+ """Override the default _generate() to disable the post-save signal."""
+
+ # Note: If the signal was defined with a dispatch_uid, include that in both calls.
+ post_save.disconnect(handler_create_user_profile, auth_models.User)
+ user = super(UserFactory, cls)._generate(create, attrs)
+ post_save.connect(handler_create_user_profile, auth_models.User)
+ return user
+
+.. OHAI_VIM:*
+
+
+.. code-block:: pycon
+
+ >>> u = UserFactory(profile__title=u"Lord")
+ >>> u.get_profile().title
+ u"Lord"
+
+Such behaviour can be extended to other situations where a signal interferes with
+factory_boy related factories.
+
+.. note:: When any :class:`~factory.RelatedFactory` or :class:`~factory.post_generation`
+ attribute is defined on the :class:`~factory.django.DjangoModelFactory` subclass,
+ a second ``save()`` is performed *after* the call to ``_create()``.
+
+ Code working with signals should thus override the :meth:`~factory.Factory._generate`
+ method.
+
+
Simple ManyToMany
-----------------
@@ -85,12 +144,12 @@ hook:
# factories.py
- class GroupFactory(factory.DjangoModelFactory):
+ class GroupFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.Group
name = factory.Sequence(lambda n: "Group #%s" % n)
- class UserFactory(factory.DjangoModelFactory):
+ class UserFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.User
name = "John Doe"
@@ -140,17 +199,17 @@ If more links are needed, simply add more :class:`RelatedFactory` declarations:
# factories.py
- class UserFactory(factory.DjangoModelFactory):
+ class UserFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.User
name = "John Doe"
- class GroupFactory(factory.DjangoModelFactory):
+ class GroupFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.Group
name = "Admins"
- class GroupLevelFactory(factory.DjangoModelFactory):
+ class GroupLevelFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.GroupLevel
user = factory.SubFactory(UserFactory)
@@ -213,20 +272,20 @@ Here, we want:
.. code-block:: python
# factories.py
- class CountryFactory(factory.DjangoModelFactory):
+ class CountryFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.Country
name = factory.Iterator(["France", "Italy", "Spain"])
lang = factory.Iterator(['fr', 'it', 'es'])
- class UserFactory(factory.DjangoModelFactory):
+ class UserFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.User
name = "John"
lang = factory.SelfAttribute('country.lang')
country = factory.SubFactory(CountryFactory)
- class CompanyFactory(factory.DjangoModelFactory):
+ class CompanyFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.Company
name = "ACME, Inc."
diff --git a/docs/reference.rst b/docs/reference.rst
index 81aa645..53584a0 100644
--- a/docs/reference.rst
+++ b/docs/reference.rst
@@ -19,15 +19,18 @@ The :class:`Factory` class
.. attribute:: FACTORY_FOR
- This required attribute describes the class of objects to generate.
- It may only be absent if the factory has been marked abstract through
- :attr:`ABSTRACT_FACTORY`.
+ This optional attribute describes the class of objects to generate.
+
+ If unset, it will be inherited from parent :class:`Factory` subclasses.
.. attribute:: ABSTRACT_FACTORY
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.
+
.. attribute:: FACTORY_ARG_PARAMETERS
Some factories require non-keyword arguments to their :meth:`~object.__init__`.
@@ -211,7 +214,7 @@ The :class:`Factory` class
.. code-block:: python
class BaseBackendFactory(factory.Factory):
- ABSTRACT_FACTORY = True
+ ABSTRACT_FACTORY = True # Optional
def _create(cls, target_class, *args, **kwargs):
obj = target_class(*args, **kwargs)
@@ -234,6 +237,48 @@ The :class:`Factory` class
values, for instance.
+ **Advanced functions:**
+
+
+ .. classmethod:: reset_sequence(cls, value=None, force=False)
+
+ :arg int value: The value to reset the sequence to
+ :arg bool force: Whether to force-reset the sequence
+
+ Allows to reset the sequence counter for a :class:`~factory.Factory`.
+ The new value can be passed in as the ``value`` argument:
+
+ .. code-block:: pycon
+
+ >>> SomeFactory.reset_sequence(4)
+ >>> SomeFactory._next_sequence
+ 4
+
+ Since subclasses of a non-:attr:`abstract <factory.Factory.ABSTRACT_FACTORY>`
+ :class:`~factory.Factory` share the same sequence counter, special care needs
+ to be taken when resetting the counter of such a subclass.
+
+ By default, :meth:`reset_sequence` will raise a :exc:`ValueError` when
+ called on a subclassed :class:`~factory.Factory` subclass. This can be
+ avoided by passing in the ``force=True`` flag:
+
+ .. code-block:: pycon
+
+ >>> InheritedFactory.reset_sequence()
+ Traceback (most recent call last):
+ File "factory_boy/tests/test_base.py", line 179, in test_reset_sequence_subclass_parent
+ SubTestObjectFactory.reset_sequence()
+ File "factory_boy/factory/base.py", line 250, in reset_sequence
+ "Cannot reset the sequence of a factory subclass. "
+ ValueError: Cannot reset the sequence of a factory subclass. Please call reset_sequence() on the root factory, or call reset_sequence(forward=True).
+
+ >>> InheritedFactory.reset_sequence(force=True)
+ >>>
+
+ This is equivalent to calling :meth:`reset_sequence` on the base
+ factory in the chain.
+
+
.. _strategies:
Strategies
@@ -318,6 +363,38 @@ factory_boy supports two main strategies for generating instances, plus stubs.
with a default strategy set to :data:`STUB_STRATEGY`.
+.. function:: debug(logger='factory', stream=None)
+
+ :param str logger: The name of the logger to enable debug for
+ :param file stream: The stream to send debug output to, defaults to :obj:`sys.stderr`
+
+ Context manager to help debugging factory_boy behavior.
+ It will temporarily put the target logger (e.g ``'factory'``) in debug mode,
+ sending all output to :obj`~sys.stderr`;
+ upon leaving the context, the logging levels are reset.
+
+ A typical use case is to understand what happens during a single factory call:
+
+ .. code-block:: python
+
+ with factory.debug():
+ obj = TestModel2Factory()
+
+ This will yield messages similar to those (artificial indentation):
+
+ .. code-block:: ini
+
+ BaseFactory: Preparing tests.test_using.TestModel2Factory(extra={})
+ LazyStub: Computing values for tests.test_using.TestModel2Factory(two=<OrderedDeclarationWrapper for <factory.declarations.SubFactory object at 0x1e15610>>)
+ SubFactory: Instantiating tests.test_using.TestModelFactory(__containers=(<LazyStub for tests.test_using.TestModel2Factory>,), one=4), create=True
+ BaseFactory: Preparing tests.test_using.TestModelFactory(extra={'__containers': (<LazyStub for tests.test_using.TestModel2Factory>,), 'one': 4})
+ LazyStub: Computing values for tests.test_using.TestModelFactory(one=4)
+ LazyStub: Computed values, got tests.test_using.TestModelFactory(one=4)
+ BaseFactory: Generating tests.test_using.TestModelFactory(one=4)
+ LazyStub: Computed values, got tests.test_using.TestModel2Factory(two=<tests.test_using.TestModel object at 0x1e15410>)
+ BaseFactory: Generating tests.test_using.TestModel2Factory(two=<tests.test_using.TestModel object at 0x1e15410>)
+
+
.. _declarations:
Declarations
@@ -353,6 +430,11 @@ accept the object being built as sole argument, and return a value.
'leo@example.com'
+The object passed to :class:`LazyAttribute` is not an instance of the target class,
+but instead a :class:`~containers.LazyStub`: a temporary container that computes
+the value of all declared fields.
+
+
Decorator
~~~~~~~~~
@@ -586,7 +668,7 @@ handles more complex cases:
SubFactory
""""""""""
-.. class:: SubFactory(sub_factory, **kwargs)
+.. class:: SubFactory(factory, **kwargs)
.. OHAI_VIM**
@@ -601,6 +683,20 @@ The :class:`SubFactory` attribute should be called with:
factory
+.. note::
+
+ When passing an actual :class:`~factory.Factory` for the
+ :attr:`~factory.SubFactory.factory` argument, make sure to pass
+ the class and not instance (i.e no ``()`` after the class):
+
+ .. code-block:: python
+
+ class FooFactory(factory.Factory):
+ FACTORY_FOR = Foo
+
+ bar = factory.SubFactory(BarFactory) # Not BarFactory()
+
+
Definition
~~~~~~~~~~
@@ -786,6 +882,19 @@ Obviously, this "follow parents" hability also handles overriding some attribute
'cn'
+This feature is also available to :class:`LazyAttribute` and :class:`LazyAttributeSequence`,
+through the :attr:`~containers.LazyStub.factory_parent` attribute of the passed-in object:
+
+.. code-block:: python
+
+ class CompanyFactory(factory.Factory):
+ FACTORY_FOR = Company
+ country = factory.SubFactory(CountryFactory)
+ owner = factory.SubFactory(UserFactory,
+ language=factory.LazyAttribute(lambda user: user.factory_parent.country.language),
+ )
+
+
Iterator
""""""""
@@ -810,6 +919,14 @@ Iterator
.. versionadded:: 1.3.0
+ .. method:: reset()
+
+ Reset the internal iterator used by the attribute, so that the next value
+ will be the first value generated by the iterator.
+
+ May be called several times.
+
+
Each call to the factory will receive the next value from the iterable:
.. code-block:: python
@@ -879,6 +996,24 @@ use the :func:`iterator` decorator:
yield line
+Resetting
+~~~~~~~~~
+
+In order to start back at the first value in an :class:`Iterator`,
+simply call the :meth:`~Iterator.reset` method of that attribute
+(accessing it from the bare :class:`~Factory` subclass):
+
+.. code-block:: pycon
+
+ >>> UserFactory().lang
+ 'en'
+ >>> UserFactory().lang
+ 'fr'
+ >>> UserFactory.lang.reset()
+ >>> UserFactory().lang
+ 'en'
+
+
Dict and List
"""""""""""""
@@ -1013,7 +1148,7 @@ as keyword arguments; ``{'post_x': 2}`` will be passed to ``SomeFactory.FACTORY_
RelatedFactory
""""""""""""""
-.. class:: RelatedFactory(factory, name='', **kwargs)
+.. class:: RelatedFactory(factory, factory_related_name='', **kwargs)
.. OHAI_VIM**
@@ -1033,13 +1168,27 @@ RelatedFactory
.. attribute:: name
The generated object (where the :class:`RelatedFactory` attribute will
- set) may be passed to the related factory if the :attr:`name` parameter
+ set) may be passed to the related factory if the :attr:`factory_related_name` parameter
is set.
It will be passed as a keyword argument, using the :attr:`name` value as
keyword:
+.. note::
+
+ When passing an actual :class:`~factory.Factory` for the
+ :attr:`~factory.RelatedFactory.factory` argument, make sure to pass
+ the class and not instance (i.e no ``()`` after the class):
+
+ .. code-block:: python
+
+ class FooFactory(factory.Factory):
+ FACTORY_FOR = Foo
+
+ bar = factory.RelatedFactory(BarFactory) # Not BarFactory()
+
+
.. code-block:: python
class CityFactory(factory.Factory):
@@ -1069,6 +1218,22 @@ Extra kwargs may be passed to the related factory, through the usual ``ATTR__SUB
>>> City.objects.get(capital_of=england)
<City: London>
+If a value if passed for the :class:`RelatedFactory` attribute, this disables
+:class:`RelatedFactory` generation:
+
+.. code-block:: pycon
+
+ >>> france = CountryFactory()
+ >>> paris = City.objects.get()
+ >>> paris
+ <City: Paris>
+ >>> reunion = CountryFactory(capital_city=paris)
+ >>> City.objects.count() # No new capital_city generated
+ 1
+ >>> guyane = CountryFactory(capital_city=paris, capital_city__name='Kourou')
+ >>> City.objects.count() # No new capital_city generated, ``name`` ignored.
+ 1
+
PostGeneration
""""""""""""""
@@ -1224,7 +1389,7 @@ factory during instantiation.
.. code-block:: python
- class UserFactory(factory.DjangoModelFactory):
+ class UserFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = User
username = 'user'
@@ -1315,12 +1480,12 @@ Lightweight factory declaration
UserFactory = make_factory(models.User,
login='john',
email=factory.LazyAttribute(lambda u: '%s@example.com' % u.login),
- FACTORY_CLASS=factory.DjangoModelFactory,
+ FACTORY_CLASS=factory.django.DjangoModelFactory,
)
# This is equivalent to:
- class UserFactory(factory.DjangoModelFactory):
+ class UserFactory(factory.django.DjangoModelFactory):
FACTORY_FOR = models.User
login = 'john'