Skip to content

Preserve carriage returns in quoted CSV/TSV fields (#895) - #897

Open
agustin18 wants to merge 1 commit into
simonw:mainfrom
agustin18:fix/rows-from-file-carriage-return-895
Open

agustin18 wants to merge 1 commit into
simonw:mainfrom
agustin18:fix/rows-from-file-carriage-return-895

Conversation

@agustin18

@agustin18 agustin18 commented Oct 3, 2026 •

Copy link
Copy Markdown

Summary

Fixes #895 by ensuring carriage returns (\r\n or lone \r) embedded inside quoted CSV/TSV fields are preserved rather than silently rewritten to \n.

Root Cause

When opening a byte stream for CSV/TSV decoding in rows_from_file() (sqlite_utils/utils.py) and the CLI insert command (sqlite_utils/cli.py), io.TextIOWrapper was initialized without an explicit newline argument. By default, io.TextIOWrapper enables universal newline mode (newline=None), translating any \r\n or lone \r into \n as bytes are read.

Because this translation happens underneath csv.reader / csv.DictReader, carriage returns inside quoted fields are destroyed before the CSV parser ever sees them.

Per the Python standard library documentation for the csv module:

"If csvfile is a file object, it should be opened with newline=''. If newline='' is not specified, newlines embedded inside quoted fields will not be interpreted correctly..."

Changes

  1. sqlite_utils/utils.py:
    • In rows_from_file() under format == Format.CSV: passed newline="" to io.TextIOWrapper(fp, encoding=use_encoding, newline=""). (Note: Format.TSV delegates to rows_from_file(..., format=Format.CSV) so it is also fixed).
  2. sqlite_utils/cli.py:
    • In insert_upsert_implementation(): set newline="" when csv or tsv is true for both sniff-buffered and unbuffered io.TextIOWrapper instances.
  3. Tests:
    • In tests/test_rows_from_file.py: Added parameterized regression test test_rows_from_file_preserves_carriage_returns_in_quoted_fields verifying Format.CSV and Format.TSV with both CRLF (\r\n) and lone CR (\r) inside quoted fields.
    • In tests/test_cli_insert.py: Added parameterized test test_insert_csv_tsv_preserves_carriage_returns_in_quoted_fields validating that sqlite-utils insert --csv and --tsv preserve embedded carriage returns when inserting into SQLite tables.

Verification

  • Tested with pytest tests/test_rows_from_file.py tests/test_cli_insert.py: all 86 tests passed.
  • Full pytest test suite: 1,502 passed, 0 failures.
  • Formatting & linting: black --check, flake8, and mypy all clean with 0 errors.

📚 Documentation preview 📚: https://sqlite-utils--897.org.readthedocs.build/en/897/

When reading CSV or TSV files through rows_from_file() or the CLI insert command, io.TextIOWrapper defaulted to universal-newline mode (newline=None), causing carriage returns (\r\n or lone \r) embedded inside quoted fields to be silently translated to \n before csv.reader could process them.

Setting newline='' in TextIOWrapper preserves carriage returns in quoted fields according to the Python standard library csv module specification.

Fixes simonw#895
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CSV/TSV reading silently drops carriage returns inside quoted fields

1 participant