Module f.l.loadable

Part of fixture.loadable

Loadable fixtures

.. contents:: :local:

A DataSet class is loaded via some storage medium, say, an object that implements a `Data Mapper`_ or `Active Record`_ pattern.  A Fixture is an environment that knows how to load data using the right objects.  Behind the scenes, the rows and columns of the DataSet are simply passed off to the storage medium so that it can save the data.

.. _Data Mapper: http://www.martinfowler.com/eaaCatalog/dataMapper.html
.. _Active Record: http://www.martinfowler.com/eaaCatalog/activeRecord.html

Supported storage media
~~~~~~~~~~~~~~~~~~~~~~~

The Fixture class is designed to support many different types of storage media and there is a section later about creating your own Fixture.  Here are the various storage media supported by built-in Fixture subclasses:

SQLAlchemy
++++++++++

DataSet classes can be loaded into `Table`_ objects or `mapped classes`_ via the `sqlalchemy`_ module::

    >>> from fixture import SQLAlchemyFixture
    
    >>> from sqlalchemy import create_session
    >>> from sqlalchemy.ext.sessioncontext import SessionContext
    >>> from fixture.examples.db import sqlalchemy_examples
    >>> dbfixture = SQLAlchemyFixture(
    ...                 session_context=SessionContext(create_session), 
    ...                 env=sqlalchemy_examples)
    ... 

For the more documentation see `SQLAlchemyFixture API`_

Elixir
++++++

DataSet class can be loaded into `Elixir entities`_ by using the SQLAlchemyFixture (see previous example).

SQLObject
+++++++++

DataSet classes can be loaded into `SQLObject classes`_ via the `sqlobject`_ module::

    >>> from fixture import SQLObjectFixture
    
    >>> from fixture.examples.db import sqlobject_examples
    >>> dbfixture = SQLObjectFixture(
    ...     dsn="sqlite:/:memory:", env=sqlobject_examples)
    ... 

For the more documentation see `SQLObjectFixture API`_.

.. _SQLAlchemyFixture API: ../apidocs/fixture.loadable.sqlalchemy_loadable.SQLAlchemyFixture.html
.. _SQLObjectFixture API: ../apidocs/fixture.loadable.sqlobject_loadable.SQLObjectFixture.html

An Example Loading Data Using SQLAlchemy
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Fixture is designed for applications that already define a way of accessing its data; the LoadableFixture just "hooks in" to that interface.  To start this example, here is some `sqlalchemy`_ code to set up a database of books and authors::

    >>> from sqlalchemy import *
    >>> engine = create_engine('sqlite:///:memory:')
    >>> meta = BoundMetaData(engine)
    >>> session = create_session(engine)
    >>> authors = Table('authors', meta,
    ...     Column('id', Integer, primary_key=True),
    ...     Column('first_name', String),
    ...     Column('last_name', String))
    ... 
    >>> class Author(object):
    ...     pass
    ... 
    >>> mapper(Author, authors) #doctest: +ELLIPSIS
    <sqlalchemy.orm.mapper.Mapper object at ...>
    >>> books = Table('books', meta, 
    ...     Column('id', Integer, primary_key=True),
    ...     Column('title', String),
    ...     Column('author_id', Integer, ForeignKey('authors.id')))
    ... 
    >>> class Book(object):
    ...     pass
    ... 
    >>> mapper(Book, books, properties={
    ...     'author': relation(Author, backref='books')
    ... }) #doctest: +ELLIPSIS
    <sqlalchemy.orm.mapper.Mapper object at ...>
    >>> meta.create_all()

Consult the `sqlalchemy`_ documentation for further examples of data mapping.

.. _sqlalchemy: http://www.sqlalchemy.org/
.. _Table: http://www.sqlalchemy.org/docs/tutorial.myt#tutorial_schemasql_table_creating
.. _mapped classes: http://www.sqlalchemy.org/docs/datamapping.myt
.. _Elixir entities: http://elixir.ematia.de/
.. _sqlobject: http://sqlobject.org/
.. _SQLObject classes: http://sqlobject.org/SQLObject.html#declaring-the-class

Defining a Fixture
~~~~~~~~~~~~~~~~~~

This is a fixture with minimal configuration to support loading data into the Book or Author mapped classes::

    >>> from fixture import SQLAlchemyFixture
    >>> dbfixture = SQLAlchemyFixture(
    ...     env={'BookData': Book, 'AuthorData': Author},
    ...     session=session )
    ... 

There are several shortcuts, like `fixture.style.NamedDataStyle`_ and specifying the `session_context keyword`_.

.. note::
    - Any keyword attribute of a LoadableFixture can be set later on as an 
      attribute of the instance.
    - LoadableFixture instances can safely be module-level objects
    - An ``env`` can be a dict or a module
    
.. _session_context keyword: ../apidocs/fixture.loadable.sqlalchemy_loadable.SQLAlchemyFixture.html
.. _fixture.style.NamedDataStyle: ../apidocs/fixture.style.NamedDataStyle.html

Loading DataSet objects
~~~~~~~~~~~~~~~~~~~~~~~

The job of the Fixture object is to load and unload DataSet objects.  Let's consider the following DataSet objects (reusing the examples from earlier)::

    >>> from fixture import DataSet
    >>> class AuthorData(DataSet):
    ...     class frank_herbert:
    ...         first_name = "Frank"
    ...         last_name = "Herbert"
    >>> class BookData(DataSet):
    ...     class dune:
    ...         title = "Dune"
    ...         author = AuthorData.frank_herbert

As you recall, we passed a dictionary into the Fixture that associates DataSet names with storage objects.  Using this dict, a Fixture.Data instance now knows to use the sqlalchemy mapped class ``Book`` when saving a DataSet named ``BookData``.  Since we also gave it a ``session`` keyword, this will be used to actually save objects::
    
    >>> data = dbfixture.data(AuthorData, BookData)
    >>> data.setup() 
    >>> all_books = list(session.query(Book).select()) 
    >>> all_books #doctest: +ELLIPSIS
    [<...Book object at ...>]
    >>> all_books[0].author.first_name
    'Frank'
    >>> data.teardown()
    >>> list(session.query(Book).select())
    []

Discovering storable objects with Style
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you didn't want to create a strict mapping of DataSet class names to their storable object names you can use Style objects to translate DataSet class names.  For example, consider this Fixture :

    >>> from fixture import SQLAlchemyFixture
    >>> from fixture.style import TrimmedNameStyle
    >>> dbfixture = SQLAlchemyFixture(
    ...     env=globals(),
    ...     style=TrimmedNameStyle(suffix="Data"),
    ...     session=session )
    ... 

This would take the name ``AuthorData`` and trim off "Data" from its name to find ``Author``, its mapped sqlalchemy class for storing data.  Since this is a logical convention to follow for naming DataSet classes, you can use a shortcut:

    >>> from fixture.style import NamedDataStyle
    >>> dbfixture = SQLAlchemyFixture(
    ...     env=globals(),
    ...     style=NamedDataStyle(),
    ...     session=session )
    ... 

See the `Style API`_ for all available Style objects.

.. _Style API: ../apidocs/fixture.style.html

Loading DataSet classes in a test
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Now that you have a Fixture object to load DataSet classes you are ready to write some tests.  You can either write your own code that creates a data instance and calls setup/teardown manually (like in previous examples), or you can use one of several utilities.  

As a hoky attempt to make these tests somewhat realistic, here is a function we will be testing, that returns True if a book by author or title is in stock:

    >>> def in_stock(book_title=None, author_last_name=None):
    ...     if book_title:
    ...         rs = session.query(Book).select(books.c.title==book_title)
    ...     elif author_last_name:
    ...         rs = session.query(Book).select(
    ...                 authors.c.last_name==author_last_name,
    ...                 from_obj=[books.join(authors)])
    ...     else:
    ...         return False
    ...     if len(list(rs)):
    ...         return True

Loading objects using DataTestCase
++++++++++++++++++++++++++++++++++

DataTestCase is a mixin class to use with Python's built-in ``unittest.TestCase``::

    >>> import unittest
    >>> from fixture import DataTestCase
    >>> class TestBookShop(DataTestCase, unittest.TestCase):
    ...     fixture = dbfixture
    ...     datasets = [BookData]
    ...
    ...     def test_books_are_in_stock(self):
    ...         assert in_stock(book_title=self.data.BookData.dune.title)
    ... 
    >>> suite = unittest.TestLoader().loadTestsFromTestCase(TestBookShop)
    >>> unittest.TextTestRunner().run(suite)
    <unittest._TextTestResult run=1 errors=0 failures=0>

Re-using what was created earlier, the ``fixture`` attribute is set to the Fixture instance and the ``datasets`` attribute is set to a list of DataSet classes.  When in the test method itself, as you can see, you can reference loaded data through ``self.data``, an instance of SuperSet.  Keep in mind that if you need to override either setUp() or tearDown() then you'll have to call the super methods.

See the `DataTestCase API`_ for a full explanation of how it can be configured.

.. _DataTestCase API: ../apidocs/fixture.util.DataTestCase.html
    

Loading objects using @dbfixture.with_data
++++++++++++++++++++++++++++++++++++++++++

If you use nose_, a test runner for Python, then you may be familiar with its `discovery of test methods`_.  Test methods (as opposed to unittest.TestCase classes) provide a quick way to write procedural tests and often illustrate concisely what features are being tested.  Nose supports test methods that are decorated with setup and teardown methods and fixture provides a way to setup/teardown DataSet objects for a test method.  If you don't have nose_ installed, simply install fixture like so and nose will be installed for you::
    
    easy_install fixture[decorators]

The special decorator method is an instance method of a Fixture class, ``with_data``; it can be used like so::

    >>> @dbfixture.with_data(AuthorData, BookData)
    ... def test_books_are_in_stock(data):
    ...     assert in_stock(book_title=data.BookData.dune.title)
    ... 
    >>> import nose
    >>> case = nose.case.FunctionTestCase(test_books_are_in_stock)
    >>> unittest.TextTestRunner().run(case)
    <unittest._TextTestResult run=1 errors=0 failures=0>

Like in the previous example, the ``data`` attribute is a SuperSet object you can use to reference loaded data.  This is passed to your decorated test method as its first argument.  (nose_ will run the above code automatically; the inline execution of the test here is merely for example.)

See the `Fixture.Data.with_data API`_ for more information.

.. _nose: http://somethingaboutorange.com/mrl/projects/nose/
.. _discovery of test methods: http://code.google.com/p/python-nose/wiki/WritingTests
.. _Fixture.Data.with_data API: ../apidocs/fixture.base.Fixture.html#with_data

Loading objects using the with statement
++++++++++++++++++++++++++++++++++++++++

In Python 2.5 or later you can write test code in a more logical manner by using the `with statement`_.  Anywhere in your code, when you enter a with block using a Fixture.Data instance, the data is loaded and you have an instance in which to reference the data.  When you exit, the data is torn down for you, regardless of whether there was an exception or not.  For example::

    from __future__ import with_statement
    with dbfixture.data(AuthorData, BookData) as data:
        assert in_stock(book_title=data.BookData.dune.title)    

.. _with statement: http://www.python.org/dev/peps/pep-0343/

Defining a custom LoadableFixture
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

It's possible to create your own LoadableFixture if you need to load data with something other than SQLAlchemy or SQLObject.

You'll need to subclass at least `fixture.loadable.loadable:LoadableFixture`_, possibly even `fixture.loadable.loadable:EnvLoadableFixture`_ or the more useful `fixture.loadable.loadable:DBLoadableFixture`_.  Here is a simple example for creating a fixture that hooks into some kind of database-centric loading mechanism::

    >>> loaded_items = set()
    >>> class Author(object):
    ...     '''This would be your actual storage object, i.e. data mapper.
    ...        For the sake of brevity, you'll have to imagine that it knows 
    ...        how to somehow store "author" data.'''
    ... 
    ...     name = None # gets set by the data set
    ... 
    ...     def save(self):
    ...         '''just one example of how to save your object.
    ...            there is no signature guideline for how this object 
    ...            should save itself (see the adapter below).'''
    ...         loaded_items.add(self)
    ...     def __repr__(self):
    ...         return "<%s name=%s>" % (self.__class__.__name__, self.name)
    ...
    >>> from fixture.loadable import DBLoadableFixture
    >>> class MyFixture(DBLoadableFixture):
    ...     '''This is the class you will instantiate, the one that knows how to 
    ...        load datasets'''
    ... 
    ...     class Medium(DBLoadableFixture.Medium):
    ...         '''This is an object that adapts a Fixture storage medium 
    ...            to the actual storage medium.'''
    ... 
    ...         def clear(self, obj):
    ...             '''where you need to expunge the obj'''
    ...             loaded_items.remove(obj)
    ... 
    ...         def visit_loader(self, loader):
    ...             '''a chance to reference any attributes from the loader.
    ...                this is called before save().'''
    ... 
    ...         def save(self, row, column_vals):
    ...             '''save data into your object using the provided 
    ...                fixture.dataset.DataRow instance'''
    ...             # instantiate your real object class (Author), which was set 
    ...             # in __init__ to self.medium ...
    ...             obj = self.medium() 
    ...             for c, val in column_vals:
    ...                 # column values become object attributes...
    ...                 setattr(obj, c, val)
    ...             obj.save()
    ...             # be sure to return the object:
    ...             return obj
    ... 
    ...     def create_transaction(self):
    ...         '''a chance to create a transaction.
    ...            two separate transactions are used: one during loading
    ...            and another during unloading.'''
    ...         class DummyTransaction(object):
    ...             def begin(self):
    ...                 pass
    ...             def commit(self): 
    ...                 pass
    ...             def rollback(self): 
    ...                 pass
    ...         t = DummyTransaction()
    ...         t.begin() # you must call begin yourself, if necessary
    ...         return t

Now let's load some data into the custom Fixture using a simple ``env`` mapping::

    >>> from fixture import DataSet
    >>> class AuthorData(DataSet):
    ...     class frank_herbert:
    ...         name="Frank Herbert"
    ...
    >>> fixture = MyFixture(env={'AuthorData': Author})
    >>> data = fixture.data(AuthorData)
    >>> data.setup()
    >>> loaded_items
    set([<Author name=Frank Herbert>])
    >>> data.teardown()
    >>> loaded_items
    set([])
    

.. _fixture.loadable.loadable:LoadableFixture: ../apidocs/fixture.loadable.loadable.LoadableFixture.html
.. _fixture.loadable.loadable:EnvLoadableFixture: ../apidocs/fixture.loadable.loadable.EnvLoadableFixture.html
.. _fixture.loadable.loadable:DBLoadableFixture: ../apidocs/fixture.loadable.loadable.DBLoadableFixture.html

.. api_only::
   The fixture.loadable module
   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
Class LoadableFixture knows how to load data into something useful.
Class EnvLoadableFixture An abstract fixture that can resolve DataSet objects from an env.
Class DBLoadableFixture An abstract fixture that will be loadable into a database.
Class DeferredStoredObject A stored representation of a row in a DatSet, deferred.
API Documentation for fixture, generated by pydoctor.