diff options
author | Thomas Goirand <thomas@goirand.fr> | 2014-05-03 22:57:46 +0800 |
---|---|---|
committer | Thomas Goirand <thomas@goirand.fr> | 2014-05-03 22:57:46 +0800 |
commit | 1073f2ae50fb0999712b6744b082f7424e4490c3 (patch) | |
tree | 26943f3545ce1fc1ae54e0398fbbd78df641f54d /docs | |
parent | c9f01d77941527b62ca67b1064bd3fb849b6a064 (diff) | |
parent | 90db123ada9739a19f3b408b50e006700923f651 (diff) | |
download | factory-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.rst | 167 | ||||
-rw-r--r-- | docs/conf.py | 15 | ||||
-rw-r--r-- | docs/fuzzy.rst | 220 | ||||
-rw-r--r-- | docs/introduction.rst | 22 | ||||
-rw-r--r-- | docs/orms.rst | 212 | ||||
-rw-r--r-- | docs/recipes.rst | 79 | ||||
-rw-r--r-- | docs/reference.rst | 185 |
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' |