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 /docs | |
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>
Diffstat (limited to 'docs')
-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 |
11 files changed, 1432 insertions, 181 deletions
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) |