diff options
Diffstat (limited to 'lib/sqlalchemy/orm/properties.py')
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 610 |
1 files changed, 28 insertions, 582 deletions
diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 3a7a7e6ba..3e64b300c 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -11,128 +11,43 @@ properties.""" from sqlalchemy import sql, schema, util, attributes, exceptions, sql_util, logging import mapper import sync +import strategies import session as sessionlib import dependency import util as mapperutil import sets, random +from interfaces import * -class ColumnProperty(mapper.MapperProperty): + +class ColumnProperty(StrategizedProperty): """describes an object attribute that corresponds to a table column.""" def __init__(self, *columns, **kwargs): """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.deepcheck = kwargs.get('deepcheck', False) self.columns = list(columns) + self.group = kwargs.pop('group', None) + self.deferred = kwargs.pop('deferred', False) + def create_strategy(self): + if self.deferred: + return strategies.DeferredColumnLoader(self) + else: + return strategies.ColumnLoader(self) def getattr(self, object): return getattr(object, self.key, None) def setattr(self, object, value): setattr(object, self.key, value) def get_history(self, obj, passive=False): return sessionlib.attribute_manager.get_history(obj, self.key, passive=passive) - def copy(self): - return ColumnProperty(*self.columns) - def setup(self, statement, eagertable=None, **options): - for c in self.columns: - if eagertable is not None: - statement.append_column(eagertable.corresponding_column(c)) - else: - statement.append_column(c) - def do_init(self): - # establish a SmartProperty property manager on the object for this key - if self.is_primary(): - self.logger.info("register managed attribute %s on class %s" % (self.key, self.parent.class_.__name__)) - sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, copy_function=lambda x: self.columns[0].type.copy_value(x), compare_function=lambda x,y:self.columns[0].type.compare_values(x,y), mutable_scalars=self.columns[0].type.is_mutable()) - def execute(self, selectcontext, instance, row, identitykey, isnew): - if isnew: - self.logger.debug("populating %s with %s/%s" % (mapperutil.attribute_str(instance, self.key), row.__class__.__name__, self.columns[0].key)) - # set a scalar object instance directly on the object, - # bypassing SmartProperty event handlers. - instance.__dict__[self.key] = row[self.columns[0]] - def adapt_to_inherited(self, key, newparent): - if newparent.concrete: - return - else: - super(ColumnProperty, self).adapt_to_inherited(key, newparent) - def __repr__(self): - return "ColumnProperty(%s)" % repr([str(c) for c in self.columns]) ColumnProperty.logger = logging.class_logger(ColumnProperty) -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.group = kwargs.pop('group', None) - ColumnProperty.__init__(self, *columns, **kwargs) - def copy(self): - return DeferredColumnProperty(*self.columns) - def do_init(self): - # establish a SmartProperty property manager on the object for this key, - # containing a callable to load in the attribute - if self.is_primary(): - self.logger.info("register managed attribute %s on class %s" % (self.key, self.parent.class_.__name__)) - sessionlib.attribute_manager.register_attribute(self.parent.class_, self.key, uselist=False, callable_=lambda i:self.setup_loader(i), copy_function=lambda x: self.columns[0].type.copy_value(x), compare_function=lambda x,y:self.columns[0].type.compare_values(x,y), mutable_scalars=self.columns[0].type.is_mutable()) - def setup_loader(self, instance): - if not self.localparent.is_assigned(instance): - return mapper.object_mapper(instance).props[self.key].setup_loader(instance) - def lazyload(): - self.logger.debug("deferred load %s group %s" % (mapperutil.attribute_str(instance, self.key), str(self.group))) - try: - pk = self.parent.pks_by_table[self.columns[0].table] - except KeyError: - pk = self.columns[0].table.primary_key - - clause = sql.and_() - for primary_key in pk: - attr = self.parent._getattrbycolumn(instance, primary_key) - if not attr: - return None - clause.clauses.append(primary_key == attr) - session = sessionlib.object_session(instance) - if session is None: - raise exceptions.InvalidRequestError("Parent instance %s is not bound to a Session; deferred load operation of attribute '%s' cannot proceed" % (instance.__class__, self.key)) - - if self.group is not None: - groupcols = [p for p in self.localparent.props.values() if isinstance(p, DeferredColumnProperty) and p.group==self.group] - result = session.execute(self.localparent, sql.select([g.columns[0] for g in groupcols], clause, use_labels=True), None) - try: - row = result.fetchone() - for prop in groupcols: - if prop is self: - continue - # set a scalar object instance directly on the object, - # bypassing SmartProperty event handlers. - sessionlib.attribute_manager.init_instance_attribute(instance, prop.key, uselist=False) - instance.__dict__[prop.key] = row[prop.columns[0]] - return row[self.columns[0]] - finally: - result.close() - else: - return session.scalar(self.localparent, sql.select([self.columns[0]], clause, use_labels=True),None) - - return lazyload - def setup(self, statement, **options): - pass - def execute(self, selectcontext, instance, row, identitykey, isnew): - if isnew: - if not self.is_primary(): - sessionlib.attribute_manager.init_instance_attribute(instance, self.key, False, callable_=self.setup_loader(instance)) - else: - sessionlib.attribute_manager.reset_instance_attribute(instance, self.key) - -DeferredColumnProperty.logger = logging.class_logger(DeferredColumnProperty) - mapper.ColumnProperty = ColumnProperty -class PropertyLoader(mapper.MapperProperty): - ONETOMANY = 0 - MANYTOONE = 1 - MANYTOMANY = 2 - +class PropertyLoader(StrategizedProperty): """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, association=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False, cascade=None, viewonly=False): + def __init__(self, argument, secondary, primaryjoin, secondaryjoin, foreignkey=None, uselist=None, private=False, association=None, order_by=False, attributeext=None, backref=None, is_backref=False, post_update=False, cascade=None, viewonly=False, lazy=True): self.uselist = uselist self.argument = argument self.secondary = secondary @@ -141,7 +56,7 @@ class PropertyLoader(mapper.MapperProperty): self.post_update = post_update self.direction = None self.viewonly = viewonly - + self.lazy = lazy self.foreignkey = util.to_set(foreignkey) if cascade is not None: @@ -169,6 +84,14 @@ class PropertyLoader(mapper.MapperProperty): private = property(lambda s:s.cascade.delete_orphan) + def create_strategy(self): + if self.lazy: + return strategies.LazyLoader(self) + elif self.lazy is False: + return strategies.EagerLoader(self) + elif self.lazy is None: + return strategies.NoLoader(self) + def __str__(self): return self.__class__.__name__ + " " + str(self.parent) + "->" + self.key + "->" + str(self.mapper) @@ -197,15 +120,6 @@ class PropertyLoader(mapper.MapperProperty): callable_(c, mapper.entity_name) mapper.cascade_callable(type, c, callable_, recursive) - def copy(self): - x = self.__class__.__new__(self.__class__) - x.__dict__.update(self.__dict__) - return x - - def do_init_subclass(self): - """template method for subclasses of PropertyLoader""" - pass - def _get_target_class(self): """return the target class of the relation, even if the property has not been initialized yet.""" if isinstance(self.argument, type): @@ -247,7 +161,7 @@ class PropertyLoader(mapper.MapperProperty): if self.primaryjoin is None: self.primaryjoin = sql.join(self.parent.unjoined_table, self.target).onclause except exceptions.ArgumentError, e: - raise exceptions.ArgumentError("Error determining primary and/or secondary join for relationship '%s' between mappers '%s' and '%s'. If the underlying error cannot be corrected, you should specify the 'primaryjoin' (and 'secondaryjoin', if there is an association table present) keyword arguments to the relation() function (or for backrefs, by specifying the backref using the backref() function with keyword arguments) to explicitly specify the join conditions. Nested error is \"%s\"" % (self.key, self.localparent, self.mapper, str(e))) + raise exceptions.ArgumentError("Error determining primary and/or secondary join for relationship '%s' between mappers '%s' and '%s'. If the underlying error cannot be corrected, you should specify the 'primaryjoin' (and 'secondaryjoin', if there is an association table present) keyword arguments to the relation() function (or for backrefs, by specifying the backref using the backref() function with keyword arguments) to explicitly specify the join conditions. Nested error is \"%s\"" % (self.key, self.parent, self.mapper, str(e))) # if the foreign key wasnt specified and theres no assocaition table, try to figure # out who is dependent on who. we dont need all the foreign keys represented in the join, # just one of them. @@ -266,18 +180,8 @@ class PropertyLoader(mapper.MapperProperty): if self.uselist is None: self.uselist = True - - if self.inherits is not None: - if hasattr(self.inherits, '_dependency_processor'): - self._dependency_processor = self.inherits._dependency_processor - - if not hasattr(self, '_dependency_processor'): - self._dependency_processor = dependency.create_dependency_processor(self) - - if self.inherits is not None and not hasattr(self.inherits, '_dependency_processor'): - self.inherits._dependency_processor = self._dependency_processor - + self._dependency_processor = dependency.create_dependency_processor(self) # primary property handler, set up class attributes if self.is_primary(): @@ -286,27 +190,13 @@ class PropertyLoader(mapper.MapperProperty): if self.backref is not None: self.attributeext = self.backref.get_extension() - # set our class attribute - self._set_class_attribute(self.parent.class_, self.key) - if self.backref is not None: self.backref.compile(self) - elif self.inherits is None and not sessionlib.attribute_manager.is_class_managed(self.parent.class_, self.key): + elif not sessionlib.attribute_manager.is_class_managed(self.parent.class_, self.key): raise exceptions.ArgumentError("Attempting to assign a new relation '%s' to a non-primary mapper on class '%s'. New relations can only be added to the primary mapper, i.e. the very first mapper created for class '%s' " % (self.key, self.parent.class_.__name__, self.parent.class_.__name__)) - self.do_init_subclass() - - def _register_attribute(self, class_, callable_=None): - self.logger.info("register managed %s attribute %s on class %s" % ((self.uselist and "list-holding" or "scalar"), self.key, self.parent.class_.__name__)) - sessionlib.attribute_manager.register_attribute(class_, self.key, uselist = self.uselist, extension=self.attributeext, cascade=self.cascade, trackparent=True, callable_=callable_) - - def _init_instance_attribute(self, instance, callable_=None): - return sessionlib.attribute_manager.init_instance_attribute(instance, self.key, self.uselist, cascade=self.cascade, trackparent=True, callable_=callable_) + super(PropertyLoader, self).do_init() - def _set_class_attribute(self, class_, key): - """sets attribute behavior on our target class.""" - self._register_attribute(class_) - def _is_self_referential(self): return self.parent.mapped_table is self.target or self.parent.select_table is self.target @@ -338,7 +228,7 @@ class PropertyLoader(mapper.MapperProperty): foreignkeys.add(binary.left) elif binary.right.foreign_key is not None and binary.right.foreign_key.references(binary.left.table): foreignkeys.add(binary.right) - visitor = BinaryVisitor(foo) + visitor = mapperutil.BinaryVisitor(foo) self.primaryjoin.accept_visitor(visitor) self.foreignkey = foreignkeys @@ -348,380 +238,12 @@ class PropertyLoader(mapper.MapperProperty): else: return self.primaryjoin - def execute(self, selectcontext, instance, row, identitykey, isnew): - if self.is_primary(): - return - #print "PLAIN PROPLOADER EXEC NON-PRIAMRY", repr(id(self)), repr(self.mapper.class_), self.key - self._init_instance_attribute(instance) - def register_dependencies(self, uowcommit): if not self.viewonly: self._dependency_processor.register_dependencies(uowcommit) - PropertyLoader.logger = logging.class_logger(PropertyLoader) -class LazyLoader(PropertyLoader): - def do_init_subclass(self): - (self.lazywhere, self.lazybinds, self.lazyreverse) = create_lazy_clause(self.parent.unjoined_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() - self.use_get = not self.uselist and self.mapper.query()._get_clause.compare(self.lazywhere) - - def _set_class_attribute(self, class_, key): - # establish a class-level lazy loader on our class - #print "SETCLASSATTR LAZY", repr(class_), key - self._register_attribute(class_, callable_=lambda i: self.setup_loader(i)) - - def setup_loader(self, instance): - #print self, "setup_loader", "parent", self.parent.mapped_table, "child", self.mapper.mapped_table, "join", self.lazywhere - # make sure our parent mapper is the one thats assigned to this instance, else call that one - if not self.localparent.is_assigned(instance): - # if no mapper association with this instance (i.e. not in a session, not loaded by a mapper), - # then we cant set up a lazy loader - if not mapper.has_mapper(instance): - return None - else: - return mapper.object_mapper(instance).props[self.key].setup_loader(instance) - - def lazyload(): - self.logger.debug("lazy load attribute %s on instance %s" % (self.key, mapperutil.instance_str(instance))) - params = {} - allparams = True - # if the instance wasnt loaded from the database, then it cannot lazy load - # child items. one reason for this is that a bi-directional relationship - # will not update properly, since bi-directional uses lazy loading functions - # in both directions, and this instance will not be present in the lazily-loaded - # results of the other objects since its not in the database - if not mapper.has_identity(instance): - return None - #print "setting up loader, lazywhere", str(self.lazywhere), "binds", self.lazybinds - for col, bind in self.lazybinds.iteritems(): - params[bind.key] = self.parent._getattrbycolumn(instance, col) - if params[bind.key] is None: - allparams = False - break - - if not allparams: - return None - - session = sessionlib.object_session(instance) - if session is None: - try: - session = mapper.object_mapper(instance).get_session() - except exceptions.InvalidRequestError: - raise exceptions.InvalidRequestError("Parent instance %s is not bound to a Session, and no contextual session is established; lazy load operation of attribute '%s' cannot proceed" % (instance.__class__, self.key)) - - # if we have a simple straight-primary key load, use mapper.get() - # to possibly save a DB round trip - if self.use_get: - ident = [] - for primary_key in self.mapper.pks_by_table[self.mapper.mapped_table]: - bind = self.lazyreverse[primary_key] - ident.append(params[bind.key]) - return self.mapper.using(session).get(ident) - elif self.order_by is not False: - order_by = self.order_by - elif self.secondary is not None and self.secondary.default_order_by() is not None: - order_by = self.secondary.default_order_by() - else: - order_by = False - result = self.mapper.using(session).select_whereclause(self.lazywhere, order_by=order_by, params=params) - - if self.uselist: - return result - else: - if len(result): - return result[0] - else: - return None - return lazyload - - def execute(self, selectcontext, instance, row, identitykey, isnew): - if isnew: - # new object instance being loaded from a result row - if not self.is_primary(): - self.logger.debug("set instance-level lazy loader on %s" % mapperutil.attribute_str(instance, self.key)) - # we are not the primary manager for this attribute on this class - set up a per-instance lazyloader, - # which will override the clareset_instance_attributess-level behavior - self._init_instance_attribute(instance, callable_=self.setup_loader(instance)) - else: - self.logger.debug("set class-level lazy loader on %s" % mapperutil.attribute_str(instance, self.key)) - # we are the primary manager for this attribute on this class - reset its per-instance attribute state, - # so that the class-level lazy loader is executed when next referenced on this instance. - # this usually is not needed unless the constructor of the object referenced the attribute before we got - # to load data into it. - sessionlib.attribute_manager.reset_instance_attribute(instance, self.key) - -LazyLoader.logger = logging.class_logger(LazyLoader) - -def create_lazy_clause(table, primaryjoin, secondaryjoin, foreignkey): - binds = {} - reverse = {} - def column_in_table(table, column): - return table.corresponding_column(column, raiseerr=False, keys_ok=False) is not None - - def bind_label(): - return "lazy_" + hex(random.randint(0, 65535))[2:] - def visit_binary(binary): - circular = isinstance(binary.left, schema.Column) and isinstance(binary.right, schema.Column) and binary.left.table is binary.right.table - if isinstance(binary.left, schema.Column) and isinstance(binary.right, schema.Column) and ((not circular and column_in_table(table, binary.left)) or (circular and binary.right in foreignkey)): - col = binary.left - binary.left = binds.setdefault(binary.left, - sql.BindParamClause(bind_label(), None, shortname=binary.left.name, type=binary.right.type)) - reverse[binary.right] = binds[col] - - if isinstance(binary.right, schema.Column) and isinstance(binary.left, schema.Column) and ((not circular and column_in_table(table, binary.right)) or (circular and binary.left in foreignkey)): - col = binary.right - binary.right = binds.setdefault(binary.right, - sql.BindParamClause(bind_label(), None, shortname=binary.right.name, type=binary.left.type)) - reverse[binary.left] = binds[col] - - lazywhere = primaryjoin.copy_container() - li = BinaryVisitor(visit_binary) - lazywhere.accept_visitor(li) - if secondaryjoin is not None: - lazywhere = sql.and_(lazywhere, secondaryjoin) - LazyLoader.logger.debug("create_lazy_clause " + str(lazywhere)) - return (lazywhere, binds, reverse) - - -class EagerLoader(LazyLoader): - """loads related objects inline with a parent query.""" - def do_init_subclass(self, recursion_stack=None): - if self.parent.isa(self.mapper): - raise exceptions.ArgumentError("Error creating eager relationship '%s' on parent class '%s' to child class '%s': Cant use eager loading on a self referential relationship." % (self.key, repr(self.parent.class_), repr(self.mapper.class_))) - if recursion_stack is None: - LazyLoader.do_init_subclass(self) - self.parent._has_eager = True - - self.eagertarget = self.target.alias() - if self.secondary: - self.eagersecondary = self.secondary.alias() - self.aliasizer = sql_util.Aliasizer(self.target, self.secondary, aliases={ - self.target:self.eagertarget, - self.secondary:self.eagersecondary - }) - #print "TARGET", self.target - self.eagersecondaryjoin = self.secondaryjoin.copy_container() - self.eagersecondaryjoin.accept_visitor(self.aliasizer) - self.eagerprimary = self.primaryjoin.copy_container() - self.eagerprimary.accept_visitor(self.aliasizer) - #print "JOINS:", str(self.eagerprimary), "|", str(self.eagersecondaryjoin) - else: - self.aliasizer = sql_util.Aliasizer(self.target, aliases={self.target:self.eagertarget}) - self.eagerprimary = self.primaryjoin.copy_container() - self.eagerprimary.accept_visitor(self.aliasizer) - - if self.order_by: - self.eager_order_by = self._aliasize_orderby(self.order_by) - else: - self.eager_order_by = None - - def _create_eager_chain(self, recursion_stack=None): - try: - if self.__eager_chain_init == id(self): - return - except AttributeError: - pass - - if recursion_stack is None: - recursion_stack = {} - - eagerprops = [] - # create a new "eager chain", starting from this eager loader and descending downwards - # through all sub-eagerloaders. this will copy all those eagerloaders and have them set up - # aliases distinct to this eager chain. if a recursive relationship to any of the tables is detected, - # the chain will terminate by copying that eager loader into a lazy loader. - for key, prop in self.mapper.props.iteritems(): - if isinstance(prop, EagerLoader): - eagerprops.append(prop) - - if len(eagerprops): - recursion_stack[self.localparent.mapped_table] = True - self.eagermapper = self.mapper.copy() - try: - for prop in eagerprops: - if recursion_stack.has_key(prop.target): - # recursion - set the relationship as a LazyLoader - p = EagerLazyOption(None, False).create_prop(self.eagermapper, prop.key) - continue - p = prop.copy() - self.eagermapper.props[prop.key] = p -# print "we are:", id(self), self.target.name, (self.secondary and self.secondary.name or "None"), self.parent.mapped_table.name -# print "prop is",id(prop), prop.target.name, (prop.secondary and prop.secondary.name or "None"), prop.parent.mapped_table.name - p.do_init_subclass(recursion_stack) - p._create_eager_chain(recursion_stack=recursion_stack) - p.eagerprimary = p.eagerprimary.copy_container() -# aliasizer = sql_util.Aliasizer(p.parent.mapped_table, aliases={p.parent.mapped_table:self.eagertarget}) - p.eagerprimary.accept_visitor(self.aliasizer) - #print "new eagertqarget", p.eagertarget.name, (p.secondary and p.secondary.name or "none"), p.parent.mapped_table.name - finally: - del recursion_stack[self.localparent.mapped_table] - else: - self.eagermapper = self.mapper - self._row_decorator = self._create_decorator_row() - self.__eager_chain_init = id(self) - -# print "ROW DECORATOR", self._row_decorator - - def _aliasize_orderby(self, orderby, copy=True): - if copy: - orderby = [o.copy_container() for o in util.to_list(orderby)] - else: - orderby = util.to_list(orderby) - for i in range(0, len(orderby)): - if isinstance(orderby[i], schema.Column): - orderby[i] = self.eagertarget.corresponding_column(orderby[i]) - else: - orderby[i].accept_visitor(self.aliasizer) - return orderby - - def setup(self, statement, eagertable=None, **options): - """add a left outer join to the statement thats being constructed""" - - # initialize the "eager" chain of EagerLoader objects - # this can't quite be done in the do_init_mapper() step - self._create_eager_chain() - - if hasattr(statement, '_outerjoin'): - towrap = statement._outerjoin - elif isinstance(self.localparent.mapped_table, schema.Table): - # if the mapper is against a plain Table, look in the from_obj of the select statement - # to join against whats already there. - for (fromclause, finder) in [(x, sql_util.TableFinder(x)) for x in statement.froms]: - # dont join against an Alias'ed Select. we are really looking either for the - # table itself or a Join that contains the table. this logic still might need - # adjustments for scenarios not thought of yet. - if not isinstance(fromclause, sql.Alias) and self.localparent.mapped_table in finder: - towrap = fromclause - break - else: - raise exceptions.InvalidRequestError("EagerLoader cannot locate a clause with which to outer join to, in query '%s' %s" % (str(statement), self.localparent.mapped_table)) - else: - # if the mapper is against a select statement or something, we cant handle that at the - # same time as a custom FROM clause right now. - towrap = self.localparent.mapped_table - - if self.secondaryjoin is not None: - statement._outerjoin = sql.outerjoin(towrap, self.eagersecondary, self.eagerprimary).outerjoin(self.eagertarget, self.eagersecondaryjoin) - if self.order_by is False and self.secondary.default_order_by() is not None: - statement.order_by(*self.eagersecondary.default_order_by()) - else: - 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()) - - if self.eager_order_by: - statement.order_by(*util.to_list(self.eager_order_by)) - elif getattr(statement, 'order_by_clause', None): - self._aliasize_orderby(statement.order_by_clause, False) - - statement.append_from(statement._outerjoin) - for value in self.eagermapper.props.values(): - value.setup(statement, eagertable=self.eagertarget) - - def execute(self, selectcontext, instance, row, identitykey, isnew): - """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.""" - - decorated_row = self._decorate_row(row) - try: - # check for identity key - identity_key = self.eagermapper._row_identity_key(decorated_row) - except KeyError: - # else degrade to a lazy loader - self.logger.debug("degrade to lazy loader on %s" % mapperutil.attribute_str(instance, self.key)) - LazyLoader.execute(self, selectcontext, instance, row, identitykey, isnew) - return - - - if not self.uselist: - self.logger.debug("eagerload scalar instance on %s" % mapperutil.attribute_str(instance, self.key)) - if isnew: - # set a scalar object instance directly on the parent object, - # bypassing SmartProperty event handlers. - instance.__dict__[self.key] = self.eagermapper._instance(selectcontext, decorated_row, None) - else: - # call _instance on the row, even though the object has been created, - # so that we further descend into properties - self.eagermapper._instance(selectcontext, decorated_row, None) - else: - if isnew: - self.logger.debug("initialize UniqueAppender on %s" % mapperutil.attribute_str(instance, self.key)) - # call the SmartProperty's initialize() method to create a new, blank list - l = getattr(instance.__class__, self.key).initialize(instance) - - # create an appender object which will add set-like semantics to the list - appender = util.UniqueAppender(l.data) - - # store it in the "scratch" area, which is local to this load operation. - selectcontext.attributes[(instance, self.key)] = appender - result_list = selectcontext.attributes[(instance, self.key)] - self.logger.debug("eagerload list instance on %s" % mapperutil.attribute_str(instance, self.key)) - self.eagermapper._instance(selectcontext, decorated_row, result_list) - - def _create_decorator_row(self): - class EagerRowAdapter(object): - def __init__(self, row): - self.row = row - def has_key(self, key): - return map.has_key(key) or self.row.has_key(key) - def __getitem__(self, key): - if map.has_key(key): - key = map[key] - return self.row[key] - def keys(self): - return map.keys() - map = {} - for c in self.eagertarget.c: - parent = self.target.corresponding_column(c) - map[parent] = c - map[parent._label] = c - map[parent.name] = c - return EagerRowAdapter - - def _decorate_row(self, row): - # since the EagerLoader makes an Alias of its mapper's table, - # we translate the actual result columns back to what they - # would normally be into a "virtual row" which is passed to the child mapper. - # that way the mapper doesnt have to know about the modified column name - # (neither do any MapperExtensions). The row is keyed off the Column object - # (which is what mappers use) as well as its "label" (which might be what - # user-defined code is using) - try: - return self._row_decorator(row) - except AttributeError: - # insure the "eager chain" step occurred - self._create_eager_chain() - return self._row_decorator(row) - -EagerLoader.logger = logging.class_logger(EagerLoader) - -class GenericOption(mapper.MapperOption): - """a mapper option that can handle dotted property names, - descending down through the relations of a mapper until it - reaches the target.""" - def __init__(self, key): - self.key = key - def process(self, mapper): - self.process_by_key(mapper, self.key) - def process_by_key(self, mapper, key): - tokens = key.split('.', 1) - if len(tokens) > 1: - oldprop = mapper.props[tokens[0]] - newprop = oldprop.copy() - newprop.argument = self.process_by_key(oldprop.mapper.copy(), tokens[1]) - mapper._compile_property(tokens[0], newprop) - else: - self.create_prop(mapper, tokens[0]) - return mapper - - def create_prop(self, mapper, key): - kwargs = util.constructor_args(oldprop) - mapper._compile_property(key, class_(**kwargs )) - - class BackRef(object): """stores the name of a backreference property as well as options to be used on the resulting PropertyLoader.""" @@ -736,28 +258,9 @@ class BackRef(object): if not mapper.props.has_key(self.key): pj = self.kwargs.pop('primaryjoin', None) sj = self.kwargs.pop('secondaryjoin', None) - # the backref will compile its own primary/secondary join conditions. if you have it - # use the pj/sj of the parent relation in all cases, a bunch of polymorphic unit tests - # fail (maybe we can look into that too). - # the PropertyLoader class is currently constructing BackRef objects using the explictly - # passed primary/secondary join conditions, if the backref was passed to it as just a string. - #if pj is None: - # if prop.secondaryjoin is not None: - # if setting up a backref to a many-to-many, reverse the order - # of the "primary" and "secondary" joins - # pj = prop.secondaryjoin - # sj = prop.primaryjoin - # else: - # pj = prop.primaryjoin - # sj = None - lazy = self.kwargs.pop('lazy', True) - if lazy: - cls = LazyLoader - else: - cls = EagerLoader # the backref property is set on the primary mapper parent = prop.parent.primary_mapper() - relation = cls(parent, prop.secondary, pj, sj, backref=prop.key, is_backref=True, **self.kwargs) + relation = PropertyLoader(parent, prop.secondary, pj, sj, backref=prop.key, is_backref=True, **self.kwargs) mapper._compile_property(self.key, relation); elif not isinstance(mapper.props[self.key], PropertyLoader): raise exceptions.ArgumentError("Cant create backref '%s' on mapper '%s'; an incompatible property of that name already exists" % (self.key, str(mapper))) @@ -772,61 +275,4 @@ class BackRef(object): def get_extension(self): """returns an attribute extension to use with this backreference.""" return attributes.GenericBackrefExtension(self.key) - -class EagerLazyOption(GenericOption): - """an option that switches a PropertyLoader to be an EagerLoader or LazyLoader""" - def __init__(self, key, toeager = True, **kwargs): - self.key = key - self.toeager = toeager - self.kwargs = kwargs - - def hash_key(self): - return "EagerLazyOption(%s, %s)" % (repr(self.key), repr(self.toeager)) - - def create_prop(self, mapper, key): - if self.toeager: - class_ = EagerLoader - elif self.toeager is None: - class_ = PropertyLoader - else: - class_ = LazyLoader - - oldprop = mapper.props[key] - newprop = class_.__new__(class_) - newprop.__dict__.update(oldprop.__dict__) - #newprop.do_init_subclass() - p = newprop - while p.inherits is not None: - p = p.inherits - real_parent_mapper = p.parent - real_parent_mapper._compile_property(key, newprop, localparent=mapper) - -class DeferredOption(GenericOption): - def __init__(self, key, defer=False, **kwargs): - self.key = key - self.defer = defer - self.kwargs = kwargs - def hash_key(self): - return "DeferredOption(%s,%s)" % (self.key, self.defer) - def create_prop(self, mapper, key): - oldprop = mapper.props[key] - if self.defer: - prop = DeferredColumnProperty(*oldprop.columns, **self.kwargs) - else: - prop = ColumnProperty(*oldprop.columns, **self.kwargs) - mapper._compile_property(key, prop) - -class DeferGroupOption(mapper.MapperOption): - def __init__(self, group, defer=False, **kwargs): - self.group = group - self.defer = defer - self.kwargs = kwargs - def process(self, mapper): - self.process_by_key(mapper, self.key) - -class BinaryVisitor(sql.ClauseVisitor): - def __init__(self, func): - self.func = func - def visit_binary(self, binary): - self.func(binary) |
