]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
unify query.with_parent, util.with_parent, fix docs some more
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 6 Sep 2010 16:24:28 +0000 (12:24 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 6 Sep 2010 16:24:28 +0000 (12:24 -0400)
doc/build/orm/query.rst
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/orm/util.py
test/orm/test_query.py

index 6f94f5c90ae8c5023654c0bff3eaa7547b14fb37..29b4196d14d66e42f07e6cce9c8db98e8197d604 100644 (file)
@@ -36,4 +36,5 @@ The public name of the :class:`.AliasedClass` class.
 
 .. autofunction:: outerjoin
 
+.. autofunction:: with_parent
 
index 9da7d3ea84f9ecb9b7e5ed77f2c96c98d463f701..3bf8280f64543da62c1bf446eaa9a0f80a2eb808 100644 (file)
@@ -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):
index 0f4adec001ea6b1fa1ae7a6979f266cc2f08ca65..a5ddf2f6ecdd9b44e1823e129a483dd8df88a64b 100644 (file)
@@ -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):
index d58f09565d2190c93f736362e1562e4ca15936d5..3a6436610f6eafcaed40bd5761fc211c3f6c38b5 100644 (file)
@@ -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)