[3.14] gh-103925: Fix csv.Sniffer for a quoted field ending a CRLF line (GH-103926) (GH-154323)

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/42459c98553247824f662d63249ab4efe7d1484b
commit: 42459c98553247824f662d63249ab4efe7d1484b
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-07-21T05:51:12Z
summary:

[3.14] gh-103925: Fix csv.Sniffer for a quoted field ending a CRLF line (GH-103926) (GH-154323)

"$" does not match before "\r" even in the MULTILINE mode, so such a
field was not found and the delimiter was guessed from character
frequencies instead, which could give a letter.
(cherry picked from commit 70f7c6c0f2ddfd3b447946f1b926776b2a344703)

Co-authored-by: Zhou Wei <[email protected]>
Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2023-04-27-14-50-03.gh-issue-103925.khC-El.rst
M Lib/csv.py
M Lib/test/test_csv.py

diff --git a/Lib/csv.py b/Lib/csv.py
index 0a627ba7a512fa..6efa889ed84677 100644
--- a/Lib/csv.py
+++ b/Lib/csv.py
@@ -283,10 +283,10 @@ def _guess_quote_and_delimiter(self, data, delimiters):
         import re
 
         matches = []
-        for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?P=delim)', # ,".*?",
-                      r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)',   #  ".*?",
-                      r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?:$|\n)',   # ,".*?"
-                      r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?:$|\n)'):                            #  ".*?" (no delim, no space)
+        for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?P=delim)',   # ,".*?",
+                      r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)',     #  ".*?",
+                      r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?:$|\r|\n)',  # ,".*?"
+                      r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?:$|\r|\n)'):                           #  ".*?" (no delim, no space)
             regexp = re.compile(restr, re.DOTALL | re.MULTILINE)
             matches = regexp.findall(data)
             if matches:
diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index a0a1e91777d893..0f5235d5b2beec 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -1402,6 +1402,9 @@ class TestSniffer(unittest.TestCase):
     sample18.append("v,twenty_one")  # 'u' was not skipped
     sample18 = '\n'.join(sample18)
 
+    sample19 = ('time,title\r\n'
+                '2020-10-01,"Pocket - Save news, videos, stories and more"\r\n')
+
     def test_issue43625(self):
         sniffer = csv.Sniffer()
         self.assertTrue(sniffer.has_header(self.sample12))
@@ -1481,6 +1484,9 @@ def test_delimiters(self):
                                sniffer.sniff, self.sample15)
         self.assertRaisesRegex(csv.Error, "Could not determine delimiter",
                                sniffer.sniff, self.sample16)
+        dialect = sniffer.sniff(self.sample19)
+        self.assertEqual(dialect.delimiter, ',')
+        self.assertEqual(dialect.quotechar, '"')
 
     def test_doublequote(self):
         sniffer = csv.Sniffer()
diff --git a/Misc/NEWS.d/next/Library/2023-04-27-14-50-03.gh-issue-103925.khC-El.rst b/Misc/NEWS.d/next/Library/2023-04-27-14-50-03.gh-issue-103925.khC-El.rst
new file mode 100644
index 00000000000000..858ce6e5bd98e7
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-04-27-14-50-03.gh-issue-103925.khC-El.rst
@@ -0,0 +1,3 @@
+Fix :meth:`csv.Sniffer.sniff` for a sample with ``\r\n`` line endings in
+which a quoted field ends a line: a letter could be detected as the
+delimiter.

_______________________________________________
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.