"0.4.7".
- orm
+ - Cascade rules can now function unidirectionally on an
+ otherwise bidirectional relation(), taking only
+ the cascade behavior of the operation's initiator into
+ account. This means that a cascade of "none" on one
+ side won't trigger a "save-update" operation when an element
+ is attached to that relation, even if the backref
+ does indicate "save-update" cascade.
+
- Added a new SessionExtension hook called after_attach().
This is called at the point of attachment for objects
via add(), add_all(), delete(), and merge().
session cascade operations.
"""
- def __init__(self, key, class_, cascade):
+ def __init__(self, key):
self.key = key
- self.class_ = class_
- self.cascade = cascade
def _target_mapper(self, state):
prop = _state_mapper(state).get_property(self.key)
# process "save_update" cascade rules for when an instance is appended to the list of another instance
sess = _state_session(state)
if sess:
- if self.cascade.save_update and item not in sess:
+ initiating_property = initiator.class_manager[initiator.key].property
+ if initiating_property.cascade.save_update and item not in sess:
sess.save_or_update(item, entity_name=self._target_mapper(state).entity_name)
def remove(self, state, item, initiator):
sess = _state_session(state)
if sess:
+ initiating_property = initiator.class_manager[initiator.key].property
# expunge pending orphans
- if self.cascade.delete_orphan and item in sess.new:
+ if initiating_property.cascade.delete_orphan and item in sess.new:
if self._target_mapper(state)._is_orphan(attributes.instance_state(item)):
sess.expunge(item)
return
sess = _state_session(state)
if sess:
- if newvalue is not None and self.cascade.save_update and newvalue not in sess:
+ initiating_property = initiator.class_manager[initiator.key].property
+ if newvalue is not None and initiating_property.cascade.save_update and newvalue not in sess:
sess.save_or_update(newvalue, entity_name=self._target_mapper(state).entity_name)
- if self.cascade.delete_orphan and oldvalue in sess.new:
+ if initiating_property.cascade.delete_orphan and oldvalue in sess.new:
sess.expunge(oldvalue)
to new InstrumentedAttributes.
"""
- cascade = kwargs.pop('cascade', None)
useobject = kwargs.get('useobject', False)
if useobject:
# for object-holding attributes, instrument UOWEventHandler
# to process per-attribute cascades
extension = util.to_list(kwargs.pop('extension', None) or [])
- extension.insert(0, UOWEventHandler(key, class_, cascade=cascade))
+ extension.insert(0, UOWEventHandler(key))
kwargs['extension'] = extension
return attributes.register_attribute(class_, key, *args, **kwargs)
assert users.count().scalar() == 1
assert orders.count().scalar() == 0
+class NoSaveCascadeTest(_fixtures.FixtureTest):
+ """test that backrefs don't force save-update cascades to occur
+ when they're not desired in the forwards direction."""
+
+ @testing.resolve_artifact_names
+ def test_unidirectional_cascade_o2m(self):
+ mapper(Order, orders)
+ mapper(User, users, properties = dict(
+ orders = relation(
+ Order, cascade="none", backref="user")
+ ))
+
+ sess = create_session()
+
+ o1 = Order()
+ sess.add(o1)
+ u1 = User(orders=[o1])
+ assert u1 not in sess
+ assert o1 in sess
+
+ sess.clear()
+ u1 = User()
+ sess.add(u1)
+ o1 = Order()
+ o1.user = u1
+ assert u1 in sess
+ assert o1 in sess
+
+ @testing.resolve_artifact_names
+ def test_unidirectional_cascade_m2o(self):
+ mapper(Order, orders, properties={
+ 'user':relation(User, cascade="none", backref="orders")
+ })
+ mapper(User, users)
+
+ sess = create_session()
+
+ o1 = Order()
+ sess.add(o1)
+ o1.user = u1 = User()
+ assert u1 not in sess
+ assert o1 in sess
+
+ sess.clear()
+
+ o1 = Order()
+ sess.add(o1)
+ u1 = User(orders=[o1])
+ assert u1 in sess
+ assert o1 in sess
+
+ sess.clear()
+
+ o1 = Order()
+ o1.user = u1 = User()
+ sess.add(o1)
+ assert u1 not in sess
+ assert o1 in sess
+
+ sess.clear()
+
+ o1 = Order()
+ u1 = User(orders=[o1])
+ sess.add(u1)
+ assert u1 in sess
+ assert o1 in sess
+
+ @testing.resolve_artifact_names
+ def test_unidirectional_cascade_m2m(self):
+ mapper(Item, items, properties={
+ 'keywords':relation(Keyword, secondary=item_keywords, cascade="none", backref="items")
+ })
+ mapper(Keyword, keywords)
+
+ sess = create_session()
+
+ i1 = Item()
+ k1 = Keyword()
+ sess.add(i1)
+ i1.keywords.append(k1)
+ assert i1 in sess
+ assert k1 not in sess
+
+ sess.clear()
+
+ i1 = Item()
+ k1 = Keyword()
+ sess.add(i1)
+ k1.items.append(i1)
+ assert i1 in sess
+ assert k1 in sess
+
+
class O2MCascadeNoOrphanTest(_fixtures.FixtureTest):
run_inserts = None