pypyodbc icon indicating copy to clipboard operation
pypyodbc copied to clipboard

NamedTupleRow broken

Open bkline opened this issue 5 years ago • 1 comments

Summary

Setting row_type_callable to NamedTupleRow causes data retrieval to fail in the constructor for the local Row class inside the global NamedTupleRow function.

Repro

Here's my repro case (tweak as necessary to adjust to your environment):

#!/usr/bin/env python

"""Repro case for pypyodbc.NamedTupleRow bug
"""

from argparse import ArgumentParser
from pypyodbc import connect, NamedTupleRow

parser = ArgumentParser()
parser.add_argument("--host", required=True)
parser.add_argument("--port", required=True)
parser.add_argument("--database", required=True)
parser.add_argument("--driver", required=True)
opts = parser.parse_args()
parms = dict(
    Driver=opts.driver,
    Server="{},{}".format(opts.host, opts.port),
    Database=opts.database,
    Trusted_Connection="yes",
)
parms = ";".join(["{}={}".format(*p) for p in parms.items()])
conn = connect(parms)
cursor = conn.cursor(row_type_callable=NamedTupleRow)
cursor.execute("CREATE TABLE #t (i INT)")
cursor.execute("INSERT INTO #t (i) VALUES (42)")
cursor.execute("SELECT * FROM #t")
row = cursor.fetchone()
print(row.i)

Output

And here's what happens:

$ python repro.py --host test-host --port 52300 --database test-db \
>                  --driver "{ODBC Driver 17 for SQL Server}"
Traceback (most recent call last):
  File "repro.py", line 27, in <module>
    row = cursor.fetchone()
  File "D:\Python\lib\site-packages\pypyodbc.py", line 1930, in fetchone
    return self._row_type(value_list)
  File "D:\Python\lib\site-packages\pypyodbc.py", line 1079, in __new__
    return super(Row, cls).__new__(cls, *iterable)
TypeError: __new__() takes 1 positional argument but 2 were given

Environment

Reproducible with Python 3.7.4 as well as 2.7.13 with pypyodbc-1.3.5 on various flavors of Windows Server.

bkline avatar Aug 25 '19 12:08 bkline

Unit test using free data source

In case you don't have access to SQL Server (and to nudge the project in the direction of using real unit tests):

#!/usr/bin/env python

"""Tests for pypyodbc package
"""

from unittest import TestCase, main
from pypyodbc import connect, NamedTupleRow

class Tests(TestCase):
    """Base class for unit tests"""
    CONNECTION_STRING = "Driver=SQLite3 ODBC Driver;Database=:memory:"

class CursorTests(Tests):
    """Tests of the cursor object"""

    def test_dictionary_rows(self):
        """Ensure that the cursor can provide rows indexable by column name"""
        conn = connect(self.CONNECTION_STRING)
        cursor = conn.cursor(row_type_callable=None)
        cursor.execute("CREATE TABLE t (i INT)")
        cursor.execute("INSERT INTO t (i) VALUES (42)")
        cursor.execute("SELECT * FROM t")
        row = cursor.fetchone()
        cursor.execute("DROP TABLE t")
        self.assertEqual(row["i"], 42)

    def test_named_tuple_rows(self):
        """Ensure that the cursor can provide rows with column attributes"""
        conn = connect(self.CONNECTION_STRING)
        cursor = conn.cursor(row_type_callable=NamedTupleRow)
        cursor.execute("CREATE TABLE t (i INT)")
        cursor.execute("INSERT INTO t (i) VALUES (42)")
        cursor.execute("SELECT * FROM t")
        row = cursor.fetchone()
        cursor.execute("DROP TABLE t")
        self.assertEqual(row.i, 42)


if __name__ == "__main__":
    main()

Test results

$ python tests/pypyodbc_tests.py -v
test_dictionary_rows (__main__.CursorTests)
Ensure that the cursor can provide rows indexable by column name ... ok
test_named_tuple_rows (__main__.CursorTests)
Ensure that the cursor can provide rows with column attributes ... ERROR

======================================================================
ERROR: test_named_tuple_rows (__main__.CursorTests)
Ensure that the cursor can provide rows with column attributes
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/pypyodbc_tests.py", line 34, in test_named_tuple_rows
    row = cursor.fetchone()
  File "D:\Python\lib\site-packages\pypyodbc.py", line 1930, in fetchone
    return self._row_type(value_list)
  File "D:\Python\lib\site-packages\pypyodbc.py", line 1079, in __new__
    return super(Row, cls).__new__(cls, *iterable)
TypeError: __new__() takes 1 positional argument but 2 were given

----------------------------------------------------------------------
Ran 2 tests in 0.026s

FAILED (errors=1)

bkline avatar Aug 25 '19 16:08 bkline