<filename>gae_app/lib/fixture/loadable/loadable.py

"""Abstract classes for :class:`fixture.base.Fixture` descendants that load / unload data

See :ref:`Using LoadableFixture<using-loadable-fixture>` for examples.

"""
# from __future__ import with_statement
__all__ = ['LoadableFixture', 'EnvLoadableFixture', 'DBLoadableFixture', 'DeferredStoredObject']
import sys, types
from fixture.base import Fixture
from fixture.util import ObjRegistry, _mklog
from fixture.style import OriginalStyle
from fixture.dataset import Ref, dataset_registry, DataRow, is_rowlike
from fixture.exc import UninitializedError, LoadError, UnloadError, StorageMediaNotFound
import logging

log     = _mklog("fixture.loadable")
treelog = _mklog("fixture.loadable.tree")

class StorageMediumAdapter(object):
    """common interface for working with storable objects.
    """
    def __init__(self, medium, dataset):
        self.medium = medium
        self.dataset = dataset
        self.transaction = None
    
    def __getattr__(self, name):
        return getattr(self.obj, name)
    
    def __repr__(self):
        return "%s at %s for %s" % (
                self.__class__.__name__, hex(id(self)), self.medium)
        
    def clear(self, obj):
        """Must clear the stored object.
        """
        raise NotImplementedError
    
    def clearall(self):
        """Must clear all stored objects.
        """
        log.info("CLEARING stored objects for %s", self.dataset)
        for obj in self.dataset.meta._stored_objects:
            try:
                self.clear(obj)
            except Exception, e:
                etype, val, tb = sys.exc_info()
                raise UnloadError(etype, val, self.dataset, 
                                     stored_object=obj), None, tb
        
    def save(self, row, column_vals):
        """Given a DataRow, must save it somehow.
        
        column_vals is an iterable of (column_name, column_value)
        """
        raise NotImplementedError
        
    def visit_loader(self, loader):
        """A chance to visit the LoadableFixture object.
        
        By default it does nothing.
        """
        pass

class LoadQueue(ObjRegistry):
    """Keeps track of what class instances were loaded.
    
    "level" is used like so:
        
    The lower the level, the lower that object is on the foreign key chain.  
    As the level increases, this means more foreign objects depend on the 
    local object.  Thus, objects need to be unloaded starting at the lowest 
    level and working up.  Also, since objects can appear multiple times in 
    foreign key chains, the queue only acknowledges the object at its 
    highest level, since this will ensure all dependencies get unloaded 
    before it.  
    
    """

    def __init__(self):
        ObjRegistry.__init__(self)
        self.tree = {}
        self.limit = {}
    
    def __repr__(self):
        return "<%s at %s>" % (
                self.__class__.__name__, hex(id(self)))
    
    def _pushid(self, id, level):
        if id in self.limit:
            # only store the object at its highest level:
            if level > self.limit[id]:
                self.tree[self.limit[id]].remove(id)
                del self.limit[id]
            else:
                return
        self.tree.setdefault(level, [])
        self.tree[level].append(id)
        self.limit[id] = level
    
    def clear(self):
        """clear internal registry"""
        ObjRegistry.clear(self)
        # this is an attempt to free up refs to database connections:
        self.tree = {}
        self.limit = {}
    
    def register(self, obj, level):
        """register this object as "loaded" at level
        """
        id = ObjRegistry.register(self, obj)
        self._pushid(id, level)
        return id
    
    def referenced(self, obj, level):
        """tell the queue that this object was referenced again at level.
        """
        id = self.id(obj)
        self._pushid(id, level)
    
    def to_unload(self):
        """yields a list of objects in an order suitable for unloading.
        """
        level_nums = self.tree.keys()
        level_nums.sort()
        treelog.info("*** unload order ***")
        for level in level_nums:
            unload_queue = self.tree[level]
            verbose_obj = []
            
            for id in unload_queue:
                obj = self.registry[id]
                verbose_obj.append(obj.__class__.__name__)
                yield obj
            
            treelog.info("%s. %s", level, verbose_obj)
            
class LoadableFixture(Fixture):
    """
    knows how to load data into something useful.
    
    This is an abstract class and cannot be used directly.  You can use a 
    LoadableFixture that already knows how to load into a specific medium, 
    such as SQLAlchemyFixture, or create your own to build your own to load 
    DataSet objects into custom storage media.

    Keyword Arguments:
    
    dataclass
        class to instantiate with datasets (defaults to that of Fixture)
    style
        a Style object to translate names with (defaults to NamedDataStyle)
    medium
        optional LoadableFixture.StorageMediumAdapter to store DataSet 
        objects with
    
    """
    style = OriginalStyle()
    dataclass = Fixture.dataclass
    
    def __init__(self, style=None, medium=None, **kw):
        Fixture.__init__(self, loader=self, **kw)
        if style:
            self.style = style
        if medium:
            self.Medium = medium
        self.loaded = None
    
    StorageMediumAdapter = StorageMediumAdapter
    Medium = StorageMediumAdapter
    StorageMediaNotFound = StorageMediaNotFound
    LoadQueue = LoadQueue
    
    def attach_storage_medium(self, ds):
        """attach a :class:`StorageMediumAdapter` to DataSet"""
        raise NotImplementedError
    
    def begin(self, unloading=False):
        """begin loading"""
        if not unloading:
            self.loaded = self.LoadQueue()
    
    def commit(self):
        """commit load transaction"""
        raise NotImplementedError
    
    def load(self, data):
        """load data"""
        def loader():
            for ds in data:
                self.load_dataset(ds)
        self.wrap_in_transaction(loader, unloading=False)
        
    def load_dataset(self, ds, level=1):
        """load this dataset and all its dependent datasets.
        
        level is essentially the order of processing (going from dataset to 
        dependent datasets).  Child datasets are always loaded before the 
        parent.  The level is important for visualizing the chain of 
        dependencies : 0 is the bottom, and thus should be the first set of 
        objects unloaded
        
        """
        is_parent = level==1
        
        levsep = is_parent and "/--------" or "|__.."
        treelog.info(
            "%s%s%s (%s)", level * '  ', levsep, ds.__class__.__name__, 
                                            (is_parent and "parent" or level))
        
        for ref_ds in ds.meta.references:
            r = ref_ds.shared_instance(default_refclass=self.dataclass)
            new_level = level+1
            self.load_dataset(r,  level=new_level)
        
        self.attach_storage_medium(ds)
        
        if ds in self.loaded:
            # keep track of its order but don't actually load it...
            self.loaded.referenced(ds, level)
            return
        
        log.info("LOADING rows in %s", ds)
        ds.meta.storage_medium.visit_loader(self)
        registered = False
        for key, row in ds:
            try:
                self.resolve_row_references(ds, row)
                if not isinstance(row, DataRow):
                    row = row(ds)
                def column_vals():
                    for c in row.columns():
                        yield (c, self.resolve_stored_object(getattr(row, c)))
                obj = ds.meta.storage_medium.save(row, column_vals())
                ds.meta._stored_objects.store(key, obj)
                # save the instance in place of the class...
                ds._setdata(key, row)
                if not registered:
                    self.loaded.register(ds, level)
                    registered = True
                
            except Exception, e:
                etype, val, tb = sys.exc_info()
                raise LoadError(etype, val, ds, key=key, row=row), None, tb
    
    def resolve_row_references(self, current_dataset, row):        
        """resolve this DataRow object's referenced values.
        """
        def resolved_rowlike(rowlike):
            key = rowlike.__name__
            if rowlike._dataset is type(current_dataset):
                return DeferredStoredObject(rowlike._dataset, key)
            loaded_ds = self.loaded[rowlike._dataset]
            return loaded_ds.meta._stored_objects.get_object(key)
        def resolve_stored_object(candidate):            
            if is_rowlike(candidate):
                return resolved_rowlike(candidate)
            else:
                # then it is the stored object itself.  this would happen if 
                # there is a reciprocal foreign key (i.e. organization has a 
                # parent organization)
                return candidate
                
        for name in row.columns():
            val = getattr(row, name)
            if type(val) in (types.ListType, types.TupleType):
                # i.e. categories = [python, ruby]
                setattr(row, name, map(resolve_stored_object, val))
            elif is_rowlike(val):
                # i.e. category = python
                setattr(row, name, resolved_rowlike(val))
            elif isinstance(val, Ref.Value):
                # i.e. category_id = python.id.
                ref = val.ref
                # now the ref will return the attribute from a stored object 
                # when __get__ is invoked
                ref.dataset_obj = self.loaded[ref.dataset_class]
    
    def rollback(self):
        """rollback load transaction"""
        raise NotImplementedError
    
    def then_finally(self, unloading=False):
        """called in a finally block after load transaction has begun"""
        pass
    
    def unload(self):
        """unload data"""
        if self.loaded is None:
            raise UninitializedError(
                "Cannot unload data because it has not yet been loaded in this "
                "process.  Call data.setup() before data.teardown()")
        def unloader():
            for dataset in self.loaded.to_unload():
                self.unload_dataset(dataset)
            self.loaded.clear()
            dataset_registry.clear()
        self.wrap_in_transaction(unloader, unloading=True)
    
    def unload_dataset(self, dataset):
        """unload data stored for this dataset"""
        dataset.meta.storage_medium.clearall()
    
    def wrap_in_transaction(self, routine, unloading=False):
        """call routine in a load transaction"""
        self.begin(unloading=unloading)
        try:
            try:
                routine()
            except:
                self.rollback()
                raise
            else:
                self.commit()
        finally:
            self.then_finally(unloading=unloading)

class EnvLoadableFixture(LoadableFixture):
    """An abstract fixture that can resolve DataSet objects from an env.
    
    Keyword "env" should be a dict or a module if not None.
    According to the style rules, the env will be used to find objects by name.
    
    """
    def __init__(self, env=None, **kw):
        LoadableFixture.__init__(self, **kw)
        self.env = env
    
    def attach_storage_medium(self, ds):
        """Lookup a storage medium in the ``env`` and attach it to a DataSet.
        
        A storage medium is looked up by name.  If a specific name has not been declared in the DataSet 
        then it will be guessed using the :meth:`Style.guess_storable_name <fixture.style.Style.guess_storable_name>` method.  
        
        Once a name is found (typically the name of a DataSet class, say, EmployeeData) then it is looked up 
        in the ``env`` which is expected to be a dict or module like object.
        
        The method first tries ``env.get('EmployeeData')`` then ``getattr(env, 'EmployeeData')``.
        
        The return value is the storage medium (i.e. a data mapper for the Employees table)
        
        Note that a :mod:`style <fixture.style>` might translate a name to maintain a consistent 
        naming scheme between DataSet classes and data mappers.
        
        """
        if ds.meta.storage_medium is not None:
            # already attached...
            return
        
        storable = ds.meta.storable
        
        if not storable:
            if not ds.meta.storable_name:
                ds.meta.storable_name = self.style.guess_storable_name(
                                                        ds.__class__.__name__)
        
            if hasattr(self.env, 'get'):
                storable = self.env.get(ds.meta.storable_name, None)
            if not storable:
                if hasattr(self.env, ds.meta.storable_name):
                    try:
                        storable = getattr(self.env, ds.meta.storable_name)
                    except AttributeError:
                        pass
        
            if not storable:
                repr_env = repr(type(self.env))
                if hasattr(self.env, '__module__'):
                    repr_env = "%s from '%s'" % (repr_env, self.env.__module__)
                
                raise self.StorageMediaNotFound(
                    "could not find %s '%s' for "
                    "dataset %s in self.env (%s)" % (
                        self.Medium, ds.meta.storable_name, ds, repr_env))
                        
        if storable == ds.__class__:
            raise ValueError(
                "cannot use %s %s as a storable object of itself! "
                "(perhaps your style object was not configured right?)" % (
                                        ds.__class__.__name__, ds.__class__))
        ds.meta.storage_medium = self.Medium(storable, ds)
        
    def resolve_stored_object(self, column_val):
        if type(column_val)==DeferredStoredObject:
            return column_val.get_stored_object_from_loader(self)
        else:
            return column_val

class DBLoadableFixture(EnvLoadableFixture):
    """
    An abstract fixture that can load a DataSet into a database like thing.
    
    More specifically, one that forces its implementation to run atomically 
    (within a begin / commit / rollback block).
    """
    def __init__(self, dsn=None, **kw):
        EnvLoadableFixture.__init__(self, **kw)
        self.dsn = dsn
        self.transaction = None
    
    def begin(self, unloading=False):
        """begin loading data"""
        EnvLoadableFixture.begin(self, unloading=unloading)
        self.transaction = self.create_transaction()
    
    def commit(self):
        """call transaction.commit() on transaction returned by :meth:`DBLoadableFixture.create_transaction`"""
        self.transaction.commit()
    
    def create_transaction(self):
        """must return a transaction object that implements commit() and rollback()
        
        .. note:: transaction.begin() will not be called.  If that is necessary then call begin before returning the object.
        
        """
        raise NotImplementedError
    
    def rollback(self):
        """call transaction.rollback() on transaction returned by :meth:`DBLoadableFixture.create_transaction`"""
        self.transaction.rollback()

class DeferredStoredObject(object):
    """A stored representation of a row in a DataSet, deferred.
    
    The actual stored object can only be resolved by the StoredMediumAdapter 
    itself
    
    Imagine...::
    
        >>> from fixture import DataSet
        >>> class PersonData(DataSet):
        ...     class adam:
        ...         father=None
        ...     class eve:
        ...         father=None
        ...     class jenny:
        ...         pass
        ...     jenny.father = adam
        ... 
    
    This would be a way to indicate that jenny's father is adam.  This class 
    will encapsulate that reference so it can be resolved as close to when it 
    was created as possible.
    
    """
    def __init__(self, dataset, key):
        self.dataset = dataset
        self.key = key
    
    def get_stored_object_from_loader(self, loader):
        loaded_ds = loader.loaded[self.dataset]
        return loaded_ds.meta._stored_objects.get_object(self.key)

if __name__ == '__main__':
    import doctest
    doctest.testmod()
    

<filename>gae_app/lib/fixture/loadable/loadable.py

"""Abstract classes for :class:`fixture.base.Fixture` descendants that load / unload data

See :ref:`Using LoadableFixture<using-loadable-fixture>` for examples.

"""
# from __future__ import with_statement
__all__ = ['LoadableFixture', 'EnvLoadableFixture', 'DBLoadableFixture', 'DeferredStoredObject']
import sys, types
from fixture.base import Fixture
from fixture.util import ObjRegistry, _mklog
from fixture.style import OriginalStyle
from fixture.dataset import Ref, dataset_registry, DataRow, is_rowlike
from fixture.exc import UninitializedError, LoadError, UnloadError, StorageMediaNotFound
import logging

log     = _mklog("fixture.loadable")
treelog = _mklog("fixture.loadable.tree")

class StorageMediumAdapter(object):
    """common interface for working with storable objects.
    """
    def __init__(self, medium, dataset):
        self.medium = medium
        self.dataset = dataset
        self.transaction = None
    
    def __getattr__(self, name):
        return getattr(self.obj, name)
    
    def __repr__(self):
        return "%s at %s for %s" % (
                self.__class__.__name__, hex(id(self)), self.medium)
        
    def clear(self, obj):
        """Must clear the stored object.
        """
        raise NotImplementedError
    
    def clearall(self):
        """Must clear all stored objects.
        """
        log.info("CLEARING stored objects for %s", self.dataset)
        for obj in self.dataset.meta._stored_objects:
            try:
                self.clear(obj)
            except Exception, e:
                etype, val, tb = sys.exc_info()
                raise UnloadError(etype, val, self.dataset, 
                                     stored_object=obj), None, tb
        
    def save(self, row, column_vals):
        """Given a DataRow, must save it somehow.
        
        column_vals is an iterable of (column_name, column_value)
        """
        raise NotImplementedError
        
    def visit_loader(self, loader):
        """A chance to visit the LoadableFixture object.
        
        By default it does nothing.
        """
        pass

class LoadQueue(ObjRegistry):
    """Keeps track of what class instances were loaded.
    
    "level" is used like so:
        
    The lower the level, the lower that object is on the foreign key chain.  
    As the level increases, this means more foreign objects depend on the 
    local object.  Thus, objects need to be unloaded starting at the lowest 
    level and working up.  Also, since objects can appear multiple times in 
    foreign key chains, the queue only acknowledges the object at its 
    highest level, since this will ensure all dependencies get unloaded 
    before it.  
    
    """

    def __init__(self):
        ObjRegistry.__init__(self)
        self.tree = {}
        self.limit = {}
    
    def __repr__(self):
        return "<%s at %s>" % (
                self.__class__.__name__, hex(id(self)))
    
    def _pushid(self, id, level):
        if id in self.limit:
            # only store the object at its highest level:
            if level > self.limit[id]:
                self.tree[self.limit[id]].remove(id)
                del self.limit[id]
            else:
                return
        self.tree.setdefault(level, [])
        self.tree[level].append(id)
        self.limit[id] = level
    
    def clear(self):
        """clear internal registry"""
        ObjRegistry.clear(self)
        # this is an attempt to free up refs to database connections:
        self.tree = {}
        self.limit = {}
    
    def register(self, obj, level):
        """register this object as "loaded" at level
        """
        id = ObjRegistry.register(self, obj)
        self._pushid(id, level)
        return id
    
    def referenced(self, obj, level):
        """tell the queue that this object was referenced again at level.
        """
        id = self.id(obj)
        self._pushid(id, level)
    
    def to_unload(self):
        """yields a list of objects in an order suitable for unloading.
        """
        level_nums = self.tree.keys()
        level_nums.sort()
        treelog.info("*** unload order ***")
        for level in level_nums:
            unload_queue = self.tree[level]
            verbose_obj = []
            
            for id in unload_queue:
                obj = self.registry[id]
                verbose_obj.append(obj.__class__.__name__)
                yield obj
            
            treelog.info("%s. %s", level, verbose_obj)
            
class LoadableFixture(Fixture):
    """
    knows how to load data into something useful.
    
    This is an abstract class and cannot be used directly.  You can use a 
    LoadableFixture that already knows how to load into a specific medium, 
    such as SQLAlchemyFixture, or create your own to build your own to load 
    DataSet objects into custom storage media.

    Keyword Arguments:
    
    dataclass
        class to instantiate with datasets (defaults to that of Fixture)
    style
        a Style object to translate names with (defaults to NamedDataStyle)
    medium
        optional LoadableFixture.StorageMediumAdapter to store DataSet 
        objects with
    
    """
    style = OriginalStyle()
    dataclass = Fixture.dataclass
    
    def __init__(self, style=None, medium=None, **kw):
        Fixture.__init__(self, loader=self, **kw)
        if style:
            self.style = style
        if medium:
            self.Medium = medium
        self.loaded = None
    
    StorageMediumAdapter = StorageMediumAdapter
    Medium = StorageMediumAdapter
    StorageMediaNotFound = StorageMediaNotFound
    LoadQueue = LoadQueue
    
    def attach_storage_medium(self, ds):
        """attach a :class:`StorageMediumAdapter` to DataSet"""
        raise NotImplementedError
    
    def begin(self, unloading=False):
        """begin loading"""
        if not unloading:
            self.loaded = self.LoadQueue()
    
    def commit(self):
        """commit load transaction"""
        raise NotImplementedError
    
    def load(self, data):
        """load data"""
        def loader():
            for ds in data:
                self.load_dataset(ds)
        self.wrap_in_transaction(loader, unloading=False)
        
    def load_dataset(self, ds, level=1):
        """load this dataset and all its dependent datasets.
        
        level is essentially the order of processing (going from dataset to 
        dependent datasets).  Child datasets are always loaded before the 
        parent.  The level is important for visualizing the chain of 
        dependencies : 0 is the bottom, and thus should be the first set of 
        objects unloaded
        
        """
        is_parent = level==1
        
        levsep = is_parent and "/--------" or "|__.."
        treelog.info(
            "%s%s%s (%s)", level * '  ', levsep, ds.__class__.__name__, 
                                            (is_parent and "parent" or level))
        
        for ref_ds in ds.meta.references:
            r = ref_ds.shared_instance(default_refclass=self.dataclass)
            new_level = level+1
            self.load_dataset(r,  level=new_level)
        
        self.attach_storage_medium(ds)
        
        if ds in self.loaded:
            # keep track of its order but don't actually load it...
            self.loaded.referenced(ds, level)
            return
        
        log.info("LOADING rows in %s", ds)
        ds.meta.storage_medium.visit_loader(self)
        registered = False
        for key, row in ds:
            try:
                self.resolve_row_references(ds, row)
                if not isinstance(row, DataRow):
                    row = row(ds)
                def column_vals():
                    for c in row.columns():
                        yield (c, self.resolve_stored_object(getattr(row, c)))
                obj = ds.meta.storage_medium.save(row, column_vals())
                ds.meta._stored_objects.store(key, obj)
                # save the instance in place of the class...
                ds._setdata(key, row)
                if not registered:
                    self.loaded.register(ds, level)
                    registered = True
                
            except Exception, e:
                etype, val, tb = sys.exc_info()
                raise LoadError(etype, val, ds, key=key, row=row), None, tb
    
    def resolve_row_references(self, current_dataset, row):        
        """resolve this DataRow object's referenced values.
        """
        def resolved_rowlike(rowlike):
            key = rowlike.__name__
            if rowlike._dataset is type(current_dataset):
                return DeferredStoredObject(rowlike._dataset, key)
            loaded_ds = self.loaded[rowlike._dataset]
            return loaded_ds.meta._stored_objects.get_object(key)
        def resolve_stored_object(candidate):            
            if is_rowlike(candidate):
                return resolved_rowlike(candidate)
            else:
                # then it is the stored object itself.  this would happen if 
                # there is a reciprocal foreign key (i.e. organization has a 
                # parent organization)
                return candidate
                
        for name in row.columns():
            val = getattr(row, name)
            if type(val) in (types.ListType, types.TupleType):
                # i.e. categories = [python, ruby]
                setattr(row, name, map(resolve_stored_object, val))
            elif is_rowlike(val):
                # i.e. category = python
                setattr(row, name, resolved_rowlike(val))
            elif isinstance(val, Ref.Value):
                # i.e. category_id = python.id.
                ref = val.ref
                # now the ref will return the attribute from a stored object 
                # when __get__ is invoked
                ref.dataset_obj = self.loaded[ref.dataset_class]
    
    def rollback(self):
        """rollback load transaction"""
        raise NotImplementedError
    
    def then_finally(self, unloading=False):
        """called in a finally block after load transaction has begun"""
        pass
    
    def unload(self):
        """unload data"""
        if self.loaded is None:
            raise UninitializedError(
                "Cannot unload data because it has not yet been loaded in this "
                "process.  Call data.setup() before data.teardown()")
        def unloader():
            for dataset in self.loaded.to_unload():
                self.unload_dataset(dataset)
            self.loaded.clear()
            dataset_registry.clear()
        self.wrap_in_transaction(unloader, unloading=True)
    
    def unload_dataset(self, dataset):
        """unload data stored for this dataset"""
        dataset.meta.storage_medium.clearall()
    
    def wrap_in_transaction(self, routine, unloading=False):
        """call routine in a load transaction"""
        self.begin(unloading=unloading)
        try:
            try:
                routine()
            except:
                self.rollback()
                raise
            else:
                self.commit()
        finally:
            self.then_finally(unloading=unloading)

class EnvLoadableFixture(LoadableFixture):
    """An abstract fixture that can resolve DataSet objects from an env.
    
    Keyword "env" should be a dict or a module if not None.
    According to the style rules, the env will be used to find objects by name.
    
    """
    def __init__(self, env=None, **kw):
        LoadableFixture.__init__(self, **kw)
        self.env = env
    
    def attach_storage_medium(self, ds):
        """Lookup a storage medium in the ``env`` and attach it to a DataSet.
        
        A storage medium is looked up by name.  If a specific name has not been declared in the DataSet 
        then it will be guessed using the :meth:`Style.guess_storable_name <fixture.style.Style.guess_storable_name>` method.  
        
        Once a name is found (typically the name of a DataSet class, say, EmployeeData) then it is looked up 
        in the ``env`` which is expected to be a dict or module like object.
        
        The method first tries ``env.get('EmployeeData')`` then ``getattr(env, 'EmployeeData')``.
        
        The return value is the storage medium (i.e. a data mapper for the Employees table)
        
        Note that a :mod:`style <fixture.style>` might translate a name to maintain a consistent 
        naming scheme between DataSet classes and data mappers.
        
        """
        if ds.meta.storage_medium is not None:
            # already attached...
            return
        
        storable = ds.meta.storable
        
        if not storable:
            if not ds.meta.storable_name:
                ds.meta.storable_name = self.style.guess_storable_name(
                                                        ds.__class__.__name__)
        
            if hasattr(self.env, 'get'):
                storable = self.env.get(ds.meta.storable_name, None)
            if not storable:
                if hasattr(self.env, ds.meta.storable_name):
                    try:
                        storable = getattr(self.env, ds.meta.storable_name)
                    except AttributeError:
                        pass
        
            if not storable:
                repr_env = repr(type(self.env))
                if hasattr(self.env, '__module__'):
                    repr_env = "%s from '%s'" % (repr_env, self.env.__module__)
                
                raise self.StorageMediaNotFound(
                    "could not find %s '%s' for "
                    "dataset %s in self.env (%s)" % (
                        self.Medium, ds.meta.storable_name, ds, repr_env))
                        
        if storable == ds.__class__:
            raise ValueError(
                "cannot use %s %s as a storable object of itself! "
                "(perhaps your style object was not configured right?)" % (
                                        ds.__class__.__name__, ds.__class__))
        ds.meta.storage_medium = self.Medium(storable, ds)
        
    def resolve_stored_object(self, column_val):
        if type(column_val)==DeferredStoredObject:
            return column_val.get_stored_object_from_loader(self)
        else:
            return column_val

class DBLoadableFixture(EnvLoadableFixture):
    """
    An abstract fixture that can load a DataSet into a database like thing.
    
    More specifically, one that forces its implementation to run atomically 
    (within a begin / commit / rollback block).
    """
    def __init__(self, dsn=None, **kw):
        EnvLoadableFixture.__init__(self, **kw)
        self.dsn = dsn
        self.transaction = None
    
    def begin(self, unloading=False):
        """begin loading data"""
        EnvLoadableFixture.begin(self, unloading=unloading)
        self.transaction = self.create_transaction()
    
    def commit(self):
        """call transaction.commit() on transaction returned by :meth:`DBLoadableFixture.create_transaction`"""
        self.transaction.commit()
    
    def create_transaction(self):
        """must return a transaction object that implements commit() and rollback()
        
        .. note:: transaction.begin() will not be called.  If that is necessary then call begin before returning the object.
        
        """
        raise NotImplementedError
    
    def rollback(self):
        """call transaction.rollback() on transaction returned by :meth:`DBLoadableFixture.create_transaction`"""
        self.transaction.rollback()

class DeferredStoredObject(object):
    """A stored representation of a row in a DataSet, deferred.
    
    The actual stored object can only be resolved by the StoredMediumAdapter 
    itself
    
    Imagine...::
    
        >>> from fixture import DataSet
        >>> class PersonData(DataSet):
        ...     class adam:
        ...         father=None
        ...     class eve:
        ...         father=None
        ...     class jenny:
        ...         pass
        ...     jenny.father = adam
        ... 
    
    This would be a way to indicate that jenny's father is adam.  This class 
    will encapsulate that reference so it can be resolved as close to when it 
    was created as possible.
    
    """
    def __init__(self, dataset, key):
        self.dataset = dataset
        self.key = key
    
    def get_stored_object_from_loader(self, loader):
        loaded_ds = loader.loaded[self.dataset]
        return loaded_ds.meta._stored_objects.get_object(self.key)

if __name__ == '__main__':
    import doctest
    doctest.testmod()
    

Abstract classes for :class:`fixture.base.Fixture` descendants that load / unload data  See :ref:`Using LoadableFixture<using-loadable-fixture>` for examples. # from __future__ import with_statement common interface for working with storable objects. Must clear the stored object. Must clear all stored objects. Given a DataRow, must save it somehow.                  column_vals is an iterable of (column_name, column_value) A chance to visit the LoadableFixture object.                  By default it does nothing. Keeps track of what class instances were loaded.          "level" is used like so:              The lower the level, the lower that object is on the foreign key chain.       As the level increases, this means more foreign objects depend on the      local object.  Thus, objects need to be unloaded starting at the lowest      level and working up.  Also, since objects can appear multiple times in      foreign key chains, the queue only acknowledges the object at its      highest level, since this will ensure all dependencies get unloaded      before it. # only store the object at its highest level: clear internal registry # this is an attempt to free up refs to database connections: register this object as "loaded" at level tell the queue that this object was referenced again at level. yields a list of objects in an order suitable for unloading. knows how to load data into something useful.          This is an abstract class and cannot be used directly.  You can use a      LoadableFixture that already knows how to load into a specific medium,      such as SQLAlchemyFixture, or create your own to build your own to load      DataSet objects into custom storage media.      Keyword Arguments:          dataclass         class to instantiate with datasets (defaults to that of Fixture)     style         a Style object to translate names with (defaults to NamedDataStyle)     medium         optional LoadableFixture.StorageMediumAdapter to store DataSet          objects with attach a :class:`StorageMediumAdapter` to DataSet begin loading commit load transaction load data load this dataset and all its dependent datasets.                  level is essentially the order of processing (going from dataset to          dependent datasets).  Child datasets are always loaded before the          parent.  The level is important for visualizing the chain of          dependencies : 0 is the bottom, and thus should be the first set of          objects unloaded # keep track of its order but don't actually load it... # save the instance in place of the class... resolve this DataRow object's referenced values. # then it is the stored object itself.  this would happen if # there is a reciprocal foreign key (i.e. organization has a # parent organization) # i.e. categories = [python, ruby] # i.e. category = python # i.e. category_id = python.id. # now the ref will return the attribute from a stored object # when __get__ is invoked rollback load transaction called in a finally block after load transaction has begun unload data unload data stored for this dataset call routine in a load transaction An abstract fixture that can resolve DataSet objects from an env.          Keyword "env" should be a dict or a module if not None.     According to the style rules, the env will be used to find objects by name. Lookup a storage medium in the ``env`` and attach it to a DataSet.                  A storage medium is looked up by name.  If a specific name has not been declared in the DataSet          then it will be guessed using the :meth:`Style.guess_storable_name <fixture.style.Style.guess_storable_name>` method.                    Once a name is found (typically the name of a DataSet class, say, EmployeeData) then it is looked up          in the ``env`` which is expected to be a dict or module like object.                  The method first tries ``env.get('EmployeeData')`` then ``getattr(env, 'EmployeeData')``.                  The return value is the storage medium (i.e. a data mapper for the Employees table)                  Note that a :mod:`style <fixture.style>` might translate a name to maintain a consistent          naming scheme between DataSet classes and data mappers. # already attached... An abstract fixture that can load a DataSet into a database like thing.          More specifically, one that forces its implementation to run atomically      (within a begin / commit / rollback block). begin loading data call transaction.commit() on transaction returned by :meth:`DBLoadableFixture.create_transaction` must return a transaction object that implements commit() and rollback()                  .. note:: transaction.begin() will not be called.  If that is necessary then call begin before returning the object. call transaction.rollback() on transaction returned by :meth:`DBLoadableFixture.create_transaction` A stored representation of a row in a DataSet, deferred.          The actual stored object can only be resolved by the StoredMediumAdapter      itself          Imagine...::              >>> from fixture import DataSet         >>> class PersonData(DataSet):         ...     class adam:         ...         father=None         ...     class eve:         ...         father=None         ...     class jenny:         ...         pass         ...     jenny.father = adam         ...           This would be a way to indicate that jenny's father is adam.  This class      will encapsulate that reference so it can be resolved as close to when it      was created as possible.