From 47a981337a8f3898482a1c0c51ad2d91c7a6bc2f Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 28 May 2014 12:58:34 +0300 Subject: [PATCH] Issue #10203: sqlite3.Row now truly supports sequence protocol. In particulr it supports reverse() and negative indices. Original patch by Claudiu Popa. --- Lib/sqlite3/dbapi2.py | 2 ++ Lib/sqlite3/test/factory.py | 26 +++++++++++++++++++++++--- Misc/NEWS | 3 +++ Modules/_sqlite/row.c | 24 ++++++++++++++++++++++-- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Lib/sqlite3/dbapi2.py b/Lib/sqlite3/dbapi2.py index 9a0b76645e1..991682ce9ef 100644 --- a/Lib/sqlite3/dbapi2.py +++ b/Lib/sqlite3/dbapi2.py @@ -22,6 +22,7 @@ import datetime import time +import collections.abc from _sqlite3 import * @@ -50,6 +51,7 @@ version_info = tuple([int(x) for x in version.split(".")]) sqlite_version_info = tuple([int(x) for x in sqlite_version.split(".")]) Binary = memoryview +collections.abc.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 1013755b9cf..98dcae5d6c4 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.abc import Sequence class MyConnection(sqlite.Connection): def __init__(self, *args, **kwargs): @@ -96,9 +97,19 @@ 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[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") + + with self.assertRaises(IndexError): + row['c'] + with self.assertRaises(IndexError): + row[2] + with self.assertRaises(IndexError): + row[-3] + with self.assertRaises(IndexError): + row[2**1000] def CheckSqliteRowIter(self): """Checks if the row object is iterable""" @@ -142,6 +153,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 43a4a0c2911..57cf50fe9b4 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 #18807: If copying (no symlinks) specified for a venv, then the python interpreter aliases (python, python3) are now created by copying rather than symlinking. diff --git a/Modules/_sqlite/row.c b/Modules/_sqlite/row.c index 4a87a048fd2..d10bd599624 100644 --- a/Modules/_sqlite/row.c +++ b/Modules/_sqlite/row.c @@ -63,9 +63,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; Py_ssize_t nitems, i; char* compare_key; @@ -76,7 +83,11 @@ PyObject* pysqlite_row_subscript(pysqlite_Row* self, PyObject* idx) PyObject* item; 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; @@ -198,6 +209,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.")}, @@ -251,5 +270,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); }