diff --git a/Lib/csv.py b/Lib/csv.py index b2aaf5fd9fa91e6..3caeaed33191962 100644 --- a/Lib/csv.py +++ b/Lib/csv.py @@ -264,6 +264,13 @@ class dialect(Dialect): dialect.quotechar = quotechar or '"' dialect.skipinitialspace = skipinitialspace + # A guessed dialect can be inconsistent (delimiter == quotechar); + # surface it as csv.Error, not a raw _csv ValueError. + try: + reader(StringIO(), dialect) + except ValueError as e: + raise Error(str(e)) from None + return dialect diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 7327c1bd5f50530..84ba569e870e736 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -1463,6 +1463,14 @@ def test_sniff(self): self.assertEqual(dialect.quotechar, "'") self.assertIs(dialect.skipinitialspace, False) + def test_sniff_delimiter_equals_quotechar(self): + # An inconsistent guessed dialect must surface as csv.Error. + sniffer = csv.Sniffer() + for sample in ('"', '""', "''"): + with self.subTest(sample=sample): + self.assertRaises(csv.Error, sniffer.sniff, sample) + self.assertRaises(csv.Error, sniffer.has_header, sample) + def test_delimiters(self): sniffer = csv.Sniffer() dialect = sniffer.sniff(self.sample3) diff --git a/Misc/NEWS.d/next/Library/2026-07-09-16-41-20.gh-issue-153634.27Fs4T.rst b/Misc/NEWS.d/next/Library/2026-07-09-16-41-20.gh-issue-153634.27Fs4T.rst new file mode 100644 index 000000000000000..f70943b18dd73ad --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-09-16-41-20.gh-issue-153634.27Fs4T.rst @@ -0,0 +1,4 @@ +:meth:`csv.Sniffer.sniff` now raises :exc:`csv.Error` for an inconsistent +guessed dialect (one whose delimiter equals its quote character) instead of +returning it. :meth:`csv.Sniffer.has_header` now raises :exc:`csv.Error` +instead of leaking a :exc:`ValueError`.