distillery 0.5
fixture utils for python ORMs
License: MIT licence

Distillery
==========

.. image:: https://secure.travis-ci.org/Birdback/distillery.png

``distillery`` is another `factory_girl
<https://github.com/thoughtbot/factory_girl>`_ like library for python
ORMs.


Installation
------------

``pip install distillery``


Distilleries
------------

Each distillery has a ``__model__`` and a set of attributes and
methods. The ``__model__`` is the ORM model class from which instance
will be produced:

.. code-block:: python

    class UserDistillery(MyOrmDistillery):
        __model__ = User


Attributes
~~~~~~~~~~

A distillery class attribute defines default values for specific model
field:

.. code-block:: python

    class UserDistillery(MyOrmDistillery):
        __model__ = User

        username = "defaultusername"

The distillery's attribute values act as *defaults*. If a ``User``
object is created using this distillery, its ``username`` attribute
will default to ``"defaultusername"``.


Lazy attributes
~~~~~~~~~~~~~~~

Using the ``lazy`` decorator, you can provide dynamic attributes.

.. code-block:: python

    from distillery import lazy

    class UserDistillery(MyOrmDistillery):
        __model__ = User

        username = "defaultusername"

        @lazy
        def email_address(cls, instance, sequence):
            return "%s@%s" % (instance.username, instance.company.domain)

All new ``User`` created from ``UserDistillery`` will have an
``email_address`` computed from his username and his company domain.

Note: all lazies received an ``instance`` and a ``sequence`` that are
the object instance and an auto-incremented sequence, respectively.


Hooks
~~~~~

A distillery can provide an ``_after_create`` class method to hook
into the factory machinery.

.. code-block:: python

    class UserDistillery(MyOrmDistillery):
        __model__ = User

        username = "defaultusername"

        @classmethod
        def _after_create(cls, instance):
            # Do stuff after instance creation
            # ...


Distillery.init()
~~~~~~~~~~~~~~~~~

The ``init()`` method creates and populates a new instance.

.. code-block:: python

    user = UserDistillery.init()
    assert user.username == "defaultusername"
    assert user.id is None

    user = UserDistillery.create(username="overriddenusername")
    assert user.username == "overriddenusername"
    assert user.id is None


Distillery.create()
~~~~~~~~~~~~~~~~~~~

The ``create()`` method initializes the object using ``init()`` and
subsequently *saves* it.

.. code-block:: python

    user = UserDistillery.create()
    assert user.username == "defaultusername"
    assert user.id is not None


Distillery.bulk()
~~~~~~~~~~~~~~~~~

Creates instances in bulk.

.. code-block:: python

    users = UserDistillery.bulk(12, username="user_%(i)%")
    assert users[7].username = 'user_7'


Sets
----

The ``distillery.Set`` class acts as a fixture container.

It's required to define a ``__distillery__`` attribute which is used
to create objects.

.. code-block:: python

    from distillery import Set

    class UserSet(Set):
        __distillery__ = UserDistillery

        class jeanphix:
            username = 'jeanphix'


To create the fixtures, simply instantiate the set.

.. code-block:: python

    users = UserSet()
    assert users.jeanphix.username == 'jeanphix'

Importantly, as long as a reference to the *instantiated set* is held
(e.g. the ``users`` variable in this example), the set can be called
again and the same instance is returned:

.. code-block:: python

    assert UserSet() is UserSet()

You can reference other sets, too. Note that you must reference using
the class, or use a lazy attribute (described later):

.. code-block:: python

    from distillery import Set

    class CompanySet(Set):
        __distillery__ = CompanyDistillery

        class my_company:
            name = 'My company'

    class UserSet(Set):
        __distillery__ = UserDistillery

        class jeanphix:
            username = 'jeanphix'
            company = CompanySet.company


    users = UserSet()
    assert users.jeanphix.company == 'My company'


In addition to classes, methods can be defined; each will result in an
object which is added to the set.

.. code-block:: python

    class ProfileSet(Set)
        class __distillery__:
            __model__ = Profile

        admin = lambda s: UserDistillery.create(username='admin').profile

This functionality extends to class-based references. Note that the
reference must be resolvable at the point of creation; circular
relationships are currently not supported.

.. code-block:: python

    class UserSet(Set):
        class peter:
            friend = None

        class paul:
            friend = classmethod(lambda c: UserSet.peter)


If the ``on_demand`` flag is set, objects are created only when first
accessed.

.. code-block:: python

    users = UserSet(on_demand=True)
    users.jeanphix  # jeanphix will be created here.

Finally, sets can be nested.

.. code-block:: python

    class fixtures(Set):
        users = UserSet

    assert fixtures().users.jeanphix.username == 'jeanphix'


Hooks
~~~~~

Each fixture in a set can provide an ``_after_create`` listener:

.. code-block:: python

    class ProfileSet(Set):
        class __distillery__:
            __model__ = Profile

        class admin:
            @classmethod
            def _after_create(cls, profile):
                profile.name = 'Full name'

    assert ProfileSet().admin.name == 'Full name'


ORMs
----

Both Django and SQLAlchemy are supported.


Django
~~~~~~

Django models could be distilled using ``DjangoDistillery`` that only
requires a ``__model__`` class member:

.. code-block:: python

    from distillery import DjangoDistillery

    from django.auth.models import User

    class UserDistillery(DjangoDistillery):
        __model__ = User

        #  ...


SQLAlchemy
~~~~~~~~~~

SQLAlchemy distilleries require both the ``__model__`` and
``__session__`` attributes.

.. code-block:: python

    from distillery import SQLAlchemyDistillery

    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    engine = create_engine('sqlite://', echo=False)
    Session = sessionmaker()
    Session.configure(bind=engine)
    session = Session()
    Base = declarative_base()

    class User(Base):
        #  ...


    class UserDistillery(SQLAlchemyDistillery):
        __model__ = User
        __session__ = session

        #  ...