From 30080fd63d29e736e64d4d7b790f36485e0099b1 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 28 May 2014 12:57:38 +0300 Subject: [PATCH] Issue #10203: sqlite3.Row now truly supports sequence protocol. In particular it supports reverse() and negative indices. Original patch by Claudiu Popa. --- Lib/sqlite3/dbapi2.py | 2 ++ Lib/sqlite3/test/factory.py | 34 +++++++++++++++++++++++++++++++--- Misc/NEWS | 3 +++ Modules/_sqlite/row.c | 26 ++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/Lib/sqlite3/dbapi2.py b/Lib/sqlite3/dbapi2.py index 00a798ba53..0d4dcaf620 100644 --- a/Lib/sqlite3/dbapi2.py +++ b/Lib/sqlite3/dbapi2.py @@ -21,6 +21,7 @@ # misrepresented as being the original software. # 3. This notice may not be removed or altered from any source distribution. +import collections import datetime import time @@ -51,6 +52,7 @@ version_info = tuple([int(x) for x in version.split(".")]) sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")]) Binary = buffer +collections.Sequence.register(Row) def register_adapters_and_converters(): def adapt_date(val): diff --git a/Lib/sqlite3/test/factory.py b/Lib/sqlite3/test/factory.py index 954862c0d2..0813a13f40 100644 --- a/Lib/sqlite3/test/factory.py +++ b/Lib/sqlite3/test/factory.py @@ -23,6 +23,7 @@ import unittest import sqlite3 as sqlite +from collections import Sequence class MyConnection(sqlite.Connection): def __init__(self, *args, **kwargs): @@ -96,9 +97,27 @@ class RowFactoryTests(unittest.TestCase): self.assertEqual(col1, 1, "by name: wrong result for column 'A'") self.assertEqual(col2, 2, "by name: wrong result for column 'B'") - col1, col2 = row[0], row[1] - self.assertEqual(col1, 1, "by index: wrong result for column 0") - self.assertEqual(col2, 2, "by index: wrong result for column 1") + self.assertEqual(row[0], 1, "by index: wrong result for column 0") + self.assertEqual(row[0L], 1, "by index: wrong result for column 0") + self.assertEqual(row[1], 2, "by index: wrong result for column 1") + self.assertEqual(row[1L], 2, "by index: wrong result for column 1") + self.assertEqual(row[-1], 2, "by index: wrong result for column -1") + self.assertEqual(row[-1L], 2, "by index: wrong result for column -1") + self.assertEqual(row[-2], 1, "by index: wrong result for column -2") + self.assertEqual(row[-2L], 1, "by index: wrong result for column -2") + + with self.assertRaises(IndexError): + row['c'] + with self.assertRaises(IndexError): + row[2] + with self.assertRaises(IndexError): + row[2L] + with self.assertRaises(IndexError): + row[-3] + with self.assertRaises(IndexError): + row[-3L] + with self.assertRaises(IndexError): + row[2**1000] def CheckSqliteRowIter(self): """Checks if the row object is iterable""" @@ -142,6 +161,15 @@ class RowFactoryTests(unittest.TestCase): self.assertNotEqual(row_1, row_3) self.assertNotEqual(hash(row_1), hash(row_3)) + def CheckSqliteRowAsSequence(self): + """ Checks if the row object can act like a sequence """ + self.con.row_factory = sqlite.Row + row = self.con.execute("select 1 as a, 2 as b").fetchone() + + as_tuple = tuple(row) + self.assertEqual(list(reversed(row)), list(reversed(as_tuple))) + self.assertIsInstance(row, Sequence) + def tearDown(self): self.con.close() diff --git a/Misc/NEWS b/Misc/NEWS index 663f389254..917c292309 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -18,6 +18,9 @@ Core and Builtins Library ------- +- Issue #10203: sqlite3.Row now truly supports sequence protocol. In particulr + it supports reverse() and negative indices. Original patch by Claudiu Popa. + - Issue #8743: Fix interoperability between set objects and the collections.Set() abstract base class. diff --git a/Modules/_sqlite/row.c b/Modules/_sqlite/row.c index 480b482ed8..ff525603e3 100644 --- a/Modules/_sqlite/row.c +++ b/Modules/_sqlite/row.c @@ -64,9 +64,16 @@ int pysqlite_row_init(pysqlite_Row* self, PyObject* args, PyObject* kwargs) return 0; } +PyObject* pysqlite_row_item(pysqlite_Row* self, Py_ssize_t idx) +{ + PyObject* item = PyTuple_GetItem(self->data, idx); + Py_XINCREF(item); + return item; +} + PyObject* pysqlite_row_subscript(pysqlite_Row* self, PyObject* idx) { - long _idx; + Py_ssize_t _idx; char* key; int nitems, i; char* compare_key; @@ -78,11 +85,17 @@ PyObject* pysqlite_row_subscript(pysqlite_Row* self, PyObject* idx) if (PyInt_Check(idx)) { _idx = PyInt_AsLong(idx); + if (_idx < 0) + _idx += PyTuple_GET_SIZE(self->data); item = PyTuple_GetItem(self->data, _idx); Py_XINCREF(item); return item; } else if (PyLong_Check(idx)) { - _idx = PyLong_AsLong(idx); + _idx = PyNumber_AsSsize_t(idx, PyExc_IndexError); + if (_idx == -1 && PyErr_Occurred()) + return NULL; + if (_idx < 0) + _idx += PyTuple_GET_SIZE(self->data); item = PyTuple_GetItem(self->data, _idx); Py_XINCREF(item); return item; @@ -199,6 +212,14 @@ PyMappingMethods pysqlite_row_as_mapping = { /* mp_ass_subscript */ (objobjargproc)0, }; +static PySequenceMethods pysqlite_row_as_sequence = { + /* sq_length */ (lenfunc)pysqlite_row_length, + /* sq_concat */ 0, + /* sq_repeat */ 0, + /* sq_item */ (ssizeargfunc)pysqlite_row_item, +}; + + static PyMethodDef pysqlite_row_methods[] = { {"keys", (PyCFunction)pysqlite_row_keys, METH_NOARGS, PyDoc_STR("Returns the keys of the row.")}, @@ -252,5 +273,6 @@ extern int pysqlite_row_setup_types(void) { pysqlite_RowType.tp_new = PyType_GenericNew; pysqlite_RowType.tp_as_mapping = &pysqlite_row_as_mapping; + pysqlite_RowType.tp_as_sequence = &pysqlite_row_as_sequence; return PyType_Ready(&pysqlite_RowType); } -- 2.50.1