https://github.com/python/cpython/commit/70f7c6c0f2ddfd3b447946f1b926776b2a344703
commit: 70f7c6c0f2ddfd3b447946f1b926776b2a344703
branch: main
author: Zhou Wei <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-07-21T05:24:21Z
summary:

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

"$" 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.

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 b2aaf5fd9fa91e..6cae34c705777d 100644
--- a/Lib/csv.py
+++ b/Lib/csv.py
@@ -281,10 +281,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 7327c1bd5f5053..2ab529b51c207d 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -1415,6 +1415,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))
@@ -1494,6 +1497,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]

Reply via email to