collection doesn't contain the item, supports noload collections
[ticket:813]
+- inline optimizations added to locate_dirty() which can greatly speed up
+ repeated calls to flush(), as occurs with autoflush=True [ticket:816]
+
- The IdentifierPreprarer's _requires_quotes test is now regex based. Any
out-of-tree dialects that provide custom sets of legal_characters or
illegal_initial_characters will need to move to regexes or override
self.callable_ = callable_
self.trackparent = trackparent
self.mutable_scalars = mutable_scalars
+ if mutable_scalars:
+ class_._sa_has_mutable_scalars = True
self.copy = None
if compare_function is None:
self.is_equal = lambda x,y: x == y
def __init__(self, class_, manager, key, callable_, trackparent=False, extension=None, copy_function=None, compare_function=None, mutable_scalars=False, **kwargs):
super(ScalarAttributeImpl, self).__init__(class_, manager, key,
callable_, trackparent=trackparent, extension=extension,
- compare_function=compare_function, **kwargs)
- self.mutable_scalars = mutable_scalars
+ compare_function=compare_function, mutable_scalars=mutable_scalars, **kwargs)
if copy_function is None:
copy_function = self.__copy
def _is_modified(self, state):
if state.modified:
return True
- else:
+ elif getattr(state.class_, '_sa_has_mutable_scalars', False):
for attr in self.managed_attributes(state.class_):
- if attr.impl.check_mutable_modified(state):
+ if getattr(attr.impl, 'mutable_scalars', False) and attr.impl.check_mutable_modified(state):
return True
else:
return False
-
+ else:
+ return False
+
def get_history(self, obj, key, **kwargs):
"""Return a new ``AttributeHistory`` object for the given
attribute on the given object.
either contain changes or are marked as deleted.
"""
- return util.Set([x for x in self.identity_map.values() if x not in self.deleted and attribute_manager.is_modified(x)])
+ # a little bit of inlining for speed
+ return util.Set([x for x in self.identity_map.values()
+ if x not in self.deleted
+ and (
+ x._state.modified
+ or (getattr(x.__class__, '_sa_has_mutable_scalars', False) and attribute_manager._is_modified(x._state))
+ )
+ ])
def flush(self, session, objects=None):
"""create a dependency tree of all pending SQL operations within this unit of work and execute."""
# communication with the mappers and relationships to fire off SQL
# and synchronize attributes between related objects.
+ # detect persistent objects that have changes
+ dirty = self.locate_dirty()
+
+ if len(dirty) == 0 and len(self.deleted) == 0 and len(self.new) == 0:
+ return
+
flush_context = UOWTransaction(self, session)
if session.extension is not None:
else:
# or just everything
objset = util.Set(self.identity_map.values()).union(self.new)
-
- # detect persistent objects that have changes
- dirty = self.locate_dirty()
-
+
# store objects whose fate has been decided
processed = util.Set()
for obj in self.deleted.intersection(objset).difference(processed):
flush_context.register_object(obj, isdelete=True)
+ if len(flush_context.tasks) == 0:
+ return
+
session.create_transaction(autoflush=False)
flush_context.transaction = session.transaction
try:
assert log == ['before_flush', 'after_flush', 'after_flush_postexec']
log = []
+ u.user_name = 'ed'
sess.commit()
assert log == ['before_commit', 'before_flush', 'after_flush', 'after_flush_postexec', 'after_commit']
-
+
+ log = []
+ sess.commit()
+ assert log == ['before_commit', 'after_commit']
class ScopedSessionTest(ORMTest):