summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/attributes.py43
-rw-r--r--lib/sqlalchemy/orm/dependency.py3
-rw-r--r--lib/sqlalchemy/orm/unitofwork.py27
3 files changed, 51 insertions, 22 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 745032e44..3eaa41e8f 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -674,7 +674,6 @@ class ScalarAttributeImpl(AttributeImpl):
self._remove_token = Event(self, OP_REMOVE)
def delete(self, state, dict_):
-
if self.dispatch._active_history:
old = self.get(state, dict_, PASSIVE_RETURN_NEVER_SET)
else:
@@ -683,10 +682,12 @@ class ScalarAttributeImpl(AttributeImpl):
if self.dispatch.remove:
self.fire_remove_event(state, dict_, old, self._remove_token)
state._modified_event(dict_, self, old)
- try:
- del dict_[self.key]
- except KeyError:
- raise AttributeError("%s object does not have a value" % self)
+
+ existing = dict_.pop(self.key, NO_VALUE)
+ if existing is NO_VALUE and old is NO_VALUE and \
+ not state.expired and \
+ self.key not in state.expired_attributes:
+ raise AttributeError("%s object does not have a value" % self)
def get_history(self, state, dict_, passive=PASSIVE_OFF):
if self.key in dict_:
@@ -744,11 +745,25 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl):
__slots__ = ()
def delete(self, state, dict_):
- old = self.get(state, dict_)
+ if self.dispatch._active_history:
+ old = self.get(
+ state, dict_,
+ passive=PASSIVE_ONLY_PERSISTENT |
+ NO_AUTOFLUSH | LOAD_AGAINST_COMMITTED)
+ else:
+ old = self.get(
+ state, dict_, passive=PASSIVE_NO_FETCH ^ INIT_OK |
+ LOAD_AGAINST_COMMITTED)
+
self.fire_remove_event(state, dict_, old, self._remove_token)
- try:
- del dict_[self.key]
- except:
+
+ existing = dict_.pop(self.key, NO_VALUE)
+
+ # if the attribute is expired, we currently have no way to tell
+ # that an object-attribute was expired vs. not loaded. So
+ # for this test, we look to see if the object has a DB identity.
+ if existing is NO_VALUE and old is not PASSIVE_NO_RESULT and \
+ state.key is None:
raise AttributeError("%s object does not have a value" % self)
def get_history(self, state, dict_, passive=PASSIVE_OFF):
@@ -1382,6 +1397,10 @@ class History(History):
# key situations
if id(original) in _NO_STATE_SYMBOLS:
deleted = ()
+ # indicate a "del" operation occured when we don't have
+ # the previous value as: ([None], (), ())
+ if id(current) in _NO_STATE_SYMBOLS:
+ current = None
else:
deleted = [original]
if current is NEVER_SET:
@@ -1398,7 +1417,7 @@ class History(History):
return cls((), (), ())
else:
return cls((), [current], ())
- elif current is original:
+ elif current is original and current is not NEVER_SET:
return cls((), [current], ())
else:
# current convention on related objects is to not
@@ -1408,6 +1427,10 @@ class History(History):
# ignore the None in any case.
if id(original) in _NO_STATE_SYMBOLS or original is None:
deleted = ()
+ # indicate a "del" operation occured when we don't have
+ # the previous value as: ([None], (), ())
+ if id(current) in _NO_STATE_SYMBOLS:
+ current = None
else:
deleted = [original]
if current is NO_VALUE or current is NEVER_SET:
diff --git a/lib/sqlalchemy/orm/dependency.py b/lib/sqlalchemy/orm/dependency.py
index 1a68ea9c7..960b9e5d5 100644
--- a/lib/sqlalchemy/orm/dependency.py
+++ b/lib/sqlalchemy/orm/dependency.py
@@ -752,6 +752,9 @@ class ManyToOneDP(DependencyProcessor):
for child in history.added:
self._synchronize(state, child, None, False,
uowcommit, "add")
+ elif history.deleted:
+ self._synchronize(
+ state, None, None, True, uowcommit, "delete")
if self.post_update:
self._post_update(state, uowcommit, history.sum())
diff --git a/lib/sqlalchemy/orm/unitofwork.py b/lib/sqlalchemy/orm/unitofwork.py
index ae5cee8d2..a83a99d78 100644
--- a/lib/sqlalchemy/orm/unitofwork.py
+++ b/lib/sqlalchemy/orm/unitofwork.py
@@ -61,19 +61,22 @@ def track_cascade_events(descriptor, prop):
if prop.uselist
else "related attribute delete")
- # expunge pending orphans
- item_state = attributes.instance_state(item)
+ if item is not None and \
+ item is not attributes.NEVER_SET and \
+ item is not attributes.PASSIVE_NO_RESULT and \
+ prop._cascade.delete_orphan:
+ # expunge pending orphans
+ item_state = attributes.instance_state(item)
- if prop._cascade.delete_orphan and \
- prop.mapper._is_orphan(item_state):
- if sess and item_state in sess._new:
- sess.expunge(item)
- else:
- # the related item may or may not itself be in a
- # Session, however the parent for which we are catching
- # the event is not in a session, so memoize this on the
- # item
- item_state._orphaned_outside_of_session = True
+ if prop.mapper._is_orphan(item_state):
+ if sess and item_state in sess._new:
+ sess.expunge(item)
+ else:
+ # the related item may or may not itself be in a
+ # Session, however the parent for which we are catching
+ # the event is not in a session, so memoize this on the
+ # item
+ item_state._orphaned_outside_of_session = True
def set_(state, newvalue, oldvalue, initiator):
# process "save_update" cascade rules for when an instance