import sqlalchemy.util as util
import objectstore
import sys
+import weakref
-mapper_registry = {}
+# a dictionary mapping classes to their primary mappers
+mapper_registry = weakref.WeakKeyDictionary()
class Mapper(object):
"""Persists object instances to and from schema.Table objects via the sql package.
Instances of this class should be constructed through this package's mapper() or
relation() function."""
def __init__(self,
- hashkey,
class_,
table,
primarytable = None,
allow_column_override = False,
**kwargs):
- self.copyargs = {
- 'class_':class_,
- 'table':table,
- 'properties':properties or {},
- 'primary_key':primary_key,
- 'is_primary':None,
- 'inherits':inherits,
- 'inherit_condition':inherit_condition,
- 'extension':extension,
- 'order_by':order_by
- }
-
if primarytable is not None:
sys.stderr.write("'primarytable' argument to mapper is deprecated\n")
self.extension = MapperExtension()
else:
self.extension = extension
- self.hashkey = hashkey
self.class_ = class_
self.is_primary = is_primary
self.order_by = order_by
+ self._options = {}
if not issubclass(class_, object):
raise TypeError("Class '%s' is not a new-style class" % class_.__name__)
for primary_key in self.pks_by_table[self.table]:
self._get_clause.clauses.append(primary_key == sql.bindparam("pk_"+primary_key.key))
- if (
- (not hasattr(self.class_, '_mapper') or not mapper_registry.has_key(self.class_._mapper))
- or self.is_primary
- or (inherits is not None and inherits._is_primary_mapper())
- ):
+ if not mapper_registry.has_key(self.class_) or self.is_primary or (inherits is not None and inherits._is_primary_mapper()):
objectstore.global_attributes.reset_class_managed(self.class_)
self._init_class()
self.identitytable = self.primarytable
else:
- self.identitytable = class_mapper(self.class_).table
+ self.identitytable = mapper_registry[self.class_].primarytable
if inherits is not None:
for key, prop in inherits.props.iteritems():
if not self.props.has_key(key):
self.props[key] = prop._copy()
+ self.props[key].parent = self
+ self.props[key].key = None # force re-init
+
+ for key, prop in self.props.iteritems():
+ if getattr(prop, 'key', None) is None:
+ prop.init(key, self)
engines = property(lambda s: [t.engine for t in s.tables])
def add_property(self, key, prop):
- self.copyargs['properties'][key] = prop
if sql.is_column(prop):
self.columns[key] = prop
prop = ColumnProperty(prop)
proplist.append(prop)
prop.init(key, self)
- def _init_properties(self):
- for key, prop in self.props.iteritems():
- if getattr(prop, 'key', None) is None:
- prop.init(key, self)
-
def __str__(self):
return "Mapper|" + self.class_.__name__ + "|" + self.primarytable.name
- def hash_key(self):
- return self.hashkey
-
+
def _is_primary_mapper(self):
- return getattr(self.class_, '_mapper', None) == self.hashkey
+ return mapper_registry.get(self.class_, None) is self
+
+ def _primary_mapper(self):
+ return mapper_registry[self.class_]
def _init_class(self):
"""sets up our classes' overridden __init__ method, this mappers hash key as its
if not nohist:
objectstore.uow().register_new(self)
self.class_.__init__ = init
- self.class_._mapper = self.hashkey
+ mapper_registry[self.class_] = self
self.class_.c = self.c
def set_property(self, key, prop):
compiling or executing it"""
return self._compile(whereclause, **options)
- def copy(self, hashkey=None):
- # TODO: at the moment, we are re-using the properties from the original mapper
- # which stay connected to that first mapper. if we start making copies of
- # mappers where the primary attributes of the mapper change, we might want
- # to look into copying all the property objects too.
- if hashkey is None:
- hashkey = hash_key(self) + "->copy"
- mapper = Mapper(hashkey, **self.copyargs)
- mapper._init_properties()
+ def copy(self):
+ mapper = Mapper.__new__(Mapper)
+ mapper.__dict__.update(self.__dict__)
+ mapper.props = self.props.copy()
return mapper
def options(self, *options):
"""uses this mapper as a prototype for a new mapper with different behavior.
*options is a list of options directives, which include eagerload(), lazyload(), and noload()"""
- hashkey = hash_key(self) + "->" + repr([hash_key(o) for o in options])
+ optkey = repr([hash_key(o) for o in options])
try:
- return mapper_registry[hashkey]
+ return self._options[optkey]
except KeyError:
- mapper = self.copy(hashkey)
-
+ mapper = self.copy()
for option in options:
option.process(mapper)
- return mapper_registry.setdefault(hashkey, mapper)
+ self._options[optkey] = mapper
+ return mapper
def get_by(self, *args, **params):
"""returns a single object instance based on the given key/value criterion.
# for this table, in the case that the user
# specified custom primary key cols.
for obj in objects:
- #print "SAVE_OBJ we are " + hash_key(self) + " obj: " + obj.__class__.__name__ + repr(id(obj))
+ #print "SAVE_OBJ we are Mapper(" + str(id(self)) + ") obj: " + obj.__class__.__name__ + repr(id(obj))
params = {}
isinsert = not hasattr(obj, "_instance_key")
if primary_key is not None:
i = 0
for col in self.pks_by_table[table]:
- # print "col: " + table.name + "." + col.key + " val: " + repr(self._getattrbycolumn(obj, col))
+ #print "col: " + table.name + "." + col.key + " val: " + repr(self._getattrbycolumn(obj, col))
if self._getattrbycolumn(obj, col) is None:
self._setattrbycolumn(obj, col, primary_key[i])
i+=1
instance = self.extension.create_instance(self, row, imap, self.class_)
if instance is None:
instance = self.class_(_mapper_nohistory=True)
- # attach mapper hashkey to the instance ?
- #instance._mapper = self.hashkey
instance._instance_key = identitykey
imap[identitykey] = instance
else:
return repr(obj)
-def mapper_hash_key(class_, table, primarytable = None, properties = None, **kwargs):
- if properties is None:
- properties = {}
- return (
- "Mapper(%s, %s, primarytable=%s, properties=%s)" % (
- repr(class_),
- hash_key(table),
- hash_key(primarytable),
- repr(dict([(k, hash_key(p)) for k,p in properties.iteritems()]))
- )
- )
setattr(object, self.key, value)
def get_history(self, obj, passive=False):
return objectstore.global_attributes.get_history(obj, self.key, passive=passive)
- def hash_key(self):
- return "ColumnProperty(%s)" % repr([hash_key(c) for c in self.columns])
def _copy(self):
return ColumnProperty(*self.columns)
self.group = kwargs.get('group', None)
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)
"""describes an object property that holds a single item or list of items that correspond
to a related database table."""
- def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, live=False, association=None, selectalias=None, order_by=False, attributeext=None, backref=None, is_backref=False):
+ def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, live=False, association=None, use_alias=False, selectalias=None, order_by=False, attributeext=None, backref=None, is_backref=False):
self.uselist = uselist
self.argument = argument
self.secondary = secondary
self.private = private
self.live = live
self.association = association
- self.selectalias = selectalias
+ if isinstance(selectalias, str):
+ print "'selectalias' argument to property is deprecated. please use 'use_alias=True'"
+ self.use_alias = True
+ else:
+ self.use_alias = use_alias
self.order_by = order_by
self.attributeext=attributeext
self.backref = backref
self.is_backref = is_backref
- self._hash_key = "%s(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.__class__.__name__, hash_key(self.argument), hash_key(secondary), hash_key(primaryjoin), hash_key(secondaryjoin), hash_key(foreignkey), repr(uselist), repr(private), hash_key(self.order_by))
def _copy(self):
- return self.__class__(self.mapper, self.secondary, self.primaryjoin, self.secondaryjoin, self.foreignkey, self.uselist, self.private)
+ x = self.__class__.__new__(self.__class__)
+ x.__dict__.update(self.__dict__)
+ return x
+
+ def init_subclass(self, key, parent):
+ pass
- def hash_key(self):
- return self._hash_key
-
def init(self, key, parent):
import sqlalchemy.mapping
if isinstance(self.argument, type):
elif not objectstore.global_attributes.is_class_managed(parent.class_, key):
raise "Non-primary property created for attribute '%s' on class '%s', but that attribute is not managed! Insure that the primary mapper for this class defines this property" % (key, parent.class_.__name__)
+ self.init_subclass(key, parent)
+
def _is_primary(self):
"""a return value of True indicates we are the primary PropertyLoader for this loader's
attribute on our mapper's class. It means we can set the object's attribute behavior
pass
def delete_obj(self, *args, **kwargs):
pass
+ def _primary_mapper(self):
+ return self
def register_dependencies(self, uowcommit):
"""tells a UOWTransaction what mappers are dependent on which, with regards
objectstore.global_attributes.create_history(instance, self.key, self.uselist)
class LazyLoader(PropertyLoader):
- def init(self, key, parent):
- PropertyLoader.init(self, key, parent)
+ def init_subclass(self, key, parent):
(self.lazywhere, self.lazybinds) = create_lazy_clause(self.parent.table, self.primaryjoin, self.secondaryjoin, self.foreignkey)
# determine if our "lazywhere" clause is the same as the mapper's
# get() clause. then we can just use mapper.get()
class EagerLoader(PropertyLoader):
"""loads related objects inline with a parent query."""
- def init(self, key, parent):
- PropertyLoader.init(self, key, parent)
-
+ def init_subclass(self, key, parent, recursion_stack=None):
parent._has_eager = True
+
+ if recursion_stack is None:
+ recursion_stack = {}
+
+ if self.use_alias:
+ pass
+
# figure out tables in the various join clauses we have, because user-defined
# whereclauses that reference the same tables will be converted to use
# aliases of those tables
if self.secondaryjoin is not None:
[self.to_alias.append(f) for f in self.secondaryjoin._get_from_objects()]
try:
-# del self.to_alias[parent.primarytable]
del self.to_alias[parent.table]
except KeyError:
pass
# or primary join condition to reference the aliased table (and the order_by).
# else we set up the target clause objects as what they are defined in the
# superclass.
- if self.selectalias is not None:
- self.eagertarget = self.target.alias(self.selectalias)
+ if self.use_alias:
+ self.eagertarget = self.target.alias()
aliasizer = Aliasizer(self.target, aliases={self.target:self.eagertarget})
if self.secondaryjoin is not None:
self.eagersecondary = self.secondaryjoin.copy_container()
self.eager_order_by[i].accept_visitor(aliasizer)
else:
self.eager_order_by = self.order_by
+
+ # we have to propigate the "use_alias" fact into
+ # any sub-mappers that are also eagerloading so that they create a unique tablename
+ # as well. this copies our child mapper and replaces any eager properties on the
+ # new mapper with an equivalent eager property, just containing use_alias=True
+ eagerprops = []
+ for key, prop in self.mapper.props.iteritems():
+ if isinstance(prop, EagerLoader) and not prop.use_alias:
+ eagerprops.append(prop)
+ if len(eagerprops):
+ recursion_stack[self] = True
+ self.mapper = self.mapper.copy()
+ try:
+ for prop in eagerprops:
+ p = prop._copy()
+ p.use_alias=True
+
+ self.mapper.props[prop.key] = p
+
+ if recursion_stack.has_key(prop):
+ raise "Circular eager load relationship detected on " + str(self.mapper) + " " + key + repr(self.mapper.props)
+
+ p.init_subclass(prop.key, prop.parent, recursion_stack)
+
+ p.eagerprimary = p.eagerprimary.copy_container()
+ aliasizer = Aliasizer(p.parent.table, aliases={p.parent.table:self.eagertarget})
+ p.eagerprimary.accept_visitor(aliasizer)
+ finally:
+ del recursion_stack[self]
+
else:
self.eagertarget = self.target
self.eagerprimary = self.primaryjoin
else:
towrap = self.parent.table
- if eagertable is not None:
- eagerprimary = self.eagerprimary.copy_container()
- aliasizer = Aliasizer(self.parent.table, aliases={self.parent.table:eagertable})
- eagerprimary.accept_visitor(aliasizer)
- else:
- eagerprimary = self.eagerprimary
-
if self.secondaryjoin is not None:
- statement._outerjoin = sql.outerjoin(towrap, self.secondary, eagerprimary).outerjoin(self.eagertarget, self.eagersecondary)
+ statement._outerjoin = sql.outerjoin(towrap, self.secondary, self.eagerprimary).outerjoin(self.eagertarget, self.eagersecondary)
if self.order_by is False and self.secondary.default_order_by() is not None:
statement.order_by(*self.secondary.default_order_by())
else:
- statement._outerjoin = towrap.outerjoin(self.eagertarget, eagerprimary)
+ statement._outerjoin = towrap.outerjoin(self.eagertarget, self.eagerprimary)
if self.order_by is False and self.eagertarget.default_order_by() is not None:
statement.order_by(*self.eagertarget.default_order_by())
statement.order_by(*util.to_list(self.eager_order_by))
statement.append_from(statement._outerjoin)
- #statement.append_column(self.eagertarget)
recursion_stack[self] = True
try:
for key, value in self.mapper.props.iteritems():
def _instance(self, row, imap, result_list=None):
"""gets an instance from a row, via this EagerLoader's mapper."""
- # if we have an alias for our mapper's table via the selectalias
+ # if we have an alias for our mapper's table via the use_alias
# parameter, we need to translate the
# aliased columns from the incoming row into a new row that maps
# the values against the columns of the mapper's original non-aliased table.
- if self.selectalias is not None:
+ if self.use_alias:
fakerow = {}
fakerow = util.DictDecorator(row)
for c in self.eagertarget.c:
tokens = key.split('.', 1)
if len(tokens) > 1:
oldprop = mapper.props[tokens[0]]
- kwargs = util.constructor_args(oldprop)
- kwargs['argument'] = self.process_by_key(oldprop.mapper.copy(), tokens[1])
- newprop = oldprop.__class__(**kwargs)
+ newprop = oldprop._copy()
+ newprop.argument = self.process_by_key(oldprop.mapper.copy(), tokens[1])
mapper.set_property(tokens[0], newprop)
else:
self.create_prop(mapper, tokens[0])
else:
class_ = LazyLoader
- # create a clone of the class using mostly the arguments from the original
- submapper = mapper.props[key].mapper
- kwargs = util.constructor_args(mapper.props[key], **self.kwargs)
- mapper.set_property(key, class_(**kwargs ))
+ oldprop = mapper.props[key]
+ newprop = class_.__new__(class_)
+ newprop.__dict__.update(oldprop.__dict__)
+ newprop.init_subclass(key, mapper)
+ if self.kwargs.get('selectalias', None):
+ newprop.use_alias = True
+ elif self.kwargs.get('use_alias', None) is not None:
+ newprop.use_alias = self.kwargs['use_alias']
+ mapper.set_property(key, newprop)
class DeferredOption(GenericOption):
def __init__(self, key, defer=False, **kwargs):