import sqlalchemy.sql as sql
import sqlalchemy.schema as schema
import sqlalchemy.engine as engine
-import weakref, random
+import sqlalchemy.util as util
+import weakref, random, copy
-__ALL__ = ['eagermapper', 'eagerloader', 'mapper', 'lazyloader', 'lazymapper', 'identitymap', 'globalidentity']
+__ALL__ = ['eagermapper', 'eagerloader', 'lazymapper', 'lazyloader', 'eagerload', 'lazyload', 'mapper', 'lazyloader', 'lazymapper', 'identitymap', 'globalidentity']
+def lazymapper(class_, selectable, whereclause, table = None, properties = None, **options):
+ return lazyloader(mapper(class_, selectable, table = table, properties = properties, isroot = False), whereclause, **options)
+
def eagermapper(class_, selectable, whereclause, table = None, properties = None, **options):
return eagerloader(mapper(class_, selectable, table = table, properties = properties, isroot = False), whereclause, **options)
def eagerloader(mapper, whereclause, **options):
return EagerLoader(mapper, whereclause, **options)
+def lazyloader(mapper, whereclause, **options):
+ return LazyLoader(mapper, whereclause, **options)
+
def mapper(class_, selectable, table = None, properties = None, identitymap = None, use_smart_properties = True, isroot = True):
return Mapper(class_, selectable, table = table, properties = properties, identitymap = identitymap, use_smart_properties = use_smart_properties, isroot = isroot)
def globalidentity():
return _global_identitymap
+
+def eagerload(name):
+ return EagerOption(name)
+
+def lazyload(name):
+ return LazyOption(name)
class Mapper(object):
def __init__(self, class_, selectable, table = None, properties = None, identitymap = None, use_smart_properties = True, isroot = True):
if isroot:
self.init(self)
+ def set_property(self, key, prop):
+ self.props[key] = prop
+ prop.init(key, self, self.root)
+
def init(self, root):
+ self.root = root
self.identitymap = root.identitymap
[prop.init(key, self, root) for key, prop in self.props.iteritems()]
def get(self, id):
"""returns an instance of the object based on the given ID."""
pass
+
+ def compile(self, whereclause = None, **options):
+ """works like select, except returns the SQL statement object without
+ compiling or executing it"""
+ return self._compile(whereclause, **options)
+
+ def options(self, *options):
+ mapper = copy.copy(self)
+ for option in options:
+ option.process(mapper)
+ return mapper
def select(self, arg = None, **params):
"""selects instances of the object from the database.
of the attribute, determines if the item is saved. if smart attributes are not being
used, the item is saved unconditionally.
"""
- if not getattr(object, 'dirty', True):
- return
+ if getattr(object, 'dirty', True):
+ pass
+ # do the save
+ for prop in self.props.values():
+ prop.save(object, traverse, refetch)
def remove(self, object, traverse = True):
"""removes the object. traverse indicates attached objects should be removed as well."""
tf = Mapper.TableFinder()
selectable.accept_visitor(tf)
return tf.table
-
- def _select_whereclause(self, whereclause = None, **params):
+
+ def _compile(self, whereclause = None, **options):
statement = sql.select([self.selectable], whereclause)
for key, value in self.props.iteritems():
- value.setup(key, self.selectable, statement)
+ value.setup(key, self.selectable, statement, **options)
+ statement.use_labels = True
+ return statement
+
+ def _select_whereclause(self, whereclause = None, **params):
+ statement = self._compile(whereclause)
return self._select_statement(statement, **params)
def _select_statement(self, statement, **params):
# call further mapper properties on the row, to pull further
# instances from the row and possibly populate this item.
for key, prop in self.props.iteritems():
- prop.execute(instance, key, row, identitykey, localmap, exists)
+ prop.execute(instance, row, identitykey, localmap, exists)
# now add to the result list, but we only want to add
# to the result list uniquely, so get another identity map
result.append(instance)
-
+class MapperOption:
+ def process(self, mapper):
+ raise NotImplementedError()
+
class MapperProperty:
- def execute(self, instance, key, row, isduplicate):
+ def execute(self, instance, row, isduplicate):
"""called when the mapper receives a row. instance is the parent instance corresponding
to the row. """
raise NotImplementedError()
- def setup(self, key, primarytable, statement):
- """called when a statement is being constructed."""
- pass
+ def setup(self, key, primarytable, statement, **options):
+ """called when a statement is being constructed. """
+ return self
def init(self, key, parent, root):
"""called when the MapperProperty is first attached to a new parent Mapper."""
pass
-
+ def save(self, object, traverse, refetch):
+ pass
+ def delete(self, object):
+ pass
+
class ColumnProperty(MapperProperty):
def __init__(self, column):
self.column = column
def init(self, key, parent, root):
+ self.key = key
if root.use_smart_properties:
self.use_smart = True
if not hasattr(parent.class_, key):
else:
self.use_smart = False
- def execute(self, instance, key, row, identitykey, localmap, isduplicate):
+ def execute(self, instance, row, identitykey, localmap, isduplicate):
if not isduplicate:
if self.use_smart:
- instance.__dict__[key] = row[self.column.label]
+ instance.__dict__[self.key] = row[self.column.label]
else:
- setattr(instance, key, row[self.column.label])
+ setattr(instance, self.key, row[self.column.label])
-
-class EagerLoader(MapperProperty):
+
+class PropertyLoader(MapperProperty):
def __init__(self, mapper, whereclause, **options):
self.mapper = mapper
self.whereclause = whereclause
-
+
def init(self, key, parent, root):
+ self.key = key
self.mapper.init(root)
+
+ def save(self, object, traverse, refetch):
+ self.mapper.save(object, )
+ def delete(self):
+ self.mapper.delete()
- def setup(self, key, primarytable, statement):
+class LazyLoader(PropertyLoader):
+ pass
+
+class EagerLoader(PropertyLoader):
+ def setup(self, key, primarytable, statement, **options):
"""add a left outer join to the statement thats being constructed"""
targettable = self.mapper.selectable
if statement.whereclause is not None:
- #if the whereclause of the statement contains the table we eager load against,
- # "aliasize" the whereclause into a new selectable unit
- for target in [targettable]: # + self.whereclause._get_from_objects():
+ # if the whereclause of the statement references tables that are also
+ # in the outer join we are constructing, then convert those objects to
+ # reference "aliases" of those tables so that their where condition does not interfere
+ # with ours
+ targets = util.Set([targettable] + self.whereclause._get_from_objects())
+ del targets[primarytable]
+ for target in targets:
aliasizer = Aliasizer(target, "aliased_" + target.name + "_" + hex(random.randint(0, 65535))[2:])
statement.whereclause.accept_visitor(aliasizer)
statement.append_from(aliasizer.alias)
for key, value in self.mapper.props.iteritems():
value.setup(key, self.mapper.selectable, statement)
- def execute(self, instance, key, row, identitykey, localmap, isduplicate):
+ def execute(self, instance, row, identitykey, localmap, isduplicate):
"""receive a row. tell our mapper to look for a new object instance in the row, and attach
it to a list on the parent instance."""
try:
- list = getattr(instance, key)
+ list = getattr(instance, self.key)
except AttributeError:
list = []
- setattr(instance, key, list)
+ setattr(instance, self.key, list)
self.mapper._instance(row, localmap, list)
+class EagerOption(MapperOption):
+ """an option that switches a PropertyLoader to be an EagerLoader"""
+ def __init__(self, key):
+ self.key = key
+
+ def process(self, mapper):
+ oldprop = mapper.props[self.key]
+ mapper.set_property(self.key, EagerLoader(oldprop.mapper, oldprop.whereclause))
+
+class LazyOption(MapperOption):
+ """an option that switches a PropertyLoader to be a LazyLoader"""
+ def __init__(self, key):
+ self.key = key
+
+ def process(self, mapper):
+ oldprop = mapper.props[self.key]
+ mapper.set_property(self.key, LazyLoader(oldprop.mapper, oldprop.whereclause))
+
class Aliasizer(sql.ClauseVisitor):
def __init__(self, table, aliasname):
self.table = table