gh-145966: Fix _csv DIALECT_GETATTR macro silently masking non-AttributeError exceptions (GH-145974)
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.
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index df79840..2e5b727 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -1280,6 +1280,19 @@ 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.
+ 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 = """\
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 0000000..c0d4907
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-03-15-00-00-00.gh-issue-145966.tCI0uD4I.rst
@@ -0,0 +1,2 @@
+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 1f41976..c48f44c 100644
--- a/Modules/_csv.c
+++ b/Modules/_csv.c
@@ -497,13 +497,13 @@ 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) \
- PyErr_Clear(); \
- } \
+#define DIALECT_GETATTR(v, n) \
+ do { \
+ if (v == NULL) { \
+ if (PyObject_GetOptionalAttrString(dialect, n, &v) < 0) { \
+ goto err; \
+ } \
+ } \
} while (0)
DIALECT_GETATTR(delimiter, "delimiter");
DIALECT_GETATTR(doublequote, "doublequote");