From: Mike Bayer Date: Mon, 6 Sep 2010 16:24:28 +0000 (-0400) Subject: unify query.with_parent, util.with_parent, fix docs some more X-Git-Tag: rel_0_6_4~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c25437301c3a15b8829e3373f22cf6fa9691e36d;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git unify query.with_parent, util.with_parent, fix docs some more --- diff --git a/doc/build/orm/query.rst b/doc/build/orm/query.rst index 6f94f5c90a..29b4196d14 100644 --- a/doc/build/orm/query.rst +++ b/doc/build/orm/query.rst @@ -36,4 +36,5 @@ The public name of the :class:`.AliasedClass` class. .. autofunction:: outerjoin +.. autofunction:: with_parent diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 9da7d3ea84..3bf8280f64 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -32,7 +32,7 @@ from sqlalchemy.orm import ( from sqlalchemy.orm.util import ( AliasedClass, ORMAdapter, _entity_descriptor, _entity_info, _is_aliased_class, _is_mapped_class, _orm_columns, _orm_selectable, - join as orm_join, + join as orm_join,with_parent ) @@ -648,43 +648,28 @@ class Query(object): self._populate_existing = True def with_parent(self, instance, property=None): - """Add filtering criterion that relates this query's primary entity - to the given related instance, using established :func:`.relationship()` + """Add filtering criterion that relates the given instance + to a child object or collection, using its attribute state + as well as an 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. - + The method uses the :func:`.with_parent` function to generate + the clause, the result of which is passed to :meth:`.Query.filter`. + + Parameters are the same as :func:`.with_parent`, with the exception + that the given property can be None, in which case a search is + performed against this :class:`.Query` object's target mapper. + """ - from sqlalchemy.orm import properties - mapper = object_mapper(instance) + if property is None: + from sqlalchemy.orm import properties + mapper = object_mapper(instance) + for prop in mapper.iterate_properties: if isinstance(prop, properties.PropertyLoader) and \ prop.mapper is self._mapper_zero(): + property = prop break else: raise sa_exc.InvalidRequestError( @@ -694,12 +679,8 @@ class Query(object): self._mapper_zero().class_.__name__, instance.__class__.__name__) ) - else: - prop = mapper.get_property(property, resolve_synonyms=True) - return self.filter(prop.compare( - operators.eq, - instance, value_is_parent=True, - detect_transient_pending=True)) + + return self.filter(with_parent(instance, property)) @_generative() def add_entity(self, entity, alias=None): diff --git a/lib/sqlalchemy/orm/util.py b/lib/sqlalchemy/orm/util.py index 0f4adec001..a5ddf2f6ec 100644 --- a/lib/sqlalchemy/orm/util.py +++ b/lib/sqlalchemy/orm/util.py @@ -482,18 +482,30 @@ def outerjoin(left, right, onclause=None, join_to_left=True): return _ORMJoin(left, right, onclause, True, join_to_left) def with_parent(instance, prop): - """Return criterion which selects instances with a given parent. - - :param instance: a parent instance, which should be persistent - or detached. - - :param property: a class-attached descriptor, MapperProperty or - string property name - attached to the parent instance. - - :param \**kwargs: all extra keyword arguments are propagated - to the constructor of Query. - + """Create 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 has some :func:`.relationship`. + + :param property: + String property name, or class-bound attribute, which indicates + what relationship from the instance should be used to reconcile the + parent/child relationship. + """ if isinstance(prop, basestring): mapper = object_mapper(instance) @@ -501,7 +513,10 @@ def with_parent(instance, prop): elif isinstance(prop, attributes.QueryableAttribute): prop = prop.property - return prop.compare(operators.eq, instance, value_is_parent=True) + return prop.compare(operators.eq, + instance, + value_is_parent=True, + detect_transient_pending=True) def _entity_info(entity, compile=True): diff --git a/test/orm/test_query.py b/test/orm/test_query.py index d58f09565d..3a6436610f 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -1375,6 +1375,9 @@ class ParentTest(QueryTest): o = sess.query(Order).with_parent(u1, property='orders').all() assert [Order(description="order 1"), Order(description="order 3"), Order(description="order 5")] == o + o = sess.query(Order).with_parent(u1, property=User.orders).all() + assert [Order(description="order 1"), Order(description="order 3"), Order(description="order 5")] == o + o = sess.query(Order).filter(with_parent(u1, User.orders)).all() assert [Order(description="order 1"), Order(description="order 3"), Order(description="order 5")] == o @@ -1417,6 +1420,12 @@ class ParentTest(QueryTest): [Order(description="order 1"), Order(description="order 3"), Order(description="order 5")], o.all() ) + + o = sess.query(Order).filter(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() @@ -1428,6 +1437,10 @@ class ParentTest(QueryTest): sess.query(User).with_parent(opending, 'user').one(), User(id=o1.user_id) ) + eq_( + sess.query(User).filter(with_parent(opending, 'user')).one(), + User(id=o1.user_id) + ) def test_with_pending_no_autoflush(self): sess = Session(autoflush=False)