https://github.com/python/cpython/commit/8288442bb61b644330780e1beb84a7c4e94fc93d commit: 8288442bb61b644330780e1beb84a7c4e94fc93d branch: 3.14 author: Serhiy Storchaka <[email protected]> committer: serhiy-storchaka <[email protected]> date: 2026-08-21T08:54:06Z summary:
[3.14] gh-152817: Prevent deletion of sqlite3 cursor.row_factory attr (GH-152818) (GH-156161) (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 e62891635e2fad..c64e109f9aa2d9 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 a4b5769040282a..4e6f46261b10bc 100644 --- a/Modules/_sqlite/cursor.c +++ b/Modules/_sqlite/cursor.c @@ -1348,13 +1348,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]
