diff options
author | Raphaël Barrois <raphael.barrois@polytechnique.org> | 2013-02-11 01:33:51 +0100 |
---|---|---|
committer | Raphaël Barrois <raphael.barrois@polytechnique.org> | 2013-03-03 18:36:09 +0100 |
commit | 5a1d9464543bcd7fdbeed1075d4461f213bede05 (patch) | |
tree | 2c5bce3c833c4d887d48b6ca31cf7368feb9c154 | |
parent | 853ecc70f0e1772b607554e98a714675c1dc5821 (diff) | |
download | factory-boy-5a1d9464543bcd7fdbeed1075d4461f213bede05.tar factory-boy-5a1d9464543bcd7fdbeed1075d4461f213bede05.tar.gz |
Rewrite the whole documentation.
Signed-off-by: Raphaël Barrois <raphael.barrois@polytechnique.org>
-rw-r--r-- | README | 186 | ||||
-rw-r--r-- | docs/changelog.rst | 7 | ||||
-rw-r--r-- | docs/conf.py | 10 | ||||
-rw-r--r-- | docs/examples.rst | 28 | ||||
-rw-r--r-- | docs/ideas.rst | 11 | ||||
-rw-r--r-- | docs/internals.rst | 2 | ||||
-rw-r--r-- | docs/introduction.rst | 258 | ||||
-rw-r--r-- | docs/orms.rst | 36 | ||||
-rw-r--r-- | docs/post_generation.rst | 91 | ||||
-rw-r--r-- | docs/recipes.rst | 62 | ||||
-rw-r--r-- | docs/reference.rst | 1027 | ||||
-rw-r--r-- | docs/subfactory.rst | 81 |
12 files changed, 1470 insertions, 329 deletions
@@ -36,8 +36,16 @@ Source: http://github.com/rbarrois/factory_boy/ $ python setup.py install +Usage +----- + + +.. note:: This section provides a quick summary of factory_boy features. + A more detailed listing is available in the full documentation. + + 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: @@ -63,7 +71,7 @@ Factories declare a set of attributes used to instantiate an object. The class o Using factories ---------------- +""""""""""""""" factory_boy supports several different build strategies: build, create, attributes and stub: @@ -98,9 +106,13 @@ No matter which strategy is used, it's possible to override the defined attribut Lazy Attributes ---------------- +""""""""""""""" + +Most factory attributes can be added using static values that are evaluated when the factory is defined, +but some attributes (such as fields whose value is computed from other elements) +will need values assigned each time an instance is generated. -Most factory attributes can be added using static values that are evaluated when the factory is defined, but some attributes (such as associations and other attributes that must be dynamically generated) will need values assigned each time an instance is generated. These "lazy" attributes can be added as follows: +These "lazy" attributes can be added as follows: .. code-block:: python @@ -116,25 +128,28 @@ Most factory attributes can be added using static values that are evaluated when "joe.blow@example.com" -The function passed to ``LazyAttribute`` is given the attributes defined for the factory up to the point of the LazyAttribute declaration. If a lambda won't cut it, the ``lazy_attribute`` decorator can be used to wrap a function: +Sequences +""""""""" + +Unique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by using ``Sequence`` or the decorator ``sequence``: .. code-block:: python - # Stub factories don't have an associated class. - class SumFactory(factory.StubFactory): - lhs = 1 - rhs = 1 + class UserFactory(factory.Factory): + FACTORY_FOR = models.User + email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n)) - @lazy_attribute - def sum(a): - result = a.lhs + a.rhs # Or some other fancy calculation - return result + >>> UserFactory().email + 'person0@example.com' + >>> UserFactory().email + 'person1@example.com' Associations ------------- +"""""""""""" -Associated instances can also be generated using ``SubFactory``: +Some objects have a complex field, that should itself be defined from a dedicated factories. +This is handled by the ``SubFactory`` helper: .. code-block:: python @@ -163,137 +178,8 @@ The associated object's strategy will be used: True -Inheritance ------------ - -You can easily create multiple factories for the same class without repeating common attributes by using inheritance: - -.. code-block:: python - - class PostFactory(factory.Factory): - FACTORY_FOR = models.Post - title = 'A title' - - class ApprovedPost(PostFactory): - # No need to replicate the FACTORY_FOR attribute - approved = True - approver = factory.SubFactory(UserFactory) - - -Sequences ---------- - -Unique values in a specific format (for example, e-mail addresses) can be generated using sequences. Sequences are defined by using ``Sequence`` or the decorator ``sequence``: - -.. code-block:: python - - class UserFactory(factory.Factory): - FACTORY_FOR = models.User - email = factory.Sequence(lambda n: 'person{0}@example.com'.format(n)) - - >>> UserFactory().email - 'person0@example.com' - >>> UserFactory().email - 'person1@example.com' - -Sequences can be combined with lazy attributes: - -.. code-block:: python - - class UserFactory(factory.Factory): - FACTORY_FOR = models.User - - name = 'Mark' - email = factory.LazyAttributeSequence(lambda a, n: '{0}+{1}@example.com'.format(a.name, n).lower()) - - >>> UserFactory().email - 'mark+0@example.com' - -If you wish to use a custom method to set the initial ID for a sequence, you can override the ``_setup_next_sequence`` class method: - -.. code-block:: python - - class MyFactory(factory.Factory): - FACTORY_FOR = MyClass - - @classmethod - def _setup_next_sequence(cls): - return cls.FACTORY_FOR.objects.values_list('id').order_by('-id')[0] + 1 - - -Customizing creation --------------------- - -Sometimes, the default build/create by keyword arguments doesn't allow for enough -customization of the generated objects. In such cases, you should override the -Factory._prepare method: - -.. code-block:: python - - class UserFactory(factory.Factory): - @classmethod - def _prepare(cls, create, **kwargs): - password = kwargs.pop('password', None) - user = super(UserFactory, cls)._prepare(create, **kwargs) - if password: - user.set_password(password) - if create: - user.save() - return user - -.. OHAI VIM** - - -Subfactories ------------- - -If one of your factories has a field which is another factory, you can declare it as a ``SubFactory``. This allows to define attributes of that field when calling -the global factory, using a simple syntax : ``field__attr=42`` will set the attribute ``attr`` of the ``SubFactory`` defined in ``field`` to 42: - -.. code-block:: python - - class InnerFactory(factory.Factory): - FACTORY_FOR = InnerClass - foo = 'foo' - bar = factory.LazyAttribute(lambda o: foo * 2) - - class ExternalFactory(factory.Factory): - FACTORY_FOR = OuterClass - inner = factory.SubFactory(InnerFactory, foo='bar') - - >>> e = ExternalFactory() - >>> e.foo - 'bar' - >>> e.bar - 'barbar' - - >>> e2 : ExternalFactory(inner__bar='baz') - >>> e2.foo - 'bar' - >>> e2.bar - 'baz' - -Abstract factories ------------------- - -If a ``Factory`` simply defines generic attribute declarations without being bound to a given class, -it should be marked 'abstract' by declaring ``FACTORY_ABSTRACT = True``. -Such factories cannot be built/created/.... - -.. code-block:: python - - class AbstractFactory(factory.Factory): - FACTORY_ABSTRACT = True - foo = 'foo' - - >>> AbstractFactory() - Traceback (most recent call last): - ... - AttributeError: type object 'AbstractFactory' has no attribute '_associated_class' - - Contributing -============ +------------ factory_boy is distributed under the MIT License. @@ -315,15 +201,19 @@ In order to test coverage, please use: Contents, indices and tables -============================ +---------------------------- .. toctree:: :maxdepth: 2 + introduction + reference + orms + recipes examples - subfactory - post_generation + internals changelog + ideas * :ref:`genindex` * :ref:`modindex` diff --git a/docs/changelog.rst b/docs/changelog.rst index c97f735..2e7aaea 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -157,4 +157,11 @@ The following features have been deprecated and will be removed in an upcoming r - First version of factory_boy +Credits +------- + +* Initial version by Mark Sandstrom (2010) +* Developed by Raphaël Barrois since 2011 + + .. vim:et:ts=4:sw=4:tw=119:ft=rst: diff --git a/docs/conf.py b/docs/conf.py index fd9ded6..0ccaf29 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,7 +42,7 @@ master_doc = 'index' # General information about the project. project = u'Factory Boy' -copyright = u'2011, Raphaël Barrois, Mark Sandstrom' +copyright = u'2011-2013, Raphaël Barrois, Mark Sandstrom' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -231,4 +231,10 @@ man_pages = [ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = { + 'http://docs.python.org/': None, + 'django': ( + 'http://docs.djangoproject.com/en/dev/', + 'http://docs.djangoproject.com/en/dev/_objects/', + ), +} diff --git a/docs/examples.rst b/docs/examples.rst index cac6bc6..aab990a 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -7,7 +7,10 @@ Here are some real-world examples of using FactoryBoy. Objects ------- -First, let's define a couple of objects:: +First, let's define a couple of objects: + + +.. code-block:: python class Account(object): def __init__(self, username, email): @@ -41,7 +44,10 @@ First, let's define a couple of objects:: Factories --------- -And now, we'll define the related factories:: +And now, we'll define the related factories: + + +.. code-block:: python import factory import random @@ -60,15 +66,18 @@ And now, we'll define the related factories:: FACTORY_FOR = objects.Profile account = factory.SubFactory(AccountFactory) - gender = random.choice([objects.Profile.GENDER_MALE, objects.Profile.GENDER_FEMALE]) + gender = factory.Iterator([objects.Profile.GENDER_MALE, objects.Profile.GENDER_FEMALE]) firstname = u'John' lastname = u'Doe' -We have now defined basic factories for our :py:class:`~Account` and :py:class:`~Profile` classes. +We have now defined basic factories for our :class:`~Account` and :class:`~Profile` classes. + +If we commonly use a specific variant of our objects, we can refine a factory accordingly: -If we commonly use a specific variant of our objects, we can refine a factory accordingly:: + +.. code-block:: python class FemaleProfileFactory(ProfileFactory): gender = objects.Profile.GENDER_FEMALE @@ -80,7 +89,10 @@ If we commonly use a specific variant of our objects, we can refine a factory ac Using the factories ------------------- -We can now use our factories, for tests:: +We can now use our factories, for tests: + + +.. code-block:: python import unittest @@ -112,7 +124,9 @@ We can now use our factories, for tests:: self.assertLess(stats.genders[objects.Profile.GENDER_FEMALE], 2) -Or for fixtures:: +Or for fixtures: + +.. code-block:: python from . import factories diff --git a/docs/ideas.rst b/docs/ideas.rst new file mode 100644 index 0000000..004f722 --- /dev/null +++ b/docs/ideas.rst @@ -0,0 +1,11 @@ +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 +* **factory-local fields**: Allow some fields to be available while building attributes, + but not passed to the associated class. + For instance, a ``global_kind`` field would be used to select values for many other fields. + diff --git a/docs/internals.rst b/docs/internals.rst new file mode 100644 index 0000000..a7402ff --- /dev/null +++ b/docs/internals.rst @@ -0,0 +1,2 @@ +Internals +========= diff --git a/docs/introduction.rst b/docs/introduction.rst new file mode 100644 index 0000000..d211a83 --- /dev/null +++ b/docs/introduction.rst @@ -0,0 +1,258 @@ +Introduction +============ + + +The purpose of factory_boy is to provide a default way of getting a new instance, +while still being able to override some fields on a per-call basis. + + +.. note:: This section will drive you through an overview of factory_boy's feature. + New users are advised to spend a few minutes browsing through this list + of useful helpers. + + Users looking for quick helpers may take a look at :doc:`recipes`, + while those needing detailed documentation will be interested in the :doc:`reference` section. + + +Basic usage +----------- + + +Factories declare a set of attributes used to instantiate an object, whose class is defined in the FACTORY_FOR attribute: + +- Subclass ``factory.Factory`` (or a more suitable subclass) +- Set its ``FACTORY_FOR`` attribute to the target class +- Add defaults for keyword args to pass to the associated class' ``__init__`` method + + +.. code-block:: python + + import factory + from . import base + + class UserFactory(factory.Factory): + FACTORY_FOR = base.User + + firstname = "John" + lastname = "Doe" + +You may now get ``base.User`` instances trivially: + +.. code-block:: pycon + + >>> john = UserFactory() + <User: John Doe> + +It is also possible to override the defined attributes by passing keyword arguments to the factory: + +.. code-block:: pycon + + >>> jack = UserFactory(firstname="Jack") + <User: Jack Doe> + + +A given class may be associated to many :class:`~factory.Factory` subclasses: + +.. code-block:: python + + class EnglishUserFactory(factory.Factory): + FACTORY_FOR = base.User + + firstname = "John" + lastname = "Doe" + lang = 'en' + + + class FrenchUserFactory(factory.Factory): + FACTORY_FOR = base.User + + firstname = "Jean" + lastname = "Dupont" + lang = 'fr' + + +.. code-block:: pycon + + >>> EnglishUserFactory() + <User: John Doe (en)> + >>> FrenchUserFactory() + <User: Jean Dupont (fr)> + + +Sequences +--------- + +When a field has a unique key, each object generated by the factory should have a different value for that field. +This is achieved with the :class:`~factory.Sequence` declaration: + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = models.User + + username = factory.Sequence(lambda n: 'user%d' % n) + +.. code-block:: pycon + + >>> UserFactory() + <User: user1> + >>> UserFactory() + <User: user2> + +.. note:: For more complex situations, you may also use the :meth:`~factory.@sequence` decorator: + + .. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = models.User + + @factory.sequence + def username(self, n): + return 'user%d' % n + + +LazyAttribute +------------- + +Some fields may be deduced from others, for instance the email based on the username. +The :class:`~factory.LazyAttribute` handles such cases: it should receive a function +taking the object being built and returning the value for the field: + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = models.User + + username = factory.Sequence(lambda n: 'user%d' % n) + email = factory.LazyAttribute(lambda obj: '%s@example.com' % obj.username) + +.. code-block:: pycon + + >>> UserFactory() + <User: user1 (user1@example.com)> + + >>> # The LazyAttribute handles overridden fields + >>> UserFactory(username='john') + <User: john (john@example.com)> + + >>> # They can be directly overridden as well + >>> UserFactory(email='doe@example.com') + <User: user3 (doe@example.com)> + + +.. note:: As for :class:`~factory.Sequence`, a :meth:`~factory.@lazy_attribute` decorator is available. + + +Inheritance +----------- + + +Once a "base" factory has been defined for a given class, +alternate versions can be easily defined through subclassing. + +The subclassed :class:`~factory.Factory` will inherit all declarations from its parent, +and update them with its own declarations: + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = base.User + + firstname = "John" + lastname = "Doe" + group = 'users' + + class AdminFactory(UserFactory): + admin = True + group = 'admins' + +.. code-block:: pycon + + >>> user = UserFactory() + >>> user + <User: John Doe> + >>> user.group + 'users' + + >>> admin = AdminFactory() + >>> admin + <User: John Doe (admin)> + >>> admin.group # The AdminFactory field has overridden the base field + 'admins' + + +Any argument of all factories in the chain can easily be overridden: + +.. code-block:: pycon + + >>> super_admin = AdminFactory(group='superadmins', lastname="Lennon") + >>> super_admin + <User: John Lennon (admin)> + >>> super_admin.group # Overridden at call time + 'superadmins' + + +Non-kwarg arguments +------------------- + +Some classes take a few, non-kwarg arguments first. + +This is handled by the :data:`~factory.Factory.FACTORY_ARG_PARAMETERS` attribute: + +.. code-block:: python + + class MyFactory(factory.Factory): + FACTORY_FOR = MyClass + FACTORY_ARG_PARAMETERS = ('x', 'y') + + x = 1 + y = 2 + z = 3 + +.. code-block:: pycon + + >>> MyFactory(y=4) + <MyClass(1, 4, z=3)> + + +Strategies +---------- + +All factories support two built-in strategies: + +* ``build`` provides a local object +* ``create`` instantiates a local object, and saves it to the database. + +.. note:: For 1.X versions, the ``create`` will actually call ``AssociatedClass.objects.create``, + 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. + + +When a :class:`~factory.Factory` includes related fields (:class:`~factory.SubFactory`, :class:`~factory.RelatedFactory`), +the parent's strategy will be pushed onto related factories. + + +Calling a :class:`~factory.Factory` subclass will provide an object through the default strategy: + +.. code-block:: python + + class MyFactory(factory.Factory): + FACTORY_FOR = MyClass + +.. code-block:: pycon + + >>> MyFactory.create() + <MyFactory: X (saved)> + + >>> MyFactory.build() + <MyFactory: X (unsaved)> + + >>> MyFactory() # equivalent to MyFactory.create() + <MyClass: X (saved)> + + +The default strategy can ba changed by setting the class-level :attr:`~factory.Factory.default_strategy` attribute. + + diff --git a/docs/orms.rst b/docs/orms.rst new file mode 100644 index 0000000..eae31d9 --- /dev/null +++ b/docs/orms.rst @@ -0,0 +1,36 @@ +Using factory_boy with ORMs +=========================== + +.. currentmodule:: factory + + +factory_boy provides custom :class:`Factory` subclasses for various ORMs, +adding dedicated features. + + +Django +------ + + +The first versions of factory_boy were designed specifically for Django, +but the library has now evolved to be framework-independant. + +Most features should thus feel quite familiar to Django users. + +The :class:`DjangoModelFactory` subclass +""""""""""""""""""""""""""""""""""""""""" + +All factories for a Django :class:`~django.db.models.Model` should use the +:class:`DjangoModelFactory` base class. + + +.. class:: DjangoModelFactory(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 + * When using :class:`~factory.RelatedFactory` attributes, the base object will be + :meth:`saved <django.db.models.Model.save>` once all post-generation hooks have run. diff --git a/docs/post_generation.rst b/docs/post_generation.rst deleted file mode 100644 index d712abc..0000000 --- a/docs/post_generation.rst +++ /dev/null @@ -1,91 +0,0 @@ -PostGenerationHook -================== - - -Some objects expect additional method calls or complex processing for proper definition. -For instance, a ``User`` may need to have a related ``Profile``, where the ``Profile`` is built from the ``User`` object. - -To support this pattern, factory_boy provides the following tools: - - :py:class:`factory.PostGeneration`: this class allows calling a given function with the generated object as argument - - :py:func:`factory.post_generation`: decorator performing the same functions as :py:class:`~factory.PostGeneration` - - :py:class:`factory.RelatedFactory`: this builds or creates a given factory *after* building/creating the first Factory. - - -Passing arguments to a post-generation hook -------------------------------------------- - -A post-generation hook will be defined with a given attribute name. -When calling the ``Factory``, some arguments will be passed to the post-generation hook instead of being available for ``Factory`` building: - - - An argument with the same name as the post-generation hook attribute will be passed to the hook - - All arguments beginning with that name and ``__`` will be passed to the hook, after removing the prefix. - -Example:: - - class MyFactory(factory.Factory): - blah = factory.PostGeneration(lambda obj, create, extracted, **kwargs: 42) - - MyFactory( - blah=42, # Passed in the 'extracted' argument of the lambda - blah__foo=1, # Passed in kwargs as 'foo': 1 - blah__baz=2, # Passed in kwargs as 'baz': 2 - blah_bar=3, # Not passed - ) - -The prefix used for extraction can be changed by setting the ``extract_prefix`` argument of the hook:: - - class MyFactory(factory.Factory): - @factory.post_generation(extract_prefix='bar') - def foo(self, create, extracted, **kwargs): - self.foo = extracted - - MyFactory( - bar=42, # Will be passed to 'extracted' - bar__baz=13, # Will be passed as 'baz': 13 in kwargs - foo=2, # Won't be passed to the post-generation hook - ) - - -PostGeneration and @post_generation ------------------------------------ - -Both declarations wrap a function, which will be called with the following arguments: - - ``obj``: the generated factory - - ``create``: whether the factory was "built" or "created" - - ``extracted``: if the :py:class:`~factory.PostGeneration` was declared as attribute ``foo``, - and another value was given for ``foo`` when calling the ``Factory``, - that value will be available in the ``extracted`` parameter. - - other keyword args are extracted from those passed to the ``Factory`` with the same prefix as the name of the :py:class:`~factory.PostGeneration` attribute - - -RelatedFactory --------------- - -This is declared with the following arguments: - - ``factory``: the :py:class:`~factory.Factory` to call - - ``name``: the keyword to use when passing this object to the related :py:class:`~factory.Factory`; if empty, the object won't be passed to the related :py:class:`~factory.Factory` - - Extra keyword args which will be passed to the factory - -When the object is built, the keyword arguments passed to the related :py:class:`~factory.Factory` are: - - ``name: obj`` if ``name`` was passed when defining the :py:class:`~factory.RelatedFactory` - - extra keyword args defined in the :py:class:`~factory.RelatedFactory` definition, overridden by any prefixed arguments passed to the object definition - - -Example:: - - class RelatedObjectFactory(factory.Factory): - FACTORY_FOR = RelatedObject - one = 1 - two = 2 - related = None - - class ObjectWithRelatedFactory(factory.Factory): - FACTORY_FOR = SomeObject - foo = factory.RelatedFactory(RelatedObjectFactory, 'related', one=2) - - ObjectWithRelatedFactory(foo__two=3) - -The ``RelatedObject`` will be called with: - - ``one=2`` - - ``two=3`` - - ``related=<SomeObject>`` diff --git a/docs/recipes.rst b/docs/recipes.rst new file mode 100644 index 0000000..8af0c8f --- /dev/null +++ b/docs/recipes.rst @@ -0,0 +1,62 @@ +Common recipes +============== + + +.. note:: Most recipes below take on Django model examples, but can also be used on their own. + + +Dependent objects (ForeignKey) +------------------------------ + +When one attribute is actually a complex field +(e.g a :class:`~django.db.models.ForeignKey` to another :class:`~django.db.models.Model`), +use the :class:`~factory.SubFactory` declaration: + + +.. code-block:: python + + # models.py + class User(models.Model): + first_name = models.CharField() + group = models.ForeignKey(Group) + + + # factories.py + import factory + from . import models + + class UserFactory(factory.Factory): + FACTORY_FOR = models.User + + first_name = factory.Sequence(lambda n: "Agent %03d" % n) + group = factory.SubFactory(GroupFactory) + + +Reverse dependencies (reverse ForeignKey) +----------------------------------------- + +When a related object should be created upon object creation +(e.g a reverse :class:`~django.db.models.ForeignKey` from another :class:`~django.db.models.Model`), +use a :class:`~factory.RelatedFactory` declaration: + + +.. code-block:: python + + # models.py + class User(models.Model): + pass + + class UserLog(models.Model): + user = models.ForeignKey(User) + action = models.CharField() + + + # factories.py + class UserFactory(factory.Factory): + FACTORY_FOR = models.User + + log = factory.RelatedFactory(UserLogFactory, 'user', action=models.UserLog.ACTION_CREATE) + + +When a :class:`UserFactory` is instantiated, factory_boy will call +``UserLogFactory(user=that_user, action=...)`` just before returning the created ``User``. diff --git a/docs/reference.rst b/docs/reference.rst new file mode 100644 index 0000000..289a9a8 --- /dev/null +++ b/docs/reference.rst @@ -0,0 +1,1027 @@ +Reference +========= + +.. currentmodule:: factory + +This section offers an in-depth description of factory_boy features. + +For internals and customization points, please refer to the :doc:`internals` section. + + +The :class:`Factory` class +-------------------------- + +.. class:: Factory + + The :class:`Factory` class is the base of factory_boy features. + + It accepts a few specific attributes (must be specified on class declaration): + + .. 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`. + + .. attribute:: ABSTRACT_FACTORY + + This attribute indicates that the :class:`Factory` subclass should not + be used to generate objects, but instead provides some extra defaults. + + .. attribute:: FACTORY_ARG_PARAMETERS + + Some factories require non-keyword arguments to their :meth:`~object.__init__`. + They should be listed, in order, in the :attr:`FACTORY_ARG_PARAMETERS` + attribute: + + .. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + FACTORY_ARG_PARAMETERS = ('login', 'email') + + login = 'john' + email = factory.LazyAttribute(lambda o: '%s@example.com' % o.login) + firstname = "John" + + .. code-block:: pycon + + >>> UserFactory() + <User: john> + >>> User('john', 'john@example.com', firstname="John") # actual call + + **Base functions:** + + The :class:`Factory` class provides a few methods for getting objects; + the usual way being to simply call the class: + + .. code-block:: pycon + + >>> UserFactory() # Calls UserFactory.create() + >>> UserFactory(login='john') # Calls UserFactory.create(login='john') + + Under the hood, factory_boy will define the :class:`Factory` + :meth:`~object.__new__` method to call the default :ref:`strategy <strategies>` + of the :class:`Factory`. + + + A specific strategy for getting instance can be selected by calling the + adequate method: + + .. classmethod:: build(cls, **kwargs) + + Provides a new object, using the 'build' strategy. + + .. classmethod:: build_batch(cls, size, **kwargs) + + Provides a list of :obj:`size` instances from the :class:`Factory`, + through the 'build' strategy. + + + .. classmethod:: create(cls, **kwargs) + + Provides a new object, using the 'create' strategy. + + .. classmethod:: create_batch(cls, size, **kwargs) + + Provides a list of :obj:`size` instances from the :class:`Factory`, + through the 'create' strategy. + + + .. classmethod:: stub(cls, **kwargs) + + Provides a new stub + + .. classmethod:: stub_batch(cls, size, **kwargs) + + Provides a list of :obj:`size` stubs from the :class:`Factory`. + + + .. classmethod:: generate(cls, strategy, **kwargs) + + Provide a new instance, with the provided :obj:`strategy`. + + .. classmethod:: generate_batch(cls, strategy, size, **kwargs) + + Provides a list of :obj:`size` instances using the specified strategy. + + + .. classmethod:: simple_generate(cls, create, **kwargs) + + Provide a new instance, either built (``create=False``) or created (``create=True``). + + .. classmethod:: simple_generate_batch(cls, create, size, **kwargs) + + Provides a list of :obj:`size` instances, either built or created + according to :obj:`create`. + + + **Extension points:** + + A :class:`Factory` subclass may override a couple of class methods to adapt + its behaviour: + + .. classmethod:: _adjust_kwargs(cls, **kwargs) + + .. OHAI_VIM** + + 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 + phase. + + .. code-block:: python + + class UserFactory(factory.Factory): + + @classmethod + def _adjust_kwargs(cls, **kwargs): + # Ensure ``lastname`` is upper-case. + kwargs['lastname'] = kwargs['lastname'].upper() + return kwargs + + .. OHAI_VIM** + + + .. classmethod:: _setup_next_sequence(cls) + + This method will compute the first value to use for the sequence counter + of this factory. + + It is called when the first instance of the factory (or one of its subclasses) + is created. + + Subclasses may fetch the next free ID from the database, for instance. + + + .. classmethod:: _build(cls, target_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 + 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) + + .. OHAI_VIM* + + The :meth:`_create` method is called whenever an instance needs to be + created. + It receives the same arguments as :meth:`_build`. + + Subclasses may override this for specific persistence backends: + + .. code-block:: python + + class BaseBackendFactory(factory.Factory): + ABSTRACT_FACTORY = True + + def _create(cls, target_class, *args, **kwargs): + obj = target_class(*args, **kwargs) + obj.save() + return obj + + .. OHAI_VIM* + + +.. _strategies: + +Strategies +"""""""""" + +factory_boy supports two main strategies for generating instances, plus stubs. + + +.. data:: BUILD_STRATEGY + + The 'build' strategy is used when an instance should be created, + 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. + + +.. data:: CREATE_STRATEGY + + The 'create' strategy builds and saves an instance into its appropriate datastore. + + This is the default strategy of factory_boy; it would typically instantiate an + object, then save it: + + .. code-block:: pycon + + >>> obj = self._associated_class(*args, **kwargs) + >>> obj.save() + >>> return obj + + .. OHAI_VIM* + + +.. function:: use_strategy(strategy) + + *Decorator* + + Change the default strategy of the decorated :class:`Factory` to the chosen :obj:`strategy`: + + .. code-block:: python + + @use_strategy(factory.BUILD_STRATEGY) + class UserBuildingFactory(UserFactory): + pass + + +.. 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 + require one to be present. + + Instead, it returns an instance of :class:`StubObject` whose attributes have been + set according to the declarations. + + +.. class:: StubObject(object) + + A plain, stupid object. No method, no helpers, simply a bunch of attributes. + + It is typically instantiated, then has its attributes set: + + .. code-block:: pycon + + >>> obj = StubObject() + >>> obj.x = 1 + >>> obj.y = 2 + + +.. class:: StubFactory(Factory) + + An :attr:`abstract <Factory.ABSTRACT_FACTORY>` :class:`Factory`, + with a default strategy set to :data:`STUB_STRATEGY`. + + +.. _declarations: + +Declarations +------------ + +LazyAttribute +""""""""""""" + +.. class:: LazyAttribute(method_to_call) + +The :class:`LazyAttribute` is a simple yet extremely powerful building brick +for extending a :class:`Factory`. + +It takes as argument a method to call (usually a lambda); that method should +accept the object being built as sole argument, and return a value. + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + username = 'john' + email = factory.LazyAttribute(lambda o: '%s@example.com' % o.username) + +.. code-block:: pycon + + >>> u = UserFactory() + >>> u.email + 'john@example.com' + + >>> u = UserFactory(username='leo') + >>> u.email + 'leo@example.com' + + +Decorator +~~~~~~~~~ + +.. function:: lazy_attribute + +If a simple lambda isn't enough, you may use the :meth:`lazy_attribute` decorator instead. + +This decorates an instance method that should take a single argument, ``self``; +the name of the method will be used as the name of the attribute to fill with the +return value of the method: + +.. code-block:: python + + class UserFactory(factory.Factory) + FACTORY_FOR = User + + name = u"Jean" + + @factory.lazy_attribute + def email(self): + # Convert to plain ascii text + clean_name = (unicodedata.normalize('NFKD', self.name) + .encode('ascii', 'ignore') + .decode('utf8')) + return u'%s@example.com' % clean_name + +.. code-block:: pycon + + >>> joel = UserFactory(name=u"Joël") + >>> joel.email + u'joel@example.com' + + +Sequence +"""""""" + +.. class:: Sequence(lambda, type=int) + +If a field should be unique, and thus different for all built instances, +use a :class:`Sequence`. + +This declaration takes a single argument, a function accepting a single parameter +- the current sequence counter - and returning the related value. + + +.. note:: An extra kwarg argument, ``type``, may be provided. + This feature is deprecated in 1.3.0 and will be removed in 2.0.0. + + +.. code-block:: python + + class UserFactory(factory.Factory) + FACTORY_FOR = User + + phone = factory.Sequence(lambda n: '123-555-%04d' % n) + +.. code-block:: pycon + + >>> UserFactory().phone + '123-555-0001' + >>> UserFactory().phone + '123-555-0002' + + +Decorator +~~~~~~~~~ + +.. function:: sequence + +As with :meth:`lazy_attribute`, a decorator is available for complex situations. + +:meth:`sequence` decorates an instance method, whose ``self`` method will actually +be the sequence counter - this might be confusing: + +.. code-block:: python + + class UserFactory(factory.Factory) + FACTORY_FOR = User + + @factory.sequence + def phone(n): + a = n // 10000 + b = n % 10000 + return '%03d-555-%04d' % (a, b) + +.. code-block:: pycon + + >>> UserFactory().phone + '000-555-9999' + >>> UserFactory().phone + '001-555-0000' + + +Sharing +~~~~~~~ + +The sequence counter is shared across all :class:`Sequence` attributes of the +:class:`Factory`: + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + phone = factory.Sequence(lambda n: '%04d' % n) + office = factory.Sequence(lambda n: 'A23-B%03d' % n) + +.. code-block:: pycon + + >>> u = UserFactory() + >>> u.phone, u.office + '0041', 'A23-B041' + >>> u2 = UserFactory() + >>> u2.phone, u2.office + '0042', 'A23-B042' + + +Inheritance +~~~~~~~~~~~ + +When a :class:`Factory` inherits from another :class:`Factory`, their +sequence counter is shared: + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + phone = factory.Sequence(lambda n: '123-555-%04d' % n) + + + class EmployeeFactory(UserFactory): + office_phone = factory.Sequence(lambda n: '%04d' % n) + +.. code-block:: pycon + + >>> u = UserFactory() + >>> u.phone + '123-555-0001' + + >>> e = EmployeeFactory() + >>> e.phone, e.office_phone + '123-555-0002', '0002' + + >>> u2 = UserFactory() + >>> u2.phone + '123-555-0003' + + +LazyAttributeSequence +""""""""""""""""""""" + +.. class:: LazyAttributeSequence(method_to_call) + +The :class:`LazyAttributeSequence` declaration merges features of :class:`Sequence` +and :class:`LazyAttribute`. + +It takes a single argument, a function whose two parameters are, in order: + +* The object being built +* The sequence counter + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + login = 'john' + email = factory.LazyAttributeSequence(lambda o, n: '%s@s%d.example.com' % (o.login, n)) + +.. code-block:: pycon + + >>> UserFactory().email + 'john@s1.example.com' + >>> UserFactory(login='jack').email + 'jack@s2.example.com' + + +Decorator +~~~~~~~~~ + +.. function:: lazy_attribute_sequence(method_to_call) + +As for :meth:`lazy_attribute` and :meth:`sequence`, the :meth:`lazy_attribute_sequence` +handles more complex cases: + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + login = 'john' + + @lazy_attribute_sequence + def email(self, n): + bucket = n % 10 + return '%s@s%d.example.com' % (self.login, bucket) + + +SubFactory +"""""""""" + +.. class:: SubFactory(sub_factory, **kwargs) + + .. OHAI_VIM** + +This attribute declaration calls another :class:`Factory` subclass, +selecting the same build strategy and collecting extra kwargs in the process. + +The :class:`SubFactory` attribute should be called with: + +* A :class:`Factory` subclass as first argument, or the fully qualified import + path to that :class:`Factory` (see :ref:`Circular imports <subfactory-circular>`) +* An optional set of keyword arguments that should be passed when calling that + factory + + +Definition +~~~~~~~~~~ + +.. code-block:: python + + + # A standard factory + class UserFactory(factory.Factory): + FACTORY_FOR = User + + # Various fields + first_name = 'John' + last_name = factory.Sequence(lambda n: 'D%se' % ('o' * n)) # De, Doe, Dooe, Doooe, ... + email = factory.LazyAttribute(lambda o: '%s.%s@example.org' % (o.first_name.lower(), o.last_name.lower())) + + # A factory for an object with a 'User' field + class CompanyFactory(factory.Factory): + FACTORY_FOR = Company + + name = factory.Sequence(lambda n: 'FactoryBoyz' + 'z' * n) + + # Let's use our UserFactory to create that user, and override its first name. + owner = factory.SubFactory(UserFactory, first_name='Jack') + + +Calling +~~~~~~~ + +The wrapping factory will call of the inner factory: + +.. code-block:: pycon + + >>> c = CompanyFactory() + >>> c + <Company: FactoryBoyz> + + # Notice that the first_name was overridden + >>> c.owner + <User: Jack De> + >>> c.owner.email + jack.de@example.org + + +Fields of the :class:`~factory.SubFactory` may be overridden from the external factory: + +.. code-block:: pycon + + >>> c = CompanyFactory(owner__first_name='Henry') + >>> c.owner + <User: Henry Doe> + + # Notice that the updated first_name was propagated to the email LazyAttribute. + >>> c.owner.email + henry.doe@example.org + + # It is also possible to override other fields of the SubFactory + >>> c = CompanyFactory(owner__last_name='Jones') + >>> c.owner + <User: Henry Jones> + >>> c.owner.email + henry.jones@example.org + + +Strategies +~~~~~~~~~~ + +The strategy chosen for the external factory will be propagated to all subfactories: + +.. code-block:: pycon + + >>> c = CompanyFactory() + >>> c.pk # Saved to the database + 3 + >>> c.owner.pk # Saved to the database + 8 + + >>> c = CompanyFactory.build() + >>> c.pk # Not saved + None + >>> c.owner.pk # Not saved either + None + + +.. _subfactory-circular: + +Circular imports +~~~~~~~~~~~~~~~~ + +Some factories may rely on each other in a circular manner. +This issue can be handled by passing the absolute import path to the target +:class:`Factory` to the :class:`SubFactory`: + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + username = 'john' + main_group = factory.SubFactory('users.factories.GroupFactory') + + class GroupFactory(factory.Factory): + FACTORY_FOR = Group + + name = "MyGroup" + owner = factory.SubFactory(UserFactory) + + +Obviously, such circular relationships require careful handling of loops: + +.. code-block:: pycon + + >>> owner = UserFactory(main_group=None) + >>> UserFactory(main_group__owner=owner) + <john (group: MyGroup)> + + +SelfAttribute +""""""""""""" + +.. class:: SelfAttribute(dotted_path_to_attribute) + +Some fields should reference another field of the object being constructed, or an attribute thereof. + +This is performed by the :class:`~factory.SelfAttribute` declaration. +That declaration takes a single argument, a dot-delimited path to the attribute to fetch: + +.. code-block:: python + + class UserFactory(factory.Factory) + FACTORY_FOR = User + + birthdate = factory.Sequence(lambda n: datetime.date(2000, 1, 1) + datetime.timedelta(days=n)) + birthmonth = factory.SelfAttribute('birthdate.month') + +.. code-block:: pycon + + >>> u = UserFactory() + >>> u.birthdate + date(2000, 3, 15) + >>> u.birthmonth + 3 + + +Parents +~~~~~~~ + +When used in conjunction with :class:`~factory.SubFactory`, the :class:`~factory.SelfAttribute` +gains an "upward" semantic through the double-dot notation, as used in Python imports. + +``factory.SelfAttribute('..country.language')`` means +"Select the ``language`` of the ``country`` of the :class:`~factory.Factory` calling me". + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + language = 'en' + + + class CompanyFactory(factory.Factory): + FACTORY_FOR = Company + + country = factory.SubFactory(CountryFactory) + owner = factory.SubFactory(UserFactory, language=factory.SelfAttribute('..country.language')) + +.. code-block:: pycon + + >>> company = CompanyFactory() + >>> company.country.language + 'fr' + >>> company.owner.language + 'fr' + +Obviously, this "follow parents" hability also handles overriding some attributes on call: + +.. code-block:: pycon + + >>> company = CompanyFactory(country=china) + >>> company.owner.language + 'cn' + + +Iterator +"""""""" + +.. class:: Iterator(iterable, cycle=True, getter=None) + +The :class:`Iterator` declaration takes succesive values from the given +iterable. When it is exhausted, it starts again from zero (unless ``cycle=False``). + +.. note:: Versions prior to 1.3.0 declared both :class:`Iterator` (for ``cycle=False``) + and :class:`InfiniteIterator` (for ``cycle=True``). + + :class:`InfiniteIterator` is deprecated as of 1.3.0 and will be removed in 2.0.0 + +The ``cycle`` argument is only useful for advanced cases, where the provided +iterable has no end (as wishing to cycle it means storing values in memory...). + +Each call to the factory will receive the next value from the iterable: + +.. code-block:: python + + class UserFactory(factory.Factory) + lang = factory.Iterator(['en', 'fr', 'es', 'it', 'de']) + +.. code-block:: pycon + + >>> UserFactory().lang + 'en' + >>> UserFactory().lang + 'fr' + + +When a value is passed in for the argument, the iterator will *not* be advanced: + +.. code-block:: pycon + + >>> UserFactory().lang + 'en' + >>> UserFactory(lang='cn').lang + 'cn' + >>> UserFactory().lang + 'fr' + +Getter +~~~~~~ + +Some situations may reuse an existing iterable, using only some component. +This is handled by the :attr:`~Iterator.getter` attribute: this is a function +that accepts as sole parameter a value from the iterable, and returns an +adequate value. + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + # CATEGORY_CHOICES is a list of (key, title) tuples + category = factory.Iterator(User.CATEGORY_CHOICES, getter=lambda c: c[0]) + + +post-building hooks +""""""""""""""""""" + +Some objects expect additional method calls or complex processing for proper definition. +For instance, a ``User`` may need to have a related ``Profile``, where the ``Profile`` is built from the ``User`` object. + +To support this pattern, factory_boy provides the following tools: + - :class:`PostGeneration`: this class allows calling a given function with the generated object as argument + - :func:`post_generation`: decorator performing the same functions as :class:`PostGeneration` + - :class:`RelatedFactory`: this builds or creates a given factory *after* building/creating the first Factory. + + +RelatedFactory +"""""""""""""" + +.. class:: RelatedFactory(some_factory, related_field, **kwargs) + + .. OHAI_VIM** + +A :class:`RelatedFactory` behaves mostly like a :class:`SubFactory`, +with the main difference that it should be provided with a ``related_field`` name +as second argument. + +Once the base object has been built (or created), the :class:`RelatedFactory` will +build the :class:`Factory` passed as first argument (with the same strategy), +passing in the base object as a keyword argument whose name is passed in the +``related_field`` argument: + +.. code-block:: python + + class CityFactory(factory.Factory): + FACTORY_FOR = City + + capital_of = None + name = "Toronto" + + class CountryFactory(factory.Factory): + FACTORY_FOR = Country + + lang = 'fr' + capital_city = factory.RelatedFactory(CityFactory, 'capital_of', name="Paris") + +.. code-block:: pycon + + >>> france = CountryFactory() + >>> City.objects.get(capital_of=france) + <City: Paris> + +Extra kwargs may be passed to the related factory, through the usual ``ATTR__SUBATTR`` syntax: + +.. code-block:: pycon + + >>> england = CountryFactory(lang='en', capital_city__name="London") + >>> City.objects.get(capital_of=england) + <City: London> + + +PostGeneration +"""""""""""""" + +.. class:: PostGeneration(callable) + +The :class:`PostGeneration` declaration performs actions once the target object +has been generated. + +Its sole argument is a callable, that will be called once the base object has + been generated. + +.. note:: Previous versions of factory_boy supported an extra ``extract_prefix`` + argument, to use an alternate argument prefix. + This feature is deprecated in 1.3.0 and will be removed in 2.0.0. + +Once the base object has been generated, the provided callable will be called +as ``callable(obj, create, extracted, **kwargs)``, where: + +- ``obj`` is the base object previously generated +- ``create`` is a boolean indicating which strategy was used +- ``extracted`` is ``None`` unless a value was passed in for the + :class:`PostGeneration` declaration at :class:`Factory` declaration time +- ``kwargs`` are any extra parameters passed as ``attr__key=value`` when calling + the :class:`Factory`: + + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + login = 'john' + make_mbox = factory.PostGeneration( + lambda obj, create, extracted, **kwargs: os.makedirs(obj.login)) + +.. OHAI_VIM** + +Decorator +~~~~~~~~~ + +.. function:: post_generation(extract_prefix=None) + +A decorator is also provided, decorating a single method accepting the same +``obj``, ``created``, ``extracted`` and keyword arguments as :class:`PostGeneration`. + + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + login = 'john' + + @factory.post_generation + def mbox(self, create, extracted, **kwargs): + if not create: + return + path = extracted or os.path.join('/tmp/mbox/', self.login) + os.path.makedirs(path) + return path + +.. OHAI_VIM** + +.. code-block:: pycon + + >>> UserFactory.build() # Nothing was created + >>> UserFactory.create() # Creates dir /tmp/mbox/john + >>> UserFactory.create(login='jack') # Creates dir /tmp/mbox/jack + >>> UserFactory.create(mbox='/tmp/alt') # Creates dir /tmp/alt + + +PostGenerationMethodCall +"""""""""""""""""""""""" + +.. class:: PostGenerationMethodCall(method_name, extract_prefix=None, *args, **kwargs) + +.. OHAI_VIM* + +The :class:`PostGenerationMethodCall` declaration will call a method on the +generated object just after it being called. + +Its sole argument is the name of the method to call. +Extra arguments and keyword arguments for the target method may also be provided. + +Once the object has been generated, the method will be called, with the arguments +provided in the :class:`PostGenerationMethodCall` declaration, and keyword +arguments taken from the combination of :class:`PostGenerationMethodCall` +declaration and prefix-based values: + +.. code-block:: python + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + password = factory.PostGenerationMethodCall('set_password', password='') + +.. code-block:: pycon + + >>> UserFactory() # Calls user.set_password(password='') + >>> UserFactory(password='test') # Calls user.set_password(password='test') + >>> UserFactory(password__disabled=True) # Calls user.set_password(password='', disabled=True) + + +Module-level functions +---------------------- + +Beyond the :class:`Factory` class and the various :ref:`declarations` classes +and methods, factory_boy exposes a few module-level functions, mostly useful +for lightweight factory generation. + + +Lightweight factory declaration +""""""""""""""""""""""""""""""" + +.. function:: make_factory(klass, **kwargs) + + .. OHAI_VIM** + + The :func:`make_factory` function takes a class, declarations as keyword arguments, + and generates a new :class:`Factory` for that class accordingly: + + .. code-block:: python + + UserFactory = make_factory(User, + login='john', + email=factory.LazyAttribute(lambda u: '%s@example.com' % u.login), + ) + + # This is equivalent to: + + class UserFactory(factory.Factory): + FACTORY_FOR = User + + login = 'john' + email = factory.LazyAttribute(lambda u: '%s@example.com' % u.login) + + +Instance building +""""""""""""""""" + +The :mod:`factory` module provides a bunch of shortcuts for creating a factory and +extracting instances from them: + +.. function:: build(klass, **kwargs) +.. function:: build_batch(klass, size, **kwargs) + + Create a factory for :obj:`klass` using declarations passed in kwargs; + return an instance built from that factory, + or a list of :obj:`size` instances (for :func:`build_batch`). + + :param class klass: Class of the instance to build + :param int size: Number of instances to build + :param kwargs: Declarations to use for the generated factory + + + +.. function:: create(klass, **kwargs) +.. function:: create_batch(klass, size, **kwargs) + + Create a factory for :obj:`klass` using declarations passed in kwargs; + return an instance created from that factory, + or a list of :obj:`size` instances (for :func:`create_batch`). + + :param class klass: Class of the instance to create + :param int size: Number of instances to create + :param kwargs: Declarations to use for the generated factory + + + +.. function:: stub(klass, **kwargs) +.. function:: stub_batch(klass, size, **kwargs) + + Create a factory for :obj:`klass` using declarations passed in kwargs; + return an instance stubbed from that factory, + or a list of :obj:`size` instances (for :func:`stub_batch`). + + :param class klass: Class of the instance to stub + :param int size: Number of instances to stub + :param kwargs: Declarations to use for the generated factory + + + +.. function:: generate(klass, strategy, **kwargs) +.. function:: generate_batch(klass, strategy, size, **kwargs) + + Create a factory for :obj:`klass` using declarations passed in kwargs; + return an instance generated from that factory with the :obj:`strategy` strategy, + or a list of :obj:`size` instances (for :func:`generate_batch`). + + :param class klass: Class of the instance to generate + :param str strategy: The strategy to use + :param int size: Number of instances to generate + :param kwargs: Declarations to use for the generated factory + + + +.. function:: simple_generate(klass, create, **kwargs) +.. function:: simple_generate_batch(klass, create, size, **kwargs) + + Create a factory for :obj:`klass` using declarations passed in kwargs; + return an instance generated from that factory according to the :obj:`create` flag, + or a list of :obj:`size` instances (for :func:`simple_generate_batch`). + + :param class klass: Class of the instance to generate + :param bool create: Whether to build (``False``) or create (``True``) instances + :param int size: Number of instances to generate + :param kwargs: Declarations to use for the generated factory + + diff --git a/docs/subfactory.rst b/docs/subfactory.rst deleted file mode 100644 index f8642f3..0000000 --- a/docs/subfactory.rst +++ /dev/null @@ -1,81 +0,0 @@ -SubFactory -========== - -Some objects may use other complex objects as parameters; in order to simplify this setup, factory_boy -provides the :py:class:`factory.SubFactory` class. - -This should be used when defining a :py:class:`~factory.Factory` attribute that will hold the other complex object:: - - import factory - - # A standard factory - class UserFactory(factory.Factory): - FACTORY_FOR = User - - # Various fields - first_name = 'John' - last_name = factory.Sequence(lambda n: 'D%se' % ('o' * n)) # De, Doe, Dooe, Doooe, ... - email = factory.LazyAttribute(lambda o: '%s.%s@example.org' % (o.first_name.lower(), o.last_name.lower())) - - # A factory for an object with a 'User' field - class CompanyFactory(factory.Factory): - FACTORY_FOR = Company - - name = factory.Sequence(lambda n: 'FactoryBoyz' + z * n) - - # Let's use our UserFactory to create that user, and override its first name. - owner = factory.SubFactory(UserFactory, first_name='Jack') - -Instantiating the external factory will in turn instantiate an object of the internal factory:: - - >>> c = CompanyFactory() - >>> c - <Company: FactoryBoyz> - - # Notice that the first_name was overridden - >>> c.owner - <User: Jack De> - >>> c.owner.email - jack.de@example.org - -Fields of the SubFactory can also be overridden when instantiating the external factory:: - - >>> c = CompanyFactory(owner__first_name='Henry') - >>> c.owner - <User: Henry Doe> - - # Notice that the updated first_name was propagated to the email LazyAttribute. - >>> c.owner.email - henry.doe@example.org - - # It is also possible to override other fields of the SubFactory - >>> c = CompanyFactory(owner__last_name='Jones') - >>> c.owner - <User: Henry Jones> - >>> c.owner.email - henry.jones@example.org - - -Circular dependencies ---------------------- - -In order to solve circular dependency issues, Factory Boy provides the :class:`~factory.CircularSubFactory` class. - -This class expects a module name and a factory name to import from that module; the given module will be imported -(as an absolute import) when the factory is first accessed:: - - # foo/factories.py - import factory - - from bar import factories - - class FooFactory(factory.Factory): - bar = factory.SubFactory(factories.BarFactory) - - - # bar/factories.py - import factory - - class BarFactory(factory.Factory): - # Avoid circular import - foo = factory.CircularSubFactory('foo.factories', 'FooFactory', bar=None) |