From 388090ceef368cbb3241729e234fb34916a0fb50 Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Wed, 13 May 2026 06:55:29 +0330 Subject: [PATCH 01/14] Fix segfault bug in connection.c --- Modules/_sqlite/connection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index bd44ff31b87c67..ee9d8bf9ac4a68 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -549,7 +549,7 @@ pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) return NULL; } - if (cursor && self->row_factory != Py_None) { + if (cursor && self->row_factory && self->row_factory != Py_None) { Py_INCREF(self->row_factory); Py_XSETREF(((pysqlite_Cursor *)cursor)->row_factory, self->row_factory); } From 3da9d0e5c7c9674e217cb402856c41bc6129ec19 Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Wed, 13 May 2026 06:55:34 +0330 Subject: [PATCH 02/14] Add news entry --- .../2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst new file mode 100644 index 00000000000000..fccd1f5c4ae678 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst @@ -0,0 +1,2 @@ +Fix a segmentation fault in :mod:`sqlite3` when ``row_factory`` is deleted +and a query is executed afterward. From 6f4faa38896bd689fe2c30ce904bf6d78e9f6142 Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Wed, 13 May 2026 18:11:35 +0330 Subject: [PATCH 03/14] Add tests --- Lib/test/test_sqlite3/test_factory.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index 776659e3b16108..8dd3b951d53658 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -146,6 +146,14 @@ def test_sqlite_row_index(self): with self.assertRaises(IndexError): row[complex()] # index must be int or string + def test_delete_connection_row_factory(self): + # gh-149738: deleting row_factory should not segfault + del self.con.row_factory + with self.assertRaises(sqlite.OperationalError): + self.con.execute("test") + cur = self.con.cursor() + self.assertIsNone(cur.row_factory) + def test_sqlite_row_index_unicode(self): row = self.con.execute("select 1 as \xff").fetchone() self.assertEqual(row["\xff"], 1) From a0d272a3c16560a481c323e3a9fe6ce2005b6044 Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 21 May 2026 16:08:41 +0330 Subject: [PATCH 04/14] Update connection.c to raise an exception when trying to delete row_factory --- Modules/_sqlite/connection.c | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index ee9d8bf9ac4a68..1b0073b81dba82 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -523,6 +523,41 @@ _sqlite3.Connection.cursor as pysqlite_connection_cursor Return a cursor for the connection. [clinic start generated code]*/ +static PyObject * +connection_get_row_factory(pysqlite_Connection *self, void *closure) +{ + return Py_NewRef(self->row_factory); +} + +static int +connection_set_row_factory(pysqlite_Connection *self, PyObject *value, void *closure) +{ + if (value == NULL) { + PyErr_SetString(PyExc_AttributeError, + "cannot delete row_factory attribute"); + return -1; + } + Py_XSETREF(self->row_factory, Py_NewRef(value)); + return 0; +} + +static PyObject * +connection_get_text_factory(pysqlite_Connection *self, void *closure) +{ + return Py_NewRef(self->text_factory); +} + +static int +connection_set_text_factory(pysqlite_Connection *self, PyObject *value, void *closure) +{ + if (value == NULL) { + PyErr_SetString(PyExc_AttributeError, + "cannot delete text_factory attribute"); + return -1; + } + Py_XSETREF(self->text_factory, Py_NewRef(value)); + return 0; +} static PyObject * pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) @@ -2620,6 +2655,10 @@ static PyGetSetDef connection_getset[] = { {"in_transaction", pysqlite_connection_get_in_transaction, NULL}, {"autocommit", get_autocommit, set_autocommit}, {"__text_signature__", get_sig, NULL}, + {"row_factory", (getter)connection_get_row_factory, + (setter)connection_set_row_factory}, + {"text_factory", (getter)connection_get_text_factory, + (setter)connection_set_text_factory}, {NULL} }; @@ -2667,8 +2706,6 @@ static struct PyMemberDef connection_members[] = {"InternalError", _Py_T_OBJECT, offsetof(pysqlite_Connection, InternalError), Py_READONLY}, {"ProgrammingError", _Py_T_OBJECT, offsetof(pysqlite_Connection, ProgrammingError), Py_READONLY}, {"NotSupportedError", _Py_T_OBJECT, offsetof(pysqlite_Connection, NotSupportedError), Py_READONLY}, - {"row_factory", _Py_T_OBJECT, offsetof(pysqlite_Connection, row_factory)}, - {"text_factory", _Py_T_OBJECT, offsetof(pysqlite_Connection, text_factory)}, {NULL} }; From 5c64a63c821d98f24e240c7c6f8ed6f285ae3d56 Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 21 May 2026 16:27:31 +0330 Subject: [PATCH 05/14] Update test_delete_connection_row_factory to reflect new changes --- Lib/test/test_sqlite3/test_factory.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index 8dd3b951d53658..034be7cc8f26a9 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -147,12 +147,9 @@ def test_sqlite_row_index(self): row[complex()] # index must be int or string def test_delete_connection_row_factory(self): - # gh-149738: deleting row_factory should not segfault - del self.con.row_factory - with self.assertRaises(sqlite.OperationalError): - self.con.execute("test") - cur = self.con.cursor() - self.assertIsNone(cur.row_factory) + # gh-149738: deleting row_factory should raise an exception + with self.assertRaises(AttributeError): + del self.con.row_factory def test_sqlite_row_index_unicode(self): row = self.con.execute("select 1 as \xff").fetchone() From 4d75cf9739b751cdfc8e3e193fa9a278f42fa9fe Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 21 May 2026 16:37:15 +0330 Subject: [PATCH 06/14] Fix linting issues --- Modules/_sqlite/connection.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 1b0073b81dba82..4bce79e8f6cad9 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -533,7 +533,7 @@ static int connection_set_row_factory(pysqlite_Connection *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, + PyErr_SetString(PyExc_AttributeError, "cannot delete row_factory attribute"); return -1; } @@ -551,7 +551,7 @@ static int connection_set_text_factory(pysqlite_Connection *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, + PyErr_SetString(PyExc_AttributeError, "cannot delete text_factory attribute"); return -1; } @@ -2655,9 +2655,9 @@ static PyGetSetDef connection_getset[] = { {"in_transaction", pysqlite_connection_get_in_transaction, NULL}, {"autocommit", get_autocommit, set_autocommit}, {"__text_signature__", get_sig, NULL}, - {"row_factory", (getter)connection_get_row_factory, + {"row_factory", (getter)connection_get_row_factory, (setter)connection_set_row_factory}, - {"text_factory", (getter)connection_get_text_factory, + {"text_factory", (getter)connection_get_text_factory, (setter)connection_set_text_factory}, {NULL} }; From 20ab78948f04df804b1f642f28d179543cf73f06 Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 21 May 2026 16:49:52 +0330 Subject: [PATCH 07/14] Update connection.c --- Modules/_sqlite/connection.c | 74 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 4bce79e8f6cad9..888a5a1f2a94bf 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -523,6 +523,40 @@ _sqlite3.Connection.cursor as pysqlite_connection_cursor Return a cursor for the connection. [clinic start generated code]*/ + +static PyObject * +pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) +/*[clinic end generated code: output=562432a9e6af2aa1 input=4127345aa091b650]*/ +{ + PyObject* cursor; + + if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { + return NULL; + } + + if (factory == NULL) { + factory = (PyObject *)self->state->CursorType; + } + + cursor = PyObject_CallOneArg(factory, (PyObject *)self); + if (cursor == NULL) + return NULL; + if (!PyObject_TypeCheck(cursor, self->state->CursorType)) { + PyErr_Format(PyExc_TypeError, + "factory must return a cursor, not %.100s", + Py_TYPE(cursor)->tp_name); + Py_DECREF(cursor); + return NULL; + } + + if (cursor && self->row_factory != Py_None) { + Py_INCREF(self->row_factory); + Py_XSETREF(((pysqlite_Cursor *)cursor)->row_factory, self->row_factory); + } + + return cursor; +} + static PyObject * connection_get_row_factory(pysqlite_Connection *self, void *closure) { @@ -533,7 +567,7 @@ static int connection_set_row_factory(pysqlite_Connection *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, + PyErr_SetString(PyExc_AttributeError, "cannot delete row_factory attribute"); return -1; } @@ -551,7 +585,7 @@ static int connection_set_text_factory(pysqlite_Connection *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, + PyErr_SetString(PyExc_AttributeError, "cannot delete text_factory attribute"); return -1; } @@ -559,38 +593,6 @@ connection_set_text_factory(pysqlite_Connection *self, PyObject *value, void *cl return 0; } -static PyObject * -pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) -/*[clinic end generated code: output=562432a9e6af2aa1 input=4127345aa091b650]*/ -{ - PyObject* cursor; - - if (!pysqlite_check_thread(self) || !pysqlite_check_connection(self)) { - return NULL; - } - - if (factory == NULL) { - factory = (PyObject *)self->state->CursorType; - } - - cursor = PyObject_CallOneArg(factory, (PyObject *)self); - if (cursor == NULL) - return NULL; - if (!PyObject_TypeCheck(cursor, self->state->CursorType)) { - PyErr_Format(PyExc_TypeError, - "factory must return a cursor, not %.100s", - Py_TYPE(cursor)->tp_name); - Py_DECREF(cursor); - return NULL; - } - - if (cursor && self->row_factory && self->row_factory != Py_None) { - Py_INCREF(self->row_factory); - Py_XSETREF(((pysqlite_Cursor *)cursor)->row_factory, self->row_factory); - } - - return cursor; -} /*[clinic input] _sqlite3.Connection.blobopen as blobopen @@ -2655,9 +2657,9 @@ static PyGetSetDef connection_getset[] = { {"in_transaction", pysqlite_connection_get_in_transaction, NULL}, {"autocommit", get_autocommit, set_autocommit}, {"__text_signature__", get_sig, NULL}, - {"row_factory", (getter)connection_get_row_factory, + {"row_factory", (getter)connection_get_row_factory, (setter)connection_set_row_factory}, - {"text_factory", (getter)connection_get_text_factory, + {"text_factory", (getter)connection_get_text_factory, (setter)connection_set_text_factory}, {NULL} }; From b36aa693b272a5f585cd8f6d5c7d31318082443a Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 21 May 2026 16:50:07 +0330 Subject: [PATCH 08/14] Add a test for text_factory --- Lib/test/test_sqlite3/test_factory.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/test/test_sqlite3/test_factory.py b/Lib/test/test_sqlite3/test_factory.py index 034be7cc8f26a9..a9abeab3193688 100644 --- a/Lib/test/test_sqlite3/test_factory.py +++ b/Lib/test/test_sqlite3/test_factory.py @@ -151,6 +151,11 @@ def test_delete_connection_row_factory(self): with self.assertRaises(AttributeError): del self.con.row_factory + def test_delete_connection_text_factory(self): + # gh-149738: deleting text_factory should raise an exception + with self.assertRaises(AttributeError): + del self.con.text_factory + def test_sqlite_row_index_unicode(self): row = self.con.execute("select 1 as \xff").fetchone() self.assertEqual(row["\xff"], 1) From 0d6c3ae3d351db2b736b91a4c6ba34c90276330f Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Thu, 21 May 2026 16:56:00 +0330 Subject: [PATCH 09/14] Fix linting problems --- Modules/_sqlite/connection.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 888a5a1f2a94bf..6669dc28dd2468 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -567,7 +567,7 @@ static int connection_set_row_factory(pysqlite_Connection *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, + PyErr_SetString(PyExc_AttributeError, "cannot delete row_factory attribute"); return -1; } @@ -585,7 +585,7 @@ static int connection_set_text_factory(pysqlite_Connection *self, PyObject *value, void *closure) { if (value == NULL) { - PyErr_SetString(PyExc_AttributeError, + PyErr_SetString(PyExc_AttributeError, "cannot delete text_factory attribute"); return -1; } @@ -2657,9 +2657,9 @@ static PyGetSetDef connection_getset[] = { {"in_transaction", pysqlite_connection_get_in_transaction, NULL}, {"autocommit", get_autocommit, set_autocommit}, {"__text_signature__", get_sig, NULL}, - {"row_factory", (getter)connection_get_row_factory, + {"row_factory", (getter)connection_get_row_factory, (setter)connection_set_row_factory}, - {"text_factory", (getter)connection_get_text_factory, + {"text_factory", (getter)connection_get_text_factory, (setter)connection_set_text_factory}, {NULL} }; From 69201323ad4858ac7a6856b819f1de59f4521221 Mon Sep 17 00:00:00 2001 From: Sepehr Rasouli Date: Thu, 21 May 2026 17:49:31 +0330 Subject: [PATCH 10/14] Update news file --- .../2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst index fccd1f5c4ae678..e62b681d716650 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst @@ -1,2 +1,2 @@ -Fix a segmentation fault in :mod:`sqlite3` when ``row_factory`` is deleted -and a query is executed afterward. +:mod:`sqlite3`: Disallow removing ``row_factory`` and ``text_factory`` attributes +of a connection to prevent a crash on a query. From bd81a8e1228dc2716dfac668d700bfeb600ea1ac Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Sat, 30 May 2026 14:37:49 +0330 Subject: [PATCH 11/14] Update docs --- Doc/library/sqlite3.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 484260e63dd5f2..96b99cca568c35 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1417,6 +1417,10 @@ Connection objects See :ref:`sqlite3-howto-row-factory` for more details. + .. versionchanged:: 3.16 + Deleting the ``row_factory`` attribute is no longer allowed and raises + :exc:`AttributeError`. + .. attribute:: text_factory A :term:`callable` that accepts a :class:`bytes` parameter @@ -1426,6 +1430,10 @@ Connection objects See :ref:`sqlite3-howto-encoding` for more details. + .. versionchanged:: 3.16 + Deleting the ``text_factory`` attribute is no longer allowed and raises + :exc:`AttributeError`. + .. attribute:: total_changes Return the total number of database rows that have been modified, inserted, or @@ -1709,6 +1717,10 @@ Cursor objects See :ref:`sqlite3-howto-row-factory` for more details. + .. versionchanged:: 3.16 + Deleting the ``row_factory`` attribute is no longer allowed and raises + :exc:`AttributeError`. + .. The sqlite3.Row example used to be a how-to. It has now been incorporated into the Row reference. We keep the anchor here in order not to break From 1aede26c83ca6a34fdb725dfc724e2665bfffb27 Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Sat, 30 May 2026 14:37:57 +0330 Subject: [PATCH 12/14] Update connection.c --- Modules/_sqlite/connection.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Modules/_sqlite/connection.c b/Modules/_sqlite/connection.c index 23fe9636b82422..892740b05e55c9 100644 --- a/Modules/_sqlite/connection.c +++ b/Modules/_sqlite/connection.c @@ -558,14 +558,16 @@ pysqlite_connection_cursor_impl(pysqlite_Connection *self, PyObject *factory) } static PyObject * -connection_get_row_factory(pysqlite_Connection *self, void *closure) +connection_get_row_factory(PyObject *op, void *closure) { + pysqlite_Connection *self = (pysqlite_Connection *)op; return Py_NewRef(self->row_factory); } static int -connection_set_row_factory(pysqlite_Connection *self, PyObject *value, void *closure) +connection_set_row_factory(PyObject *op, PyObject *value, void *closure) { + pysqlite_Connection *self = (pysqlite_Connection *)op; if (value == NULL) { PyErr_SetString(PyExc_AttributeError, "cannot delete row_factory attribute"); @@ -576,14 +578,16 @@ connection_set_row_factory(pysqlite_Connection *self, PyObject *value, void *clo } static PyObject * -connection_get_text_factory(pysqlite_Connection *self, void *closure) +connection_get_text_factory(PyObject *op, void *closure) { + pysqlite_Connection *self = (pysqlite_Connection *)op; return Py_NewRef(self->text_factory); } static int -connection_set_text_factory(pysqlite_Connection *self, PyObject *value, void *closure) +connection_set_text_factory(PyObject *op, PyObject *value, void *closure) { + pysqlite_Connection *self = (pysqlite_Connection *)op; if (value == NULL) { PyErr_SetString(PyExc_AttributeError, "cannot delete text_factory attribute"); @@ -2657,10 +2661,10 @@ static PyGetSetDef connection_getset[] = { {"in_transaction", pysqlite_connection_get_in_transaction, NULL}, {"autocommit", get_autocommit, set_autocommit}, {"__text_signature__", get_sig, NULL}, - {"row_factory", (getter)connection_get_row_factory, - (setter)connection_set_row_factory}, - {"text_factory", (getter)connection_get_text_factory, - (setter)connection_set_text_factory}, + {"row_factory", connection_get_row_factory, + connection_set_row_factory}, + {"text_factory", connection_get_text_factory, + connection_set_text_factory}, {NULL} }; From 030aa69df91ab0f5555408a0ed6098f16146f8c9 Mon Sep 17 00:00:00 2001 From: Sepehr Rasouli Date: Mon, 1 Jun 2026 16:06:41 +0330 Subject: [PATCH 13/14] Change 3.16 to next --- Doc/library/sqlite3.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 96b99cca568c35..00b9746c1582c9 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1417,7 +1417,7 @@ Connection objects See :ref:`sqlite3-howto-row-factory` for more details. - .. versionchanged:: 3.16 + .. versionchanged:: next Deleting the ``row_factory`` attribute is no longer allowed and raises :exc:`AttributeError`. @@ -1430,7 +1430,7 @@ Connection objects See :ref:`sqlite3-howto-encoding` for more details. - .. versionchanged:: 3.16 + .. versionchanged:: next Deleting the ``text_factory`` attribute is no longer allowed and raises :exc:`AttributeError`. @@ -1717,7 +1717,7 @@ Cursor objects See :ref:`sqlite3-howto-row-factory` for more details. - .. versionchanged:: 3.16 + .. versionchanged:: next Deleting the ``row_factory`` attribute is no longer allowed and raises :exc:`AttributeError`. From 5489c45770b270024135015552e098ec7c8ffd64 Mon Sep 17 00:00:00 2001 From: sepehrrasooli Date: Mon, 1 Jun 2026 16:40:15 +0330 Subject: [PATCH 14/14] Update docs --- Doc/library/sqlite3.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Doc/library/sqlite3.rst b/Doc/library/sqlite3.rst index 00b9746c1582c9..3a75d44f3f7d21 100644 --- a/Doc/library/sqlite3.rst +++ b/Doc/library/sqlite3.rst @@ -1418,8 +1418,7 @@ Connection objects See :ref:`sqlite3-howto-row-factory` for more details. .. versionchanged:: next - Deleting the ``row_factory`` attribute is no longer allowed and raises - :exc:`AttributeError`. + Deleting the ``row_factory`` attribute is no longer allowed. .. attribute:: text_factory @@ -1431,8 +1430,7 @@ Connection objects See :ref:`sqlite3-howto-encoding` for more details. .. versionchanged:: next - Deleting the ``text_factory`` attribute is no longer allowed and raises - :exc:`AttributeError`. + Deleting the ``text_factory`` attribute is no longer allowed. .. attribute:: total_changes @@ -1718,8 +1716,7 @@ Cursor objects See :ref:`sqlite3-howto-row-factory` for more details. .. versionchanged:: next - Deleting the ``row_factory`` attribute is no longer allowed and raises - :exc:`AttributeError`. + Deleting the ``row_factory`` attribute is no longer allowed. .. The sqlite3.Row example used to be a how-to. It has now been incorporated