]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Provide special row proxies for count and index
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 17 Mar 2021 12:39:45 +0000 (08:39 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 17 Mar 2021 16:30:22 +0000 (12:30 -0400)
The Python ``namedtuple()`` has the behavior such that the names ``count``
and ``index`` will be served as tuple values if the named tuple includes
those names; if they are absent, then their behavior as methods of
``collections.abc.Sequence`` is maintained. Therefore the
:class:`_result.Row` and :class:`_result.LegacyRow` classes have been fixed
so that they work in this same way, maintaining the expected behavior for
database rows that have columns named "index" or "count".

Fixes: #6074
Change-Id: I49a093da02f33f231d22ed5999c09fcaa3a68601

doc/build/changelog/unreleased_14/6074.rst [new file with mode: 0644]
lib/sqlalchemy/engine/row.py
test/sql/test_resultset.py

diff --git a/doc/build/changelog/unreleased_14/6074.rst b/doc/build/changelog/unreleased_14/6074.rst
new file mode 100644 (file)
index 0000000..88cb71e
--- /dev/null
@@ -0,0 +1,11 @@
+.. change::
+    :tags: bug, engine
+    :tickets: 6074
+
+    The Python ``namedtuple()`` has the behavior such that the names ``count``
+    and ``index`` will be served as tuple values if the named tuple includes
+    those names; if they are absent, then their behavior as methods of
+    ``collections.abc.Sequence`` is maintained. Therefore the
+    :class:`_result.Row` and :class:`_result.LegacyRow` classes have been fixed
+    so that they work in this same way, maintaining the expected behavior for
+    database rows that have columns named "index" or "count".
index ac65d1b18e61db8c3c090f4a68071455e6eda502..b870e6534ad3916faa5437b448e446402a9e0f56 100644 (file)
@@ -220,6 +220,27 @@ class Row(BaseRow, collections_abc.Sequence):
             self._data,
         )
 
+    def _special_name_accessor(name):
+        """Handle ambiguous names such as "count" and "index" """
+
+        @property
+        def go(self):
+            if self._parent._has_key(name):
+                return self.__getattr__(name)
+            else:
+
+                def meth(*arg, **kw):
+                    return getattr(collections_abc.Sequence, name)(
+                        self, *arg, **kw
+                    )
+
+                return meth
+
+        return go
+
+    count = _special_name_accessor("count")
+    index = _special_name_accessor("index")
+
     def __contains__(self, key):
         return key in self._data
 
index e99ce881ca909cdfa4f909a6840606637eed70a5..5439d63b574727d14f79204cca774cb5cfa1ea06 100644 (file)
@@ -28,6 +28,8 @@ from sqlalchemy import VARCHAR
 from sqlalchemy.engine import cursor as _cursor
 from sqlalchemy.engine import default
 from sqlalchemy.engine import Row
+from sqlalchemy.engine.result import SimpleResultMetaData
+from sqlalchemy.engine.row import LegacyRow
 from sqlalchemy.ext.compiler import compiles
 from sqlalchemy.sql import ColumnElement
 from sqlalchemy.sql import expression
@@ -1324,6 +1326,47 @@ class CursorResultTest(fixtures.TablesTest):
         )
         is_true(isinstance(row, collections_abc.Sequence))
 
+    @testing.combinations((Row,), (LegacyRow,))
+    def test_row_special_names(self, row_cls):
+        metadata = SimpleResultMetaData(["key", "count", "index"])
+        row = row_cls(
+            metadata,
+            [None, None, None],
+            metadata._keymap,
+            Row._default_key_style,
+            ["kv", "cv", "iv"],
+        )
+        is_true(isinstance(row, collections_abc.Sequence))
+
+        eq_(row.key, "kv")
+        eq_(row.count, "cv")
+        eq_(row.index, "iv")
+
+        if isinstance(row, LegacyRow):
+            eq_(row["count"], "cv")
+            eq_(row["index"], "iv")
+
+        eq_(row._mapping["count"], "cv")
+        eq_(row._mapping["index"], "iv")
+
+        metadata = SimpleResultMetaData(["key", "q", "p"])
+
+        row = row_cls(
+            metadata,
+            [None, None, None],
+            metadata._keymap,
+            Row._default_key_style,
+            ["kv", "cv", "iv"],
+        )
+        is_true(isinstance(row, collections_abc.Sequence))
+
+        eq_(row.key, "kv")
+        eq_(row.q, "cv")
+        eq_(row.p, "iv")
+        eq_(row.index("cv"), 1)
+        eq_(row.count("cv"), 1)
+        eq_(row.count("x"), 0)
+
     def test_row_is_hashable(self):
 
         row = Row(