https://github.com/python/cpython/commit/8e96dd6f25b53f0773d3f3f2a898eaa1501ee92e
commit: 8e96dd6f25b53f0773d3f3f2a898eaa1501ee92e
branch: main
author: Steve Stagg <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-21T07:58:49Z
summary:
gh-152817: Prevent deletion of sqlite3 `cursor.row_factory` attr, missed from:
gh-149738 (GH-152818)
files:
M Lib/test/test_sqlite3/test_factory.py
M
Misc/NEWS.d/next/Core_and_Builtins/2026-05-13-06-54-41.gh-issue-149738.4BLFoH.rst
M Modules/_sqlite/cursor.c
diff --git a/Lib/test/test_sqlite3/test_factory.py
b/Lib/test/test_sqlite3/test_factory.py
index b9b18fdee87226..2dd42921d31ddd 100644
--- a/Lib/test/test_sqlite3/test_factory.py
+++ b/Lib/test/test_sqlite3/test_factory.py
@@ -156,6 +156,14 @@ def test_delete_connection_text_factory(self):
with self.assertRaises(AttributeError):
del self.con.text_factory
+ def test_delete_cursor_row_factory(self):
+ # gh-149738: deleting row_factory should raise an exception
+ cur = self.con.cursor()
+ with self.assertRaises(AttributeError):
+ del cur.row_factory
+ # Executing a query here should succeed.
+ self.assertEqual(tuple(cur.execute("select 1").fetchone()), (1,))
+
def test_sqlite_row_index_unicode(self):
row = self.con.execute("select 1 as \xff").fetchone()
self.assertEqual(row["\xff"], 1)
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 e62b681d716650..e1935555b09174 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 @@
:mod:`sqlite3`: Disallow removing ``row_factory`` and ``text_factory``
attributes
-of a connection to prevent a crash on a query.
+of a connection or cursor to prevent a crash on a query.
diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c
index 3778ccd32fe4c4..96fb3dc9e42d3a 100644
--- a/Modules/_sqlite/cursor.c
+++ b/Modules/_sqlite/cursor.c
@@ -1405,13 +1405,33 @@ static struct PyMemberDef cursor_members[] =
{"description", _Py_T_OBJECT, offsetof(pysqlite_Cursor, description),
Py_READONLY},
{"lastrowid", _Py_T_OBJECT, offsetof(pysqlite_Cursor, lastrowid),
Py_READONLY},
{"rowcount", Py_T_LONG, offsetof(pysqlite_Cursor, rowcount), Py_READONLY},
- {"row_factory", _Py_T_OBJECT, offsetof(pysqlite_Cursor, row_factory), 0},
{"__weaklistoffset__", Py_T_PYSSIZET, offsetof(pysqlite_Cursor,
in_weakreflist), Py_READONLY},
{NULL}
};
+static PyObject *
+cursor_get_row_factory(PyObject *op, void *Py_UNUSED(closure))
+{
+ pysqlite_Cursor *self = _pysqlite_Cursor_CAST(op);
+ return Py_NewRef(self->row_factory);
+}
+
+static int
+cursor_set_row_factory(PyObject *op, PyObject *value, void *Py_UNUSED(closure))
+{
+ pysqlite_Cursor *self = _pysqlite_Cursor_CAST(op);
+ 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 struct PyGetSetDef cursor_getsets[] = {
_SQLITE3_CURSOR_ARRAYSIZE_GETSETDEF
+ {"row_factory", cursor_get_row_factory, cursor_set_row_factory},
{NULL},
};
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]