diff -r 63dac5212552 Doc/library/sqlite3.rst --- a/Doc/library/sqlite3.rst Sat Jan 10 09:00:20 2015 +0100 +++ b/Doc/library/sqlite3.rst Sun Jan 11 09:58:07 2015 +0200 @@ -690,6 +690,27 @@ Now we plug :class:`Row` in:: 35.14 +NamedTupleRow Objects +--------------------- + +.. function:: NamedTupleRow + + A :func:`NamedTupleRow` instance serves as a + :attr:`~Connection.row_factory` for :class:`Connection` objects. + It returns a :func:`~collections.namedtuple` representation of a + row. + +Using the example above we plug :func:`NamedTupleRow` in:: + + >>> conn.row_factory = sqlite3.NamedTupleRow + >>> c = conn.cursor() + >>> c.execute('select * from stocks') + + >>> r = c.fetchone() + >>> r + Row(date='2006-01-05', trans='BUY', symbol='RHAT', qty=100.0, price=35.14) + + .. _sqlite3-types: SQLite and Python types diff -r 63dac5212552 Lib/sqlite3/dbapi2.py --- a/Lib/sqlite3/dbapi2.py Sat Jan 10 09:00:20 2015 +0100 +++ b/Lib/sqlite3/dbapi2.py Sun Jan 11 09:58:07 2015 +0200 @@ -87,3 +87,18 @@ register_adapters_and_converters() # Clean up namespace del(register_adapters_and_converters) + +# TODO: Use lru_cache() after adding C implementation +_cache = {} +def NamedTupleRow(cursor, row): + """ + Return a namedtuple row factory for connection objects. + """ + fields = tuple([col[0] for col in cursor.description]) + try: + cls = _cache[fields] + except KeyError: + if len(_cache) >= 20: + _cache.clear() + _cache[fields] = cls = collections.namedtuple('Row', fields) + return cls._make(row) diff -r 63dac5212552 Lib/sqlite3/test/factory.py --- a/Lib/sqlite3/test/factory.py Sat Jan 10 09:00:20 2015 +0100 +++ b/Lib/sqlite3/test/factory.py Sun Jan 11 09:58:07 2015 +0200 @@ -162,6 +162,33 @@ class RowFactoryTests(unittest.TestCase) self.assertEqual(list(reversed(row)), list(reversed(as_tuple))) self.assertIsInstance(row, Sequence) + def CheckSqliteNamedTuple(self): + self.con.row_factory = sqlite.NamedTupleRow + row = self.con.execute("select 1 as a, 2 as b").fetchone() + self.assertIsInstance(row, tuple) + self.assertEqual(row, (1, 2)) + + self.assertEqual(row[0], 1, "by index: wrong result for column 0") + self.assertEqual(row[1], 2, "by index: wrong result for column 1") + self.assertEqual(row[-1], 2, "by index: wrong result for column -1") + self.assertEqual(row[-2], 1, "by index: wrong result for column -2") + + self.assertTrue(hasattr(row, 'a'), "has no attribute 'a'") + self.assertEqual(row.a, 1, "attribute: wrong result for column 'a'") + self.assertTrue(hasattr(row, 'b'), "has no attribute 'b'") + self.assertEqual(row.b, 2, "attribute: wrong result for column 'b'") + + with self.assertRaises(AttributeError): + row.c + with self.assertRaises(AttributeError): + row.A + with self.assertRaises(IndexError): + row[2] + with self.assertRaises(IndexError): + row[-3] + with self.assertRaises(IndexError): + row[2**1000] + def tearDown(self): self.con.close()