summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2021-03-17 17:02:21 +0000
committerGerrit Code Review <gerrit@ci3.zzzcomputing.com>2021-03-17 17:02:21 +0000
commit266ffe9b0c70cb7794379a77077a801689fcb97a (patch)
treed93f0610b9fd0e7b66bc04a74554222015bc1fbd
parent8b275553adf317685c80c54b4c477607b1468168 (diff)
parentaabc72bd33ba445c0a207432acf0aa1cf25263cb (diff)
downloadsqlalchemy-266ffe9b0c70cb7794379a77077a801689fcb97a.tar.gz
Merge "Provide special row proxies for count and index"
-rw-r--r--doc/build/changelog/unreleased_14/6074.rst11
-rw-r--r--lib/sqlalchemy/engine/row.py21
-rw-r--r--test/sql/test_resultset.py43
3 files changed, 75 insertions, 0 deletions
diff --git a/doc/build/changelog/unreleased_14/6074.rst b/doc/build/changelog/unreleased_14/6074.rst
new file mode 100644
index 000000000..88cb71eb1
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/6074.rst
@@ -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".
diff --git a/lib/sqlalchemy/engine/row.py b/lib/sqlalchemy/engine/row.py
index ac65d1b18..b870e6534 100644
--- a/lib/sqlalchemy/engine/row.py
+++ b/lib/sqlalchemy/engine/row.py
@@ -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
diff --git a/test/sql/test_resultset.py b/test/sql/test_resultset.py
index e99ce881c..5439d63b5 100644
--- a/test/sql/test_resultset.py
+++ b/test/sql/test_resultset.py
@@ -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(