diff options
Diffstat (limited to 'docs/introduction.rst')
-rw-r--r-- | docs/introduction.rst | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/docs/introduction.rst b/docs/introduction.rst new file mode 100644 index 0000000..8bbb10c --- /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.FACTROY_STRATEGY` attribute. + + |