From: Mike Bayer Date: Thu, 17 May 2012 19:48:39 +0000 (-0400) Subject: - [feature] Added utility feature X-Git-Tag: rel_0_8_0b1~420 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=93b29aefdcaa736cd180ab3ae31168163e9f0212;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - [feature] Added utility feature Session.enable_relationship_loading(), supersedes relationship.load_on_pending. Both features should be avoided, however. [ticket:2372] --- diff --git a/CHANGES b/CHANGES index 18867b54c5..3d4b0de188 100644 --- 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 diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index bc3523fe28..daa843dc5c 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -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 diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 00d640066a..55e0291b50 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -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) diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index eb15e033e7..40e4375ed5 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -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 \ diff --git a/lib/sqlalchemy/orm/state.py b/lib/sqlalchemy/orm/state.py index bb6104762e..9b0f7538f1 100644 --- a/lib/sqlalchemy/orm/state.py +++ b/lib/sqlalchemy/orm/state.py @@ -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__ diff --git a/lib/sqlalchemy/orm/strategies.py b/lib/sqlalchemy/orm/strategies.py index a9a73cd66c..e1f8387939 100644 --- a/lib/sqlalchemy/orm/strategies.py +++ b/lib/sqlalchemy/orm/strategies.py @@ -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 diff --git a/test/orm/test_load_on_fks.py b/test/orm/test_load_on_fks.py index 031ac66054..be355808e1 100644 --- a/test/orm/test_load_on_fks.py +++ b/test/orm/test_load_on_fks.py @@ -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()