]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- [feature] Added utility feature
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 17 May 2012 19:48:39 +0000 (15:48 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 17 May 2012 19:48:39 +0000 (15:48 -0400)
Session.enable_relationship_loading(),
supersedes relationship.load_on_pending.
Both features should be avoided, however.
[ticket:2372]

CHANGES
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/orm/session.py
lib/sqlalchemy/orm/state.py
lib/sqlalchemy/orm/strategies.py
test/orm/test_load_on_fks.py

diff --git a/CHANGES b/CHANGES
index 18867b54c5ed43019f2e7cff3502d03fc0b2ae1a..3d4b0de188a484eea0734d0e307807d135b7288c 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -79,6 +79,12 @@ CHANGES
     set of objects that weren't modified in 
     that sub-transaction.  [ticket:2452]
 
+  - [feature] Added utility feature 
+    Session.enable_relationship_loading(),
+    supersedes relationship.load_on_pending.
+    Both features should be avoided, however.
+    [ticket:2372]
+
   - [bug] Fixed bug whereby subqueryload() from 
     a polymorphic mapping to a target would incur
     a new invocation of the query for each 
index bc3523fe282ef09f82870e187682ec1075609a49..daa843dc5cd8fd8efcf1096ceb75cb12dca5785c 100644 (file)
@@ -465,20 +465,17 @@ def relationship(argument, secondary=None, **kwargs):
 
     :param load_on_pending=False:
       Indicates loading behavior for transient or pending parent objects.
+      
+      .. note::
+      
+      load_on_pending is superseded by :meth:`.Session.enable_relationship_loading`.
 
       When set to ``True``, causes the lazy-loader to
       issue a query for a parent object that is not persistent, meaning it has
       never been flushed.  This may take effect for a pending object when
       autoflush is disabled, or for a transient object that has been
       "attached" to a :class:`.Session` but is not part of its pending
-      collection. Attachment of transient objects to the session without
-      moving to the "pending" state is not a supported behavior at this time.
-
-      Note that the load of related objects on a pending or transient object
-      also does not trigger any attribute change events - no user-defined
-      events will be emitted for these attributes, and if and when the 
-      object is ultimately flushed, only the user-specific foreign key 
-      attributes will be part of the modified state.
+      collection. 
 
       The load_on_pending flag does not improve behavior
       when the ORM is used normally - object references should be constructed
@@ -486,7 +483,7 @@ def relationship(argument, secondary=None, **kwargs):
       are present in an ordinary way before flush() proceeds.  This flag
       is not not intended for general use.
 
-      New in 0.6.5.
+      .. versionadded:: 0.6.5
 
     :param order_by:
       indicates the ordering that should be applied when loading these
index 00d640066a47260e81f293f02ac4ef66ded02231..55e0291b5033257b46dda516361b3e97ee028e8a 100644 (file)
@@ -817,7 +817,6 @@ class CollectionAttributeImpl(AttributeImpl):
     def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
         if initiator and initiator.parent_token is self.parent_token:
             return
-
         collection = self.get_collection(state, dict_, passive=passive)
         if collection is PASSIVE_NO_RESULT:
             value = self.fire_append_event(state, dict_, value, initiator)
index eb15e033e7b56e5e527d780ad1fea44cc350e2f2..40e4375ed55411cc11816d8547bd69bad86ab9cc 100644 (file)
@@ -1514,6 +1514,50 @@ class Session(object):
         self._deleted[state] = state.obj()
         self.identity_map.add(state)
 
+    def enable_relationship_loading(self, obj):
+        """Associate an object with this :class:`.Session` for related
+        object loading.
+
+        Accesses of attributes mapped with :class:`.relationship`
+        will attempt to load a value from the database using this
+        :class:`.Session` as the source of connectivity.  The values
+        will be loaded based on foreign key values present on this
+        object - it follows that this functionality
+        generally only works for many-to-one-relationships.
+
+        The object will be attached to this session, but will
+        ''not'' participate in any persistence operations; its state
+        for almost all purposes will remain either "transient" or
+        "detached", except for the case of relationship loading.
+        
+        Also note that backrefs will often not work as expected.
+        Altering a relationship-bound attribute on the target object
+        may not fire off a backref event, if the effective value
+        is what was already loaded from a foreign-key-holding value.
+        
+        The :meth:`.Session.enable_relationship_loading` method supersedes
+        the ``load_on_pending`` flag on :func:`.relationship`.   Unlike
+        that flag, :meth:`.Session.enable_relationship_loading` allows
+        an object to remain transient while still being able to load
+        related items.   
+        
+        To make a transient object associated with a :class:`.Session`
+        via :meth:`.Session.enable_relationship_loading` pending, add
+        it to the :class:`.Session` using :meth:`.Session.add` normally.
+
+        :meth:`.Session.enable_relationship_loading` does not improve
+        behavior when the ORM is used normally - object references should be constructed
+        at the object level, not at the foreign key level, so that they
+        are present in an ordinary way before flush() proceeds.  This method
+        is not intended for general use.
+
+        .. versionadded:: 0.8
+        
+        """
+        state = attributes.instance_state(obj)
+        self._attach(state)
+        state._load_pending = True
+
     def _attach(self, state):
         if state.key and \
             state.key in self.identity_map and \
index bb6104762ec586b7ca9b638f2ccab4bd5729f95d..9b0f7538f155f2c745fd23141578234890bf022e 100644 (file)
@@ -37,6 +37,7 @@ class InstanceState(object):
     modified = False
     expired = False
     deleted = False
+    _load_pending = False
 
     def __init__(self, obj, manager):
         self.class_ = obj.__class__
index a9a73cd66ce2fe29b5c040fabf6312d6caab4621..e1f83879393504f125b111240eba7d3bacf69c45 100644 (file)
@@ -448,8 +448,10 @@ class LazyLoader(AbstractRelationshipLoader):
         return criterion
 
     def _load_for_state(self, state, passive):
-        if not state.key and \
-            (not self.parent_property.load_on_pending or not state.session_id):
+        if not state.session_id or \
+            not state.key and \
+            not self.parent_property.load_on_pending and \
+            not state._load_pending:
             return attributes.ATTR_EMPTY
 
         pending = not state.key
index 031ac66054d0313f2b0bcb4c837d027d39b07ef8..be355808e1c665dab15b4f239ca792dd91ef3a5f 100644 (file)
@@ -112,6 +112,17 @@ class LoadOnFKsTest(AssertsExecutionResults, fixtures.TestBase):
         # should be turned on.
         assert c3 not in p1.children
 
+    def test_enable_rel_loading_disallows_backref_event(self):
+        sess.autoflush = False
+        c3 = Child()
+        sess.enable_relationship_loading(c3)
+        c3.parent_id = p1.id
+        c3.parent = p1
+
+        # c3.parent is already acting like a "load" here,
+        # so backref events don't work
+        assert c3 not in p1.children
+
     def test_load_on_persistent_allows_backref_event(self):
         Child.parent.property.load_on_pending = True
         c3 = Child()
@@ -121,6 +132,16 @@ class LoadOnFKsTest(AssertsExecutionResults, fixtures.TestBase):
 
         assert c3 in p1.children
 
+    def test_enable_rel_loading_on_persistent_disallows_backref_event(self):
+        c3 = Child()
+        sess.enable_relationship_loading(c3)
+        c3.parent_id = p1.id
+        c3.parent = p1
+
+        # c3.parent is already acting like a "load" here,
+        # so backref events don't work
+        assert c3 not in p1.children
+
     def test_no_load_on_pending_allows_backref_event(self):
         # users who stick with the program and don't use
         # 'load_on_pending' get expected behavior
@@ -274,21 +295,25 @@ class LoadOnFKsTest(AssertsExecutionResults, fixtures.TestBase):
             for attach in (False, True):
                 for autoflush in (False, True):
                     for manualflush in (False, True):
-                        Child.parent.property.load_on_pending = loadonpending
-                        sess.autoflush = autoflush
-                        c2 = Child()
+                        for enable_relationship_rel in (False, True):
+                            Child.parent.property.load_on_pending = loadonpending
+                            sess.autoflush = autoflush
+                            c2 = Child()
 
-                        if attach:
-                            sess._attach(instance_state(c2))
+                            if attach:
+                                sess._attach(instance_state(c2))
 
-                        c2.parent_id = p2.id
+                            if enable_relationship_rel:
+                                sess.enable_relationship_loading(c2)
 
-                        if manualflush:
-                           sess.flush()
+                            c2.parent_id = p2.id
 
-                        if loadonpending and attach:
-                            assert c2.parent is p2
-                        else:
-                            assert c2.parent is None
+                            if manualflush:
+                               sess.flush()
+
+                            if (loadonpending and attach) or enable_relationship_rel:
+                                assert c2.parent is p2
+                            else:
+                                assert c2.parent is None
 
-                        sess.rollback()
+                            sess.rollback()