Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v3.0.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -790,8 +790,8 @@ I/O
- Bug in :meth:`read_stata` where extreme value integers were incorrectly interpreted as missing for format versions 111 and prior (:issue:`58130`)
- Bug in :meth:`read_stata` where the missing code for double was not recognised for format versions 105 and prior (:issue:`58149`)
- Bug in :meth:`set_option` where setting the pandas option ``display.html.use_mathjax`` to ``False`` has no effect (:issue:`59884`)
- Bug in :meth:`to_csv` where ``quotechar``` is not escaped when ``escapechar`` is not None (:issue:`61407`)
- Bug in :meth:`to_excel` where :class:`MultiIndex` columns would be merged to a single row when ``merge_cells=False`` is passed (:issue:`60274`)

Copy link
Member

Choose a reason for hiding this comment

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

/home/runner/work/pandas/pandas/doc/source/whatsnew/v3.0.0.rst:809: WARNING: Bullet list ends without a blank line; unexpected unindent. [docutils]

Period
^^^^^^
- Fixed error message when passing invalid period alias to :meth:`PeriodIndex.to_timestamp` (:issue:`58974`)
Expand Down
4 changes: 2 additions & 2 deletions pandas/io/formats/csvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ def __init__(
self.index_label = self._initialize_index_label(index_label)
self.errors = errors
self.quoting = quoting or csvlib.QUOTE_MINIMAL
self.quotechar = self._initialize_quotechar(quotechar)
self.doublequote = doublequote
self.escapechar = escapechar
self.quotechar = self._initialize_quotechar(quotechar)
self.lineterminator = lineterminator or os.linesep
self.date_format = date_format
self.cols = self._initialize_columns(cols)
Expand Down Expand Up @@ -141,7 +141,7 @@ def _get_index_label_flat(self) -> Sequence[Hashable]:
return [""] if index_label is None else [index_label]

def _initialize_quotechar(self, quotechar: str | None) -> str | None:
if self.quoting != csvlib.QUOTE_NONE:
if self.quoting != csvlib.QUOTE_NONE or self.escapechar is not None:
# prevents crash in _csv
return quotechar
return None
Expand Down
19 changes: 19 additions & 0 deletions pandas/tests/frame/methods/test_to_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -1450,3 +1450,22 @@ def test_to_csv_warn_when_zip_tar_and_append_mode(self, tmp_path):
RuntimeWarning, match=msg, raise_on_extra_warnings=False
):
df.to_csv(tar_path, mode="a")

def test_to_csv_escape_quotechar(self):
# GH61514
df = DataFrame(
{
"col_a": ["a", "a2"],
"col_b": ['b"c', None],
"col_c": ['de,f"', '"c'],
}
)

result = df.to_csv(quotechar='"', escapechar="\\", quoting=csv.QUOTE_NONE)
expected_rows = [
",col_a,col_b,col_c",
'0,a,b\\"c,de\\,f\\"',
'1,a2,,\\"c',
]
expected = tm.convert_rows_list_to_csv_str(expected_rows)
assert result == expected
Loading