summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/attributes.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2006-09-23 20:26:20 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2006-09-23 20:26:20 +0000
commit7d74fc7785832ebd3bf39c9e42465e2d22b0c9e2 (patch)
treecf20c329d7f577ec7fc1846dd393b5adbf592f38 /lib/sqlalchemy/attributes.py
parent68e893d21af31edd2bbc6dec608c95457eaffde6 (diff)
downloadsqlalchemy-7d74fc7785832ebd3bf39c9e42465e2d22b0c9e2.tar.gz
- added "pickleable" module to test suite to have cPickle-compatible
test objects - added copy_function, compare_function arguments to InstrumentedAttribute - added MutableType mixin, copy_value/compare_values methods to TypeEngine, PickleType - ColumnProperty and DeferredProperty propigate the TypeEngine copy/compare methods to the attribute instrumentation - cleanup of UnitOfWork, removed unused methods - UnitOfWork "dirty" list is calculated across the total collection of persistent objects when called, no longer has register_dirty. - attribute system can still report "modified" status fairly quickly, but does extra work for InstrumentedAttributes that have detected a "mutable" type where catching the __set__() event is not enough (i.e. PickleTypes) - attribute tracking modified to be more intelligent about detecting changes, particularly with mutable types. TypeEngine objects now take a greater role in defining how to compare two scalar instances, including the addition of a MutableType mixin which is implemented by PickleType. unit-of-work now tracks the "dirty" list as an expression of all persistent objects where the attribute manager detects changes. The basic issue thats fixed is detecting changes on PickleType objects, but also generalizes type handling and "modified" object checking to be more complete and extensible.
Diffstat (limited to 'lib/sqlalchemy/attributes.py')
-rw-r--r--lib/sqlalchemy/attributes.py55
1 files changed, 45 insertions, 10 deletions
diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py
index 84a1d58fb..7fd9686e3 100644
--- a/lib/sqlalchemy/attributes.py
+++ b/lib/sqlalchemy/attributes.py
@@ -13,13 +13,28 @@ class InstrumentedAttribute(object):
PASSIVE_NORESULT = object()
- def __init__(self, manager, key, uselist, callable_, typecallable, trackparent=False, extension=None, **kwargs):
+ def __init__(self, manager, key, uselist, callable_, typecallable, trackparent=False, extension=None, copy_function=None, compare_function=None, **kwargs):
self.manager = manager
self.key = key
self.uselist = uselist
self.callable_ = callable_
self.typecallable= typecallable
self.trackparent = trackparent
+ if copy_function is None:
+ self._check_mutable_modified = False
+ if uselist:
+ self._copyfunc = lambda x: [y for y in x]
+ else:
+ # scalar values are assumed to be immutable unless a copy function
+ # is passed
+ self._copyfunc = lambda x: x
+ else:
+ self._check_mutable_modified = True
+ self._copyfunc = copy_function
+ if compare_function is None:
+ self._compare_function = lambda x,y: x == y
+ else:
+ self._compare_function = compare_function
self.extensions = util.to_list(extension or [])
def __set__(self, obj, value):
@@ -31,6 +46,23 @@ class InstrumentedAttribute(object):
return self
return self.get(obj)
+ def is_equal(self, x, y):
+ return self._compare_function(x, y)
+ def copy(self, value):
+ return self._copyfunc(value)
+
+ def check_mutable_modified(self, obj):
+ if self._check_mutable_modified:
+ h = self.get_history(obj, passive=True)
+ if h is not None and h.is_modified():
+ obj._state['modified'] = True
+ return True
+ else:
+ return False
+ else:
+ return False
+
+
def hasparent(self, item, optimistic=False):
"""return the boolean value of a "hasparent" flag attached to the given item.
@@ -490,16 +522,14 @@ class CommittedState(object):
if obj.__dict__.has_key(attr.key):
value = obj.__dict__[attr.key]
if value is not False:
- if attr.uselist:
- self.data[attr.key] = [x for x in value]
- # not tracking parent on lazy-loaded instances at the moment.
- # its not needed since they will be "optimistically" tested
+ self.data[attr.key] = attr.copy(value)
+
+ # not tracking parent on lazy-loaded instances at the moment.
+ # its not needed since they will be "optimistically" tested
+ #if attr.uselist:
#if attr.trackparent:
# [attr.sethasparent(x, True) for x in self.data[attr.key] if x is not None]
- else:
- self.data[attr.key] = value
- # not tracking parent on lazy-loaded instances at the moment.
- # its not needed since they will be "optimistically" tested
+ #else:
#if attr.trackparent and value is not None:
# attr.sethasparent(value, True)
@@ -550,7 +580,7 @@ class AttributeHistory(object):
if a not in self._unchanged_items:
self._deleted_items.append(a)
else:
- if current is original:
+ if attr.is_equal(current, original):
self._unchanged_items = [current]
self._added_items = []
self._deleted_items = []
@@ -564,6 +594,8 @@ class AttributeHistory(object):
#print "key", attr.key, "orig", original, "current", current, "added", self._added_items, "unchanged", self._unchanged_items, "deleted", self._deleted_items
def __iter__(self):
return iter(self._current)
+ def is_modified(self):
+ return len(self._deleted_items) > 0 or len(self._added_items) > 0
def added_items(self):
return self._added_items
def unchanged_items(self):
@@ -622,6 +654,9 @@ class AttributeManager(object):
yield value
def is_modified(self, object):
+ for attr in self.managed_attributes(object.__class__):
+ if attr.check_mutable_modified(object):
+ return True
return object._state.get('modified', False)
def init_attr(self, obj):