where a one-step Session constructor is desired. Most
users should stick with sessionmaker() for general use,
however.
-
+
+ - query.with_parent() now accepts transient objects
+ and will use the non-persistent values of their pk/fk
+ attributes in order to formulate the criterion.
+ Docs are also clarified as to the purpose of with_parent().
+
- The include_properties and exclude_properties arguments
to mapper() now accept Column objects as members in
addition to strings. This so that same-named Column
self.prop.parent.compile()
return self.prop
- def compare(self, op, value, value_is_parent=False, alias_secondary=True):
+ def compare(self, op, value,
+ value_is_parent=False,
+ alias_secondary=True,
+ detect_transient_pending=False):
if op == operators.eq:
if value is None:
if self.uselist:
else:
return self._optimized_compare(None,
value_is_parent=value_is_parent,
+ detect_transient_pending=detect_transient_pending,
alias_secondary=alias_secondary)
else:
- return self._optimized_compare(value,
- value_is_parent=value_is_parent,
- alias_secondary=alias_secondary)
+ return self._optimized_compare(value,
+ value_is_parent=value_is_parent,
+ detect_transient_pending=detect_transient_pending,
+ alias_secondary=alias_secondary)
else:
return op(self.comparator, value)
def _optimized_compare(self, value, value_is_parent=False,
- adapt_source=None, alias_secondary=True):
+ adapt_source=None,
+ detect_transient_pending=False,
+ alias_secondary=True):
if value is not None:
value = attributes.instance_state(value)
return self._get_strategy(strategies.LazyLoader).lazy_clause(value,
reverse_direction=not value_is_parent,
alias_secondary=alias_secondary,
+ detect_transient_pending=detect_transient_pending,
adapt_source=adapt_source)
def __str__(self):
self._populate_existing = True
def with_parent(self, instance, property=None):
- """Add a join criterion corresponding to a relationship to the given
- parent instance.
-
- instance
- a persistent or detached instance which is related to class
- represented by this query.
-
- property
- string name of the property which relates this query's class to the
- instance. if None, the method will attempt to find a suitable
- property.
-
- Currently, this method only works with immediate parent relationships,
- but in the future may be enhanced to work across a chain of parent
- mappers.
+ """Add filtering criterion that relates this query's primary entity
+ to the given related instance, using established :func:`.relationship()`
+ configuration.
+
+ The SQL rendered is the same as that rendered when a lazy loader
+ would fire off from the given parent on that attribute, meaning
+ that the appropriate state is taken from the parent object in
+ Python without the need to render joins to the parent table
+ in the rendered statement.
+
+ As of 0.6.4, this method accepts parent instances in all
+ persistence states, including transient, persistent, and detached.
+ Only the requisite primary key/foreign key attributes need to
+ be populated. Previous versions didn't work with transient
+ instances.
+
+ :param instance:
+ An instance which is related to the class represented by
+ this query via some :func:`.relationship`, that also
+ contains the appropriate attribute state that identifies
+ the child object or collection.
+
+ :param property:
+ String property name, or class-bound attribute, which indicates
+ what relationship should be used to reconcile the parent/child
+ relationship. If None, the method will use the first relationship
+ that links them together - note that this is not deterministic
+ in the case of multiple relationships linking parent/child,
+ so using None is not recommended.
"""
from sqlalchemy.orm import properties
prop = mapper.get_property(property, resolve_synonyms=True)
return self.filter(prop.compare(
operators.eq,
- instance, value_is_parent=True))
+ instance, value_is_parent=True,
+ detect_transient_pending=True))
@_generative()
def add_entity(self, entity, alias=None):
)
def lazy_clause(self, state, reverse_direction=False,
- alias_secondary=False, adapt_source=None):
+ alias_secondary=False,
+ adapt_source=None,
+ detect_transient_pending=False):
if state is None:
return self._lazy_none_clause(
reverse_direction,
else:
mapper = self.parent_property.parent
+ o = state.obj() # strong ref
+ dict_ = attributes.instance_dict(o)
+
def visit_bindparam(bindparam):
if bindparam.key in bind_to_col:
- # use the "committed" (database) version to get
- # query column values
- # also its a deferred value; so that when used
- # by Query, the committed value is used
- # after an autoflush occurs
- o = state.obj() # strong ref
- bindparam.value = \
- lambda: mapper._get_committed_attr_by_column(
- o, bind_to_col[bindparam.key])
+ # using a flag to enable "detect transient pending" so that
+ # the slightly different usage paradigm of "dynamic" loaders
+ # continue to work as expected, i.e. that all pending objects
+ # should use the "post flush" attributes, and to limit this
+ # newer behavior to the query.with_parent() method.
+ # It would be nice to do away with this flag.
+
+ if detect_transient_pending and \
+ (not state.key or not state.session_id):
+ bindparam.value = mapper._get_state_attr_by_column(
+ state, dict_, bind_to_col[bindparam.key])
+ else:
+ # send value as a lambda so that the value is
+ # acquired after any autoflush occurs.
+ bindparam.value = \
+ lambda: mapper._get_committed_state_attr_by_column(
+ state, dict_, bind_to_col[bindparam.key])
+
if self.parent_property.secondary is not None and alias_secondary:
criterion = sql_util.ClauseAdapter(
result.close()
result = \
sess.query(Foo).execution_options(stream_results=True).\
- subquery().execute()
+ statement.execute()
assert result.cursor.name
result.close()
finally:
q = sess.query(User)
u = q.filter(User.id==7).first()
+
eq_([User(id=7,
addresses=[Address(id=1, email_address='jack@bean.com')])],
q.filter(User.id==7).all())
q = sess.query(Item).with_parent(u1)
assert False
except sa_exc.InvalidRequestError, e:
- assert str(e) == "Could not locate a property which relates instances of class 'Item' to instances of class 'User'"
+ assert str(e) \
+ == "Could not locate a property which relates "\
+ "instances of class 'Item' to instances of class 'User'"
def test_m2m(self):
sess = create_session()
k = sess.query(Keyword).with_parent(i1).all()
assert [Keyword(name='red'), Keyword(name='small'), Keyword(name='square')] == k
+ def test_with_transient(self):
+ sess = Session()
+
+ q = sess.query(User)
+ u1 = q.filter_by(name='jack').one()
+ utrans = User(id=u1.id)
+ o = sess.query(Order).with_parent(utrans, 'orders')
+ eq_(
+ [Order(description="order 1"), Order(description="order 3"), Order(description="order 5")],
+ o.all()
+ )
+
+ def test_with_pending_autoflush(self):
+ sess = Session()
+
+ o1 = sess.query(Order).first()
+ opending = Order(id=20, user_id=o1.user_id)
+ sess.add(opending)
+ eq_(
+ sess.query(User).with_parent(opending, 'user').one(),
+ User(id=o1.user_id)
+ )
+
+ def test_with_pending_no_autoflush(self):
+ sess = Session(autoflush=False)
+
+ o1 = sess.query(Order).first()
+ opending = Order(user_id=o1.user_id)
+ sess.add(opending)
+ eq_(
+ sess.query(User).with_parent(opending, 'user').one(),
+ User(id=o1.user_id)
+ )
+
class InheritedJoinTest(_base.MappedTest, AssertsCompiledSQL):
run_setup_mappers = 'once'