diff options
Diffstat (limited to 'lib/sqlalchemy/orm')
| -rw-r--r-- | lib/sqlalchemy/orm/dependency.py | 3 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 28 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/properties.py | 59 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/session.py | 4 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/unitofwork.py | 85 |
5 files changed, 89 insertions, 90 deletions
diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py index 7d3b341ab..145ec5e9b 100644 --- a/lib/sqlalchemy/orm/dependency.py +++ b/lib/sqlalchemy/orm/dependency.py @@ -10,6 +10,7 @@ together to allow processing of scalar- and list-based dependencies at flush tim from sync import ONETOMANY,MANYTOONE,MANYTOMANY from sqlalchemy import sql, util +import session as sessionlib def create_dependency_processor(key, syncrules, cascade, secondary=None, association=None, is_backref=False, post_update=False): types = { @@ -78,7 +79,7 @@ class DependencyProcessor(object): def get_object_dependencies(self, obj, uowcommit, passive = True): """returns the list of objects that are dependent on the given object, as according to the relationship this dependency processor represents""" - return uowcommit.uow.attributes.get_history(obj, self.key, passive = passive) + return sessionlib.attribute_manager.get_history(obj, self.key, passive = passive) class OneToManyDP(DependencyProcessor): diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 0fbcdb8e1..90db7f83e 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -436,7 +436,7 @@ class Mapper(object): if not self.non_primary and (mapper_registry.has_key(self.class_key) and not self.is_primary): raise exceptions.ArgumentError("Class '%s' already has a primary mapper defined. Use is_primary=True to assign a new primary mapper to the class, or use non_primary=True to create a non primary Mapper" % self.class_) - sessionlib.global_attributes.reset_class_managed(self.class_) + sessionlib.attribute_manager.reset_class_managed(self.class_) oldinit = self.class_.__init__ def init(self, *args, **kwargs): @@ -447,7 +447,7 @@ class Mapper(object): # this gets the AttributeManager to do some pre-initialization, # in order to save on KeyErrors later on - sessionlib.global_attributes.init_attr(self) + sessionlib.attribute_manager.init_attr(self) if kwargs.has_key('_sa_session'): session = kwargs.pop('_sa_session') @@ -595,13 +595,15 @@ class Mapper(object): offset = kwargs.get('offset', None) populate_existing = kwargs.get('populate_existing', False) - result = util.HistoryArraySet() + result = util.UniqueAppender([]) if mappers: otherresults = [] for m in mappers: - otherresults.append(util.HistoryArraySet()) + otherresults.append(util.UniqueAppender([])) imap = {} + scratch = {} + imap['_scratch'] = scratch while True: row = cursor.fetchone() if row is None: @@ -614,11 +616,14 @@ class Mapper(object): # store new stuff in the identity map for value in imap.values(): + if value is scratch: + continue session._register_clean(value) - + if mappers: - result = [result] + otherresults - return result + return [result.data] + [o.data for o in otherresults] + else: + return result.data def identity_key(self, primary_key): """returns the instance key for the given identity value. this is a global tracking object used by the Session, and is usually available off a mapped object as instance._instance_key.""" @@ -952,7 +957,7 @@ class Mapper(object): prop.execute(session, instance, row, identitykey, imap, True) if self.extension.append_result(self, session, row, imap, result, instance, isnew, populate_existing=populate_existing) is EXT_PASS: if result is not None: - result.append_nohistory(instance) + result.append(instance) return instance # look in result-local identitymap for it. @@ -981,7 +986,7 @@ class Mapper(object): self.populate_instance(session, instance, row, identitykey, imap, isnew) if self.extension.append_result(self, session, row, imap, result, instance, isnew, populate_existing=populate_existing) is EXT_PASS: if result is not None: - result.append_nohistory(instance) + result.append(instance) return instance def _create_instance(self, session): @@ -990,7 +995,7 @@ class Mapper(object): # this gets the AttributeManager to do some pre-initialization, # in order to save on KeyErrors later on - sessionlib.global_attributes.init_attr(obj) + sessionlib.attribute_manager.init_attr(obj) return obj @@ -1207,8 +1212,7 @@ class MapperExtension(object): current result set result - an instance of util.HistoryArraySet(), which may be an attribute on an - object if this is a related object load (lazy or eager). use result.append_nohistory(value) - to append objects to this list. + object if this is a related object load (lazy or eager). instance - the object instance to be appended to the result diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 23cdc78f1..843bcf817 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -28,7 +28,7 @@ class ColumnProperty(mapper.MapperProperty): def setattr(self, object, value): setattr(object, self.key, value) def get_history(self, obj, passive=False): - return sessionlib.global_attributes.get_history(obj, self.key, passive=passive) + return sessionlib.attribute_manager.get_history(obj, self.key, passive=passive) def copy(self): return ColumnProperty(*self.columns) def setup(self, key, statement, eagertable=None, **options): @@ -41,10 +41,12 @@ class ColumnProperty(mapper.MapperProperty): # establish a SmartProperty property manager on the object for this key if parent._is_primary_mapper(): #print "regiser col on class %s key %s" % (parent.class_.__name__, key) - sessionlib.global_attributes.register_attribute(parent.class_, key, uselist = False) + sessionlib.attribute_manager.register_attribute(parent.class_, key, uselist = False) def execute(self, session, instance, row, identitykey, imap, isnew): if isnew: #print "POPULATING OBJ", instance.__class__.__name__, "COL", self.columns[0]._label, "WITH DATA", row[self.columns[0]], "ROW IS A", row.__class__.__name__, "COL ID", id(self.columns[0]) + # set a scalar object instance directly on the object, + # bypassing SmartProperty event handlers. instance.__dict__[self.key] = row[self.columns[0]] def __repr__(self): return "ColumnProperty(%s)" % repr([str(c) for c in self.columns]) @@ -61,7 +63,7 @@ class DeferredColumnProperty(ColumnProperty): # establish a SmartProperty property manager on the object for this key, # containing a callable to load in the attribute if self.is_primary(): - sessionlib.global_attributes.register_attribute(parent.class_, key, uselist=False, callable_=lambda i:self.setup_loader(i)) + sessionlib.attribute_manager.register_attribute(parent.class_, key, uselist=False, callable_=lambda i:self.setup_loader(i)) def setup_loader(self, instance): if not self.localparent.is_assigned(instance): return mapper.object_mapper(instance).props[self.key].setup_loader(instance) @@ -88,8 +90,10 @@ class DeferredColumnProperty(ColumnProperty): for prop in groupcols: if prop is self: continue + # set a scalar object instance directly on the object, + # bypassing SmartProperty event handlers. instance.__dict__[prop.key] = row[prop.columns[0]] - sessionlib.global_attributes.create_history(instance, prop.key, uselist=False) + sessionlib.attribute_manager.init_instance_attribute(instance, prop.key, uselist=False) return row[self.columns[0]] else: return connection.scalar(sql.select([self.columns[0]], clause, use_labels=True),None) @@ -101,9 +105,9 @@ class DeferredColumnProperty(ColumnProperty): def execute(self, session, instance, row, identitykey, imap, isnew): if isnew: if not self.is_primary(): - sessionlib.global_attributes.create_history(instance, self.key, False, callable_=self.setup_loader(instance)) + sessionlib.attribute_manager.init_instance_attribute(instance, self.key, False, callable_=self.setup_loader(instance)) else: - sessionlib.global_attributes.reset_history(instance, self.key) + sessionlib.attribute_manager.reset_instance_attribute(instance, self.key) mapper.ColumnProperty = ColumnProperty @@ -150,7 +154,7 @@ class PropertyLoader(mapper.MapperProperty): def cascade_iterator(self, type, object, recursive): if not type in self.cascade: return - childlist = sessionlib.global_attributes.get_history(object, self.key, passive=True) + childlist = sessionlib.attribute_manager.get_history(object, self.key, passive=True) mapper = self.mapper.primary_mapper() for c in childlist.added_items() + childlist.deleted_items() + childlist.unchanged_items(): @@ -163,9 +167,9 @@ class PropertyLoader(mapper.MapperProperty): def cascade_callable(self, type, object, callable_, recursive): if not type in self.cascade: return - childlist = sessionlib.global_attributes.get_history(object, self.key, passive=True) + mapper = self.mapper.primary_mapper() - for c in childlist.added_items() + childlist.deleted_items() + childlist.unchanged_items(): + for c in sessionlib.attribute_manager.get_as_list(object, self.key, passive=True): if c is not None and c not in recursive: recursive.add(c) callable_(c, mapper.entity_name) @@ -241,16 +245,16 @@ class PropertyLoader(mapper.MapperProperty): if self.backref is not None: self.backref.compile(self) - elif not sessionlib.global_attributes.is_class_managed(parent.class_, key): + elif not sessionlib.attribute_manager.is_class_managed(parent.class_, 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' " % (key, parent.class_.__name__, parent.class_.__name__)) self.do_init_subclass(key, parent) def _register_attribute(self, class_, callable_=None): - sessionlib.global_attributes.register_attribute(class_, self.key, uselist = self.uselist, extension=self.attributeext, cascade=self.cascade, trackparent=True, callable_=callable_) + sessionlib.attribute_manager.register_attribute(class_, self.key, uselist = self.uselist, extension=self.attributeext, cascade=self.cascade, trackparent=True, callable_=callable_) - def _create_history(self, instance, callable_=None): - return sessionlib.global_attributes.create_history(instance, self.key, self.uselist, 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_) def _set_class_attribute(self, class_, key): """sets attribute behavior on our target class.""" @@ -298,7 +302,7 @@ class PropertyLoader(mapper.MapperProperty): if self.is_primary(): return #print "PLAIN PROPLOADER EXEC NON-PRIAMRY", repr(id(self)), repr(self.mapper.class_), self.key - self._create_history(instance) + self._init_instance_attribute(instance) def register_dependencies(self, uowcommit): self._dependency_processor.register_dependencies(uowcommit) @@ -388,15 +392,15 @@ class LazyLoader(PropertyLoader): if not self.is_primary(): #print "EXEC NON-PRIAMRY", repr(self.mapper.class_), self.key # we are not the primary manager for this attribute on this class - set up a per-instance lazyloader, - # which will override the class-level behavior - self._create_history(instance, callable_=self.setup_loader(instance)) + # which will override the clareset_instance_attributess-level behavior + self._init_instance_attribute(instance, callable_=self.setup_loader(instance)) else: #print "EXEC PRIMARY", repr(self.mapper.class_), 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.global_attributes.reset_history(instance, self.key) + sessionlib.attribute_manager.reset_instance_attribute(instance, self.key) def create_lazy_clause(table, primaryjoin, secondaryjoin, foreignkey): binds = {} @@ -559,24 +563,29 @@ class EagerLoader(LazyLoader): LazyLoader.execute(self, session, instance, row, identitykey, imap, isnew) return - if isnew: - # new row loaded from the database. initialize a blank container on the instance. - # this will override any per-class lazyloading type of stuff. - h = self._create_history(instance) if not self.uselist: if isnew: - h.setattr_clean(self.mapper._instance(session, decorated_row, imap, None)) + # set a scalar object instance directly on the parent object, + # bypassing SmartProperty event handlers. + instance.__dict__[self.key] = self.mapper._instance(session, decorated_row, imap, None) else: # call _instance on the row, even though the object has been created, # so that we further descend into properties self.mapper._instance(session, decorated_row, imap, None) return - elif isnew: - result_list = h else: - result_list = getattr(instance, self.key) + if isnew: + # 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. + imap['_scratch'][(instance, self.key)] = appender + result_list = imap['_scratch'][(instance, self.key)] self.mapper._instance(session, decorated_row, imap, result_list) def _create_decorator_row(self): diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 1ba6d1a35..7c86fafb5 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -339,7 +339,7 @@ class Session(object): return if not hasattr(object, '_instance_key'): raise exceptions.InvalidRequestError("Instance '%s' is not persisted" % repr(object)) - if global_attributes.is_modified(object): + if attribute_manager.is_modified(object): self._register_dirty(object) else: self._register_clean(object) @@ -425,7 +425,7 @@ def class_mapper(class_, **kwargs): # this is the AttributeManager instance used to provide attribute behavior on objects. # to all the "global variable police" out there: its a stateless object. -global_attributes = unitofwork.global_attributes +attribute_manager = unitofwork.attribute_manager # this dictionary maps the hash key of a Session to the Session itself, and # acts as a Registry with which to locate Sessions. this is to enable diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py index 5c222edee..dcaf750de 100644 --- a/lib/sqlalchemy/orm/unitofwork.py +++ b/lib/sqlalchemy/orm/unitofwork.py @@ -28,41 +28,28 @@ import sets # with the "echo_uow=True" keyword argument. LOG = False -class UOWProperty(attributes.SmartProperty): - """overrides SmartProperty to provide ORM-specific accessors""" - def __init__(self, class_, *args, **kwargs): - super(UOWProperty, self).__init__(*args, **kwargs) +class UOWEventHandler(attributes.AttributeExtension): + """an event handler added to all class attributes which handles session operations.""" + def __init__(self, key, class_, cascade=None): + self.key = key self.class_ = class_ - property = property(lambda s:class_mapper(s.class_).props[s.key], doc="returns the MapperProperty object associated with this property") - - -class UOWListElement(attributes.ListAttribute): - """overrides ListElement to provide unit-of-work "dirty" hooks when list attributes are modified, - plus specialzed append() method.""" - def __init__(self, obj, key, data=None, cascade=None, **kwargs): - attributes.ListAttribute.__init__(self, obj, key, data=data, **kwargs) self.cascade = cascade - def do_value_changed(self, obj, key, item, listval, isdelete): + def append(self, event, obj, item): sess = object_session(obj) if sess is not None: sess._register_changed(obj) - if self.cascade is not None and not isdelete and self.cascade.save_update and item not in sess: + if self.cascade is not None and self.cascade.save_update and item not in sess: mapper = object_mapper(obj) prop = mapper.props[self.key] ename = prop.mapper.entity_name sess.save_or_update(item, entity_name=ename) - def append(self, item, _mapper_nohistory = False): - if _mapper_nohistory: - self.append_nohistory(item) - else: - attributes.ListAttribute.append(self, item) - -class UOWScalarElement(attributes.ScalarAttribute): - def __init__(self, obj, key, cascade=None, **kwargs): - attributes.ScalarAttribute.__init__(self, obj, key, **kwargs) - self.cascade=cascade - def do_value_changed(self, oldvalue, newvalue): - obj = self.obj + + def delete(self, event, obj, item): + sess = object_session(obj) + if sess is not None: + sess._register_changed(obj) + + def set(self, event, obj, newvalue, oldvalue): sess = object_session(obj) if sess is not None: sess._register_changed(obj) @@ -71,21 +58,24 @@ class UOWScalarElement(attributes.ScalarAttribute): prop = mapper.props[self.key] ename = prop.mapper.entity_name sess.save_or_update(newvalue, entity_name=ename) + +class UOWProperty(attributes.InstrumentedAttribute): + """overrides InstrumentedAttribute to provide an extra AttributeExtension to all managed attributes + as well as the 'property' property.""" + def __init__(self, manager, class_, key, uselist, callable_, typecallable, cascade=None, extension=None, **kwargs): + extension = util.to_list(extension or []) + extension.insert(0, UOWEventHandler(key, class_, cascade=cascade)) + super(UOWProperty, self).__init__(manager, key, uselist, callable_, typecallable, extension=extension,**kwargs) + self.class_ = class_ + + property = property(lambda s:class_mapper(s.class_).props[s.key], doc="returns the MapperProperty object associated with this property") class UOWAttributeManager(attributes.AttributeManager): - """overrides AttributeManager to provide unit-of-work "dirty" hooks when scalar attribues are modified, plus factory methods for UOWProperrty/UOWListElement.""" - def __init__(self): - attributes.AttributeManager.__init__(self) + """overrides AttributeManager to provide the UOWProperty instance for all InstrumentedAttributes.""" + def create_prop(self, class_, key, uselist, callable_, typecallable, **kwargs): + return UOWProperty(self, class_, key, uselist, callable_, typecallable, **kwargs) - def create_prop(self, class_, key, uselist, callable_, **kwargs): - return UOWProperty(class_, self, key, uselist, callable_, **kwargs) - def create_scalar(self, obj, key, **kwargs): - return UOWScalarElement(obj, key, **kwargs) - - def create_list(self, obj, key, list_, **kwargs): - return UOWListElement(obj, key, list_, **kwargs) - class UnitOfWork(object): """main UOW object which stores lists of dirty/new/deleted objects. provides top-level "flush" functionality as well as the transaction boundaries with the SQLEngine(s) involved in a write operation.""" def __init__(self, identity_map=None): @@ -94,7 +84,6 @@ class UnitOfWork(object): else: self.identity_map = weakref.WeakValueDictionary() - self.attributes = global_attributes self.new = util.OrderedSet() self.dirty = util.Set() @@ -112,19 +101,17 @@ class UnitOfWork(object): self.identity_map[key] = obj def refresh(self, sess, obj): - self.rollback_object(obj) sess.query(obj.__class__)._get(obj._instance_key, reload=True) def expire(self, sess, obj): - self.rollback_object(obj) def exp(): sess.query(obj.__class__)._get(obj._instance_key, reload=True) - global_attributes.trigger_history(obj, exp) + attribute_manager.trigger_history(obj, exp) def is_expired(self, obj, unexpire=False): - ret = global_attributes.has_trigger(obj) + ret = attribute_manager.has_trigger(obj) if ret and unexpire: - global_attributes.untrigger_history(obj) + attribute_manager.untrigger_history(obj) return ret def has_key(self, key): @@ -151,8 +138,6 @@ class UnitOfWork(object): self.new.remove(obj) except KeyError: pass - #self.attributes.commit(obj) - self.attributes.remove(obj) def _validate_obj(self, obj): """validates that dirty/delete/flush operations can occur upon the given object, by checking @@ -167,10 +152,10 @@ class UnitOfWork(object): self.register_dirty(obj) def register_attribute(self, class_, key, uselist, **kwargs): - self.attributes.register_attribute(class_, key, uselist, **kwargs) + attribute_manager.register_attribute(class_, key, uselist, **kwargs) def register_callable(self, obj, key, func, uselist, **kwargs): - self.attributes.set_callable(obj, key, func, uselist, **kwargs) + attribute_manager.set_callable(obj, key, func, uselist, **kwargs) def register_clean(self, obj): try: @@ -185,7 +170,7 @@ class UnitOfWork(object): mapper = object_mapper(obj) obj._instance_key = mapper.instance_key(obj) self._put(obj._instance_key, obj) - self.attributes.commit(obj) + attribute_manager.commit(obj) def register_new(self, obj): if hasattr(obj, '_instance_key'): @@ -251,7 +236,7 @@ class UnitOfWork(object): def rollback_object(self, obj): """'rolls back' the attributes that have been changed on an object instance.""" - self.attributes.rollback(obj) + attribute_manager.rollback(obj) try: self.dirty.remove(obj) except KeyError: @@ -906,5 +891,5 @@ def object_mapper(obj): def class_mapper(class_): return sqlalchemy.class_mapper(class_) -global_attributes = UOWAttributeManager() +attribute_manager = UOWAttributeManager() |
