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
@agustin18
agustin18 force-pushed the fix/rows-from-file-carriage-return-895 branch from c270922 to 6a7f166 Compare October 25, 2026 21:08

@Muguett-DBY Muguett-DBY left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix also works through format auto-detection and insert --sniff in my checks. I exercised CSV and TSV with both embedded CRLF and lone CR: the base rewrites the cell content in all 16 cases, while this head preserves it. The --text control still normalizes newlines as before.

Checked the exact rows_from_file and CLI implementation functions from 6a7f166 in an existing sqlite-utils 4.2.1 environment, using stdin imports and real SQLite files. Codex-assisted; this wasn't a full branch installation or pytest run.

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

2 participants