Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #895 by ensuring carriage returns (
\r\nor 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 CLIinsertcommand (sqlite_utils/cli.py),io.TextIOWrapperwas initialized without an explicitnewlineargument. By default,io.TextIOWrapperenables universal newline mode (newline=None), translating any\r\nor lone\rinto\nas 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
csvmodule:Changes
sqlite_utils/utils.py:rows_from_file()underformat == Format.CSV: passednewline=""toio.TextIOWrapper(fp, encoding=use_encoding, newline=""). (Note:Format.TSVdelegates torows_from_file(..., format=Format.CSV)so it is also fixed).sqlite_utils/cli.py:insert_upsert_implementation(): setnewline=""whencsv or tsvis true for both sniff-buffered and unbufferedio.TextIOWrapperinstances.tests/test_rows_from_file.py: Added parameterized regression testtest_rows_from_file_preserves_carriage_returns_in_quoted_fieldsverifyingFormat.CSVandFormat.TSVwith both CRLF (\r\n) and lone CR (\r) inside quoted fields.tests/test_cli_insert.py: Added parameterized testtest_insert_csv_tsv_preserves_carriage_returns_in_quoted_fieldsvalidating thatsqlite-utils insert --csvand--tsvpreserve embedded carriage returns when inserting into SQLite tables.Verification
pytest tests/test_rows_from_file.py tests/test_cli_insert.py: all 86 tests passed.black --check,flake8, andmypyall clean with 0 errors.📚 Documentation preview 📚: https://sqlite-utils--897.org.readthedocs.build/en/897/