]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Add Query.one_or_none()
authorEric Siegerman <eric.siegerman@rci.rogers.com>
Thu, 24 Sep 2015 17:16:58 +0000 (13:16 -0400)
committerEric Siegerman <eric.siegerman@rci.rogers.com>
Thu, 24 Sep 2015 17:22:45 +0000 (13:22 -0400)
doc/build/changelog/changelog_10.rst
doc/build/orm/tutorial.rst
lib/sqlalchemy/orm/query.py
test/orm/test_query.py

index 09800bea812e544e785a73d33c9ba42e791ed73a..5c5ffed1d6bbeedfe47b049ada003b2fb1f599d4 100644 (file)
         to again work as it is based on an 8.0.x version of Postgresql.
         Fix courtesy Pete Hollobon.
 
+    .. change::
+        :tags: orm
+
+        Add Query.one_or_none() method.
 
 .. changelog::
     :version: 1.0.8
index 6958e15d812839bc44a70453a31b87afc742a3ea..ed8d05534e969f6f12cc6602acd37bbba987d200 100644 (file)
@@ -849,7 +849,7 @@ database results.  Here's a brief tour:
       ('%ed', 1, 0)
       {stop}<User(name='ed', fullname='Ed Jones', password='f8s7ccs')>
 
-* :meth:`~.Query.one()`, fully fetches all rows, and if not
+* :meth:`~.Query.one()` fully fetches all rows, and if not
   exactly one object identity or composite row is present in the result, raises
   an error.  With multiple rows found:
 
@@ -892,6 +892,11 @@ database results.  Here's a brief tour:
   web service, which may want to raise a "404 not found" when no results are found,
   but raise an application error when multiple results are found.
 
+* :meth:`~.Query.one_or_none` is like :meth:`~.Query.one`, except that if no
+  results are found, it doesn't raise an error; it just returns ``None``. Like
+  :meth:`~.Query.one`, however, it does raise an error if multiple results are
+  found.
+
 * :meth:`~.Query.scalar` invokes the :meth:`~.Query.one` method, and upon
   success returns the first column of the row:
 
index 7e77c66a827caf54bca2e2930d3e7b7c06782158..8c4925905d9d676ad3de4e1770873730e1cccf19 100644 (file)
@@ -2473,6 +2473,38 @@ class Query(object):
         else:
             return None
 
+    def one_or_none(self):
+        """Return at most one result or raise an exception.
+
+        Returns ``None`` if the query selects
+        no rows.  Raises ``sqlalchemy.orm.exc.MultipleResultsFound``
+        if multiple object identities are returned, or if multiple
+        rows are returned for a query that does not return object
+        identities.
+
+        Note that an entity query, that is, one which selects one or
+        more mapped classes as opposed to individual column attributes,
+        may ultimately represent many rows but only one row of
+        unique entity or entities - this is a successful result for
+        `one_or_none()`.
+
+        Calling ``one_or_none()`` results in an execution of the underlying
+        query.
+
+        .. versionchanged:: 1.0.9
+            Added ``one_or_none()``
+        """
+        ret = list(self)
+
+        l = len(ret)
+        if l == 1:
+            return ret[0]
+        elif l == 0:
+            return None
+        else:
+            raise orm_exc.MultipleResultsFound(
+                "Multiple rows were found for one_or_none()")
+
     def one(self):
         """Return exactly one result or raise an exception.
 
index b0501739f6942a8b8591a1fb8d3b04263c2a725c..4ae0b010a36657a56cca1c7d20db3b2545ae94aa 100644 (file)
@@ -3675,6 +3675,60 @@ class ImmediateTest(_fixtures.FixtureTest):
             sess.query(User).join(User.addresses).filter(User.id.in_([8, 9])).
             order_by(User.id).one)
 
+    def test_one_or_none(self):
+        User, Address = self.classes.User, self.classes.Address
+
+        sess = create_session()
+
+        eq_(sess.query(User).filter(User.id == 99).one_or_none(), None)
+
+        eq_(sess.query(User).filter(User.id == 7).one_or_none().id, 7)
+
+        assert_raises_message(
+            sa.orm.exc.MultipleResultsFound,
+            "Multiple rows were found for one_or_none\(\)",
+            sess.query(User).one_or_none)
+
+        eq_(sess.query(User.id, User.name).filter(User.id == 99).one_or_none(), None)
+
+        eq_(sess.query(User.id, User.name).filter(User.id == 7).one_or_none(),
+            (7, 'jack'))
+
+        assert_raises(
+            sa.orm.exc.MultipleResultsFound,
+            sess.query(User.id, User.name).one_or_none)
+
+        eq_(
+            (sess.query(User, Address).join(User.addresses).
+           filter(Address.id == 99)).one_or_none(), None)
+
+        eq_((sess.query(User, Address).
+            join(User.addresses).
+            filter(Address.id == 4)).one_or_none(),
+           (User(id=8), Address(id=4)))
+
+        assert_raises(
+            sa.orm.exc.MultipleResultsFound,
+            sess.query(User, Address).join(User.addresses).one_or_none)
+
+        # this result returns multiple rows, the first
+        # two rows being the same.  but uniquing is
+        # not applied for a column based result.
+        assert_raises(
+            sa.orm.exc.MultipleResultsFound,
+            sess.query(User.id).join(User.addresses).
+            filter(User.id.in_([8, 9])).order_by(User.id).one_or_none)
+
+        # test that a join which ultimately returns
+        # multiple identities across many rows still
+        # raises, even though the first two rows are of
+        # the same identity and unique filtering
+        # is applied ([ticket:1688])
+        assert_raises(
+            sa.orm.exc.MultipleResultsFound,
+            sess.query(User).join(User.addresses).filter(User.id.in_([8, 9])).
+            order_by(User.id).one_or_none)
+
     @testing.future
     def test_getslice(self):
         assert False