https://github.com/python/cpython/commit/4f3afba4fe1b0f9cd09c892e02484385fe71e0f8
commit: 4f3afba4fe1b0f9cd09c892e02484385fe71e0f8
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-21T08:50:42Z
summary:

[3.13] gh-152817: Prevent deletion of sqlite3 cursor.row_factory attr 
(GH-152818) (GH-156162)

(cherry picked from commit 8e96dd6f25b53f0773d3f3f2a898eaa1501ee92e)

Co-authored-by: Steve Stagg <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-08-21-11-00-00.gh-issue-152817.Rt6mNc.rst
M Lib/test/test_sqlite3/test_factory.py
M Modules/_sqlite/cursor.c

diff --git a/Lib/test/test_sqlite3/test_factory.py 
b/Lib/test/test_sqlite3/test_factory.py
index d28754ff14729c..c19822bd10780f 100644
--- a/Lib/test/test_sqlite3/test_factory.py
+++ b/Lib/test/test_sqlite3/test_factory.py
@@ -165,6 +165,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/Library/2026-08-21-11-00-00.gh-issue-152817.Rt6mNc.rst 
b/Misc/NEWS.d/next/Library/2026-08-21-11-00-00.gh-issue-152817.Rt6mNc.rst
new file mode 100644
index 00000000000000..1cc45cc6846713
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-21-11-00-00.gh-issue-152817.Rt6mNc.rst
@@ -0,0 +1,2 @@
+:mod:`sqlite3`: Disallow removing the ``row_factory`` attribute of a cursor
+to prevent a crash on a query.
diff --git a/Modules/_sqlite/cursor.c b/Modules/_sqlite/cursor.c
index cc014c37323bbf..d8b7fc939a1d7a 100644
--- a/Modules/_sqlite/cursor.c
+++ b/Modules/_sqlite/cursor.c
@@ -1357,13 +1357,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 *)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 *)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]

Reply via email to