summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy/orm')
-rw-r--r--lib/sqlalchemy/orm/dependency.py3
-rw-r--r--lib/sqlalchemy/orm/mapper.py28
-rw-r--r--lib/sqlalchemy/orm/properties.py59
-rw-r--r--lib/sqlalchemy/orm/session.py4
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py85
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()