# define the mapper. we will make "convenient" property
# names vs. the more verbose names in the table definition
-TreeNode.mapper=assignmapper(tables.trees, class_=TreeNode, properties=dict(
+assign_mapper(TreeNode, tables.trees, properties=dict(
id=tables.trees.c.node_id,
name=tables.trees.c.node_name,
parent_id=tables.trees.c.parent_node_id,
used to create the initial value. The definition for this attribute is
wrapped up into a callable which is then stored in the classes'
dictionary of "class managed" attributes. When instances of the class
- are created and the attribute first referenced, the callable is invoked to
- create the new history container. Extra keyword arguments can be sent which
+ are created and the attribute first referenced, the callable is invoked with
+ the new object instance as an argument to create the new history container.
+ Extra keyword arguments can be sent which
will be passed along to newly created history containers."""
def createprop(obj):
if callable_ is not None:
from properties import *
import mapper as mapperlib
-__all__ = ['relation', 'eagerload', 'lazyload', 'noload', 'assignmapper',
+__all__ = ['relation', 'eagerload', 'lazyload', 'noload', 'deferred', 'assignmapper', 'column', 'deferred',
'mapper', 'clear_mappers', 'objectstore', 'sql', 'extension', 'class_mapper', 'object_mapper', 'MapperExtension',
'ColumnProperty', 'assign_mapper'
]
live=live, association=association, lazy=lazy,
selectalias=selectalias, order_by=order_by, attributeext=attributeext)
+def column(*columns):
+ return ColumnProperty(*columns)
+
+def deferred(*columns):
+ return DeferredColumnProperty(*columns)
+
+
class assignmapper(object):
"""provides a property object that will instantiate a Mapper for a given class the first
time it is called off of the object. This is useful for attaching a Mapper to a class
crit = []
for i in range(0, len(self.table.primary_key)):
crit.append(s3.primary_key[i] == self.table.primary_key[i])
- statement = sql.select([self.table], sql.and_(*crit), use_labels=True)
+ statement = sql.select([], sql.and_(*crit), from_obj=[self.table], use_labels=True)
if kwargs.has_key('order_by'):
statement.order_by(*kwargs['order_by'])
else:
statement.order_by(order_by)
else:
- statement = sql.select([self.table], whereclause, use_labels=True, **kwargs)
+ statement = sql.select([], whereclause, from_obj=[self.table], use_labels=True, **kwargs)
if not kwargs.get('distinct', False) and order_by is not None and kwargs.get('order_by', None) is None:
statement.order_by(order_by)
# plugin point
value.setup(key, statement, **kwargs)
return statement
+
def _identity_key(self, row):
return objectstore.get_row_key(row, self.class_, self.primarytable, self.pks_by_table[self.table])
def setup(self, key, statement, **options):
"""called when a statement is being constructed. """
return self
+
def init(self, key, parent):
"""called when the MapperProperty is first attached to a new parent Mapper."""
pass
class ColumnProperty(MapperProperty):
"""describes an object attribute that corresponds to a table column."""
def __init__(self, *columns):
- """the list of columns describes a single object property populating
- multiple columns, typcially across multiple tables"""
+ """the list of columns describes a single object property. if there
+ are multiple tables joined together for the mapper, this list represents
+ the equivalent column as it appears across each table."""
self.columns = list(columns)
def getattr(self, object):
def _copy(self):
return ColumnProperty(*self.columns)
+ def setup(self, key, statement, eagertable=None, **options):
+ for c in self.columns:
+ if eagertable is not None:
+ statement.append_column(eagertable._get_col_by_original(c))
+ else:
+ statement.append_column(c)
+
def init(self, key, parent):
self.key = key
# establish a SmartProperty property manager on the object for this key
if isnew:
instance.__dict__[self.key] = row[self.columns[0]]
+class DeferredColumnProperty(ColumnProperty):
+ """describes an object attribute that corresponds to a table column, which also
+ will "lazy load" its value from the table. this is per-column lazy loading."""
+
+ def __init__(self, *columns, **kwargs):
+ self.isoption = kwargs.get('isoption', False)
+ ColumnProperty.__init__(self, *columns)
+
+ def hash_key(self):
+ return "DeferredColumnProperty(%s)" % repr([hash_key(c) for c in self.columns])
+
+ def _copy(self):
+ return DeferredColumnProperty(*self.columns)
+
+ def setup_loader(self, instance):
+ def lazyload():
+ clause = sql.and_()
+ for primary_key in self.parent.pks_by_table[self.parent.primarytable]:
+ clause.clauses.append(primary_key == self.parent._getattrbycolumn(instance, primary_key))
+ return sql.select([self.parent.table.c[self.key]], clause).scalar()
+ return lazyload
+
+ def _is_primary(self):
+ """a return value of True indicates we are the primary MapperProperty for this loader's
+ attribute on our mapper's class. It means we can set the object's attribute behavior
+ at the class level. otherwise we have to set attribute behavior on a per-instance level."""
+ return self.parent._is_primary_mapper and not self.isoption
+
+ def setup(self, key, statement, **options):
+ pass
+
+ def init(self, key, parent):
+ self.key = key
+ self.parent = parent
+ # establish a SmartProperty property manager on the object for this key,
+ # containing a callable to load in the attribute
+ if parent._is_primary_mapper():
+ objectstore.uow().register_attribute(parent.class_, key, uselist=False, callable_=lambda i:self.setup_loader(i))
+
+ def execute(self, instance, row, identitykey, imap, isnew):
+ if isnew:
+ if not self._is_primary():
+ objectstore.global_attributes.create_history(instance, self.key, False, callable_=self.setup_loader(instance))
+ else:
+ objectstore.global_attributes.reset_history(instance, self.key)
+
mapper.ColumnProperty = ColumnProperty
class PropertyLoader(MapperProperty):
statement.order_by(*self.eager_order_by)
statement.append_from(statement._outerjoin)
- statement.append_column(self.eagertarget)
+ #statement.append_column(self.eagertarget)
recursion_stack[self] = True
try:
for key, value in self.mapper.props.iteritems():
if recursion_stack.has_key(value):
raise "Circular eager load relationship detected on " + str(self.mapper) + " " + key + repr(self.mapper.props)
- value.setup(key, statement, recursion_stack=recursion_stack)
+ value.setup(key, statement, recursion_stack=recursion_stack, eagertable=self.eagertarget)
finally:
del recursion_stack[self]
objectstore.commit()
self.echo(repr(AddressUser.mapper.select(AddressUser.c.user_name == 'jack')))
+
+class DeferredTest(MapperSuperTest):
+
+ def testbasic(self):
+ """tests a basic "deferred" load"""
+
+ m = mapper(Order, orders, properties={
+ 'description':deferred(orders.c.description)
+ })
+
+ def go():
+ l = m.select()
+ o2 = l[2]
+ print o2.description
+
+ self.assert_sql(db, go, [
+ ("SELECT orders.order_id AS orders_order_id, orders.user_id AS orders_user_id, orders.isopen AS orders_isopen FROM orders ORDER BY orders.oid", {}),
+ ("SELECT orders.description FROM orders WHERE orders.order_id = :orders_order_id", {'orders_order_id':3})
+ ])
+
class LazyTest(MapperSuperTest):
def execute_compiled(self, compiled, parameters, **kwargs):
self.engine.logger = self.logger
statement = str(compiled)
+ statement = re.sub(r'\n', '', statement)
if self.assert_list is not None:
item = self.assert_list.pop()
repl = None
counter = 0
query = re.sub(r':([\w_]+)', repl, query)
-
+
self.unittest.assert_(statement == query and params == parameters, "Testing for query '%s' params %s, received '%s' with params %s" % (query, repr(params), statement, repr(parameters)))
return self.realexec(compiled, parameters, **kwargs)