From 6f3ae87965e12fb0803f3f6330ef496b409c0588 Mon Sep 17 00:00:00 2001 From: raminfp Date: Sun, 15 Mar 2026 13:44:24 +0330 Subject: [PATCH 1/3] gh-145966: Fix _csv DIALECT_GETATTR macro silently masking non-AttributeError exceptions The DIALECT_GETATTR macro in dialect_new() unconditionally called PyErr_Clear() when PyObject_GetAttrString() failed, which suppressed all exceptions including MemoryError, KeyboardInterrupt, and RuntimeError. Now only AttributeError is cleared; other exceptions propagate via the existing error handling path. --- Lib/test/test_csv.py | 14 ++++++++++++++ ...026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst | 4 ++++ Modules/_csv.c | 9 +++++++-- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index df79840088abc3..8381d34c1e91bf 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -1281,6 +1281,20 @@ class mydialect(csv.Dialect): skipinitialspace=True) + def test_dialect_getattr_non_attribute_error_propagates(self): + # gh-145966: non-AttributeError exceptions raised by __getattr__ + # during dialect attribute lookup must propagate, not be silenced. + class BadDialect: + def __getattr__(self, name): + raise RuntimeError("boom") + + with self.assertRaises(RuntimeError): + csv.reader([], dialect=BadDialect()) + + with self.assertRaises(RuntimeError): + csv.writer(StringIO(), dialect=BadDialect()) + + class TestSniffer(unittest.TestCase): sample1 = """\ Harry's, Arlington Heights, IL, 2/1/03, Kimi Hayes diff --git a/Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst b/Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst new file mode 100644 index 00000000000000..e687af4653f84c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst @@ -0,0 +1,4 @@ +Fixed ``DIALECT_GETATTR`` macro in :mod:`csv` to only clear +:exc:`AttributeError` exceptions. Previously, all exceptions (including +:exc:`MemoryError` and :exc:`KeyboardInterrupt`) were silently suppressed +when looking up dialect attributes. diff --git a/Modules/_csv.c b/Modules/_csv.c index 1f41976e95fdb1..ddc9a8ad6e1867 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -501,8 +501,13 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) do { \ if (v == NULL) { \ v = PyObject_GetAttrString(dialect, n); \ - if (v == NULL) \ - PyErr_Clear(); \ + if (v == NULL) { \ + if (PyErr_ExceptionMatches( \ + PyExc_AttributeError)) \ + PyErr_Clear(); \ + else \ + goto err; \ + } \ } \ } while (0) DIALECT_GETATTR(delimiter, "delimiter"); From c9b4479f6d9d7a25f9c283449614d1b02f4d451f Mon Sep 17 00:00:00 2001 From: raminfp Date: Sun, 15 Mar 2026 14:14:23 +0330 Subject: [PATCH 2/3] gh-145966: Address review feedback: fix PEP-7 alignment, simplify NEWS, fix blank lines --- Lib/test/test_csv.py | 1 - ...3-15-00-00-00.gh-issue-145966.tCI0uD4I.rst | 6 ++--- Modules/_csv.c | 24 +++++++++---------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 8381d34c1e91bf..2e5b72742c3f41 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -1280,7 +1280,6 @@ class mydialect(csv.Dialect): self.assertRaises(ValueError, create_invalid, field_name, " ", skipinitialspace=True) - def test_dialect_getattr_non_attribute_error_propagates(self): # gh-145966: non-AttributeError exceptions raised by __getattr__ # during dialect attribute lookup must propagate, not be silenced. diff --git a/Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst b/Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst index e687af4653f84c..c0d4907ada073c 100644 --- a/Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst +++ b/Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst @@ -1,4 +1,2 @@ -Fixed ``DIALECT_GETATTR`` macro in :mod:`csv` to only clear -:exc:`AttributeError` exceptions. Previously, all exceptions (including -:exc:`MemoryError` and :exc:`KeyboardInterrupt`) were silently suppressed -when looking up dialect attributes. +Non-:exc:`AttributeError` exceptions raised during dialect attribute lookup +in :mod:`csv` are no longer silently suppressed. diff --git a/Modules/_csv.c b/Modules/_csv.c index ddc9a8ad6e1867..ede4b96116d41d 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -497,18 +497,18 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) Py_XINCREF(skipinitialspace); Py_XINCREF(strict); if (dialect != NULL) { -#define DIALECT_GETATTR(v, n) \ - do { \ - if (v == NULL) { \ - v = PyObject_GetAttrString(dialect, n); \ - if (v == NULL) { \ - if (PyErr_ExceptionMatches( \ - PyExc_AttributeError)) \ - PyErr_Clear(); \ - else \ - goto err; \ - } \ - } \ +#define DIALECT_GETATTR(v, n) \ + do { \ + if (v == NULL) { \ + v = PyObject_GetAttrString(dialect, n); \ + if (v == NULL) { \ + if (PyErr_ExceptionMatches( \ + PyExc_AttributeError)) \ + PyErr_Clear(); \ + else \ + goto err; \ + } \ + } \ } while (0) DIALECT_GETATTR(delimiter, "delimiter"); DIALECT_GETATTR(doublequote, "doublequote"); From fbd8b14d837ac4227b0e5fcbe548e735a0ce69df Mon Sep 17 00:00:00 2001 From: Ramin Farajpour Cami Date: Sun, 15 Mar 2026 14:21:15 +0330 Subject: [PATCH 3/3] Update Modules/_csv.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Modules/_csv.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/Modules/_csv.c b/Modules/_csv.c index ede4b96116d41d..20e247e4953c13 100644 --- a/Modules/_csv.c +++ b/Modules/_csv.c @@ -497,18 +497,19 @@ dialect_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) Py_XINCREF(skipinitialspace); Py_XINCREF(strict); if (dialect != NULL) { -#define DIALECT_GETATTR(v, n) \ - do { \ - if (v == NULL) { \ - v = PyObject_GetAttrString(dialect, n); \ - if (v == NULL) { \ - if (PyErr_ExceptionMatches( \ - PyExc_AttributeError)) \ - PyErr_Clear(); \ - else \ - goto err; \ - } \ - } \ +#define DIALECT_GETATTR(v, n) \ + do { \ + if (v == NULL) { \ + v = PyObject_GetAttrString(dialect, n); \ + if (v == NULL) { \ + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { \ + PyErr_Clear(); \ + } \ + else { \ + goto err; \ + } \ + } \ + } \ } while (0) DIALECT_GETATTR(delimiter, "delimiter"); DIALECT_GETATTR(doublequote, "doublequote");