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
: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
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
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)
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 \
modified = False
expired = False
deleted = False
+ _load_pending = False
def __init__(self, obj, manager):
self.class_ = obj.__class__
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
# 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()
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
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()