Skip to content

Commit c0129a0

Browse files
miss-islingtonlilabocserhiy-storchakaclaude
authored
[3.15] gh-103925: Fix csv.Sniffer for a quoted field ending a CRLF line (GH-103926) (GH-154322)
"$" 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 70f7c6c) Co-authored-by: Zhou Wei <lilaboc.cn@gmail.com> Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8ba1b04 commit c0129a0

3 files changed

Lines changed: 13 additions & 4 deletions

File tree

Lib/csv.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,10 @@ def _guess_quote_and_delimiter(self, data, delimiters):
281281
import re
282282

283283
matches = []
284-
for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?P=delim)', # ,".*?",
285-
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)', # ".*?",
286-
r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?:$|\n)', # ,".*?"
287-
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?:$|\n)'): # ".*?" (no delim, no space)
284+
for restr in (r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?P=delim)', # ,".*?",
285+
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?P<delim>[^\w\n"\'])(?P<space> ?)', # ".*?",
286+
r'(?P<delim>[^\w\n"\'])(?P<space> ?)(?P<quote>["\']).*?(?P=quote)(?:$|\r|\n)', # ,".*?"
287+
r'(?:^|\n)(?P<quote>["\']).*?(?P=quote)(?:$|\r|\n)'): # ".*?" (no delim, no space)
288288
regexp = re.compile(restr, re.DOTALL | re.MULTILINE)
289289
matches = regexp.findall(data)
290290
if matches:

Lib/test/test_csv.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,9 @@ class TestSniffer(unittest.TestCase):
14151415
sample18.append("v,twenty_one") # 'u' was not skipped
14161416
sample18 = '\n'.join(sample18)
14171417

1418+
sample19 = ('time,title\r\n'
1419+
'2020-10-01,"Pocket - Save news, videos, stories and more"\r\n')
1420+
14181421
def test_issue43625(self):
14191422
sniffer = csv.Sniffer()
14201423
self.assertTrue(sniffer.has_header(self.sample12))
@@ -1494,6 +1497,9 @@ def test_delimiters(self):
14941497
sniffer.sniff, self.sample15)
14951498
self.assertRaisesRegex(csv.Error, "Could not determine delimiter",
14961499
sniffer.sniff, self.sample16)
1500+
dialect = sniffer.sniff(self.sample19)
1501+
self.assertEqual(dialect.delimiter, ',')
1502+
self.assertEqual(dialect.quotechar, '"')
14971503

14981504
def test_doublequote(self):
14991505
sniffer = csv.Sniffer()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :meth:`csv.Sniffer.sniff` for a sample with ``\r\n`` line endings in
2+
which a quoted field ends a line: a letter could be detected as the
3+
delimiter.

0 commit comments

Comments
 (0)