Skip to content

Commit df8b686

Browse files
authored
ENH: Add engine_kwargs to DataFrame.to_excel (#53220)
1 parent 5e725e9 commit df8b686

File tree

6 files changed

+65
-2
lines changed

6 files changed

+65
-2
lines changed

doc/source/user_guide/io.rst

+9
Original file line numberDiff line numberDiff line change
@@ -3785,6 +3785,15 @@ one can pass an :class:`~pandas.io.excel.ExcelWriter`.
37853785
37863786
.. _io.excel_writing_buffer:
37873787

3788+
When using the ``engine_kwargs`` parameter, pandas will pass these arguments to the
3789+
engine. For this, it is important to know which function pandas is using internally.
3790+
3791+
* For the engine openpyxl, pandas is using :func:`openpyxl.Workbook` to create a new sheet and :func:`openpyxl.load_workbook` to append data to an existing sheet. The openpyxl engine writes to (``.xlsx``) and (``.xlsm``) files.
3792+
3793+
* For the engine xlsxwriter, pandas is using :func:`xlsxwriter.Workbook` to write to (``.xlsx``) files.
3794+
3795+
* For the engine odf, pandas is using :func:`odf.opendocument.OpenDocumentSpreadsheet` to write to (``.ods``) files.
3796+
37883797
Writing Excel files to memory
37893798
+++++++++++++++++++++++++++++
37903799

doc/source/whatsnew/v2.1.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,10 @@ Other enhancements
9797
- Let :meth:`DataFrame.to_feather` accept a non-default :class:`Index` and non-string column names (:issue:`51787`)
9898
- Performance improvement in :func:`read_csv` (:issue:`52632`) with ``engine="c"``
9999
- :meth:`Categorical.from_codes` has gotten a ``validate`` parameter (:issue:`50975`)
100+
- Added ``engine_kwargs`` parameter to :meth:`DataFrame.to_excel` (:issue:`53220`)
100101
- Performance improvement in :func:`concat` with homogeneous ``np.float64`` or ``np.float32`` dtypes (:issue:`52685`)
101102
- Performance improvement in :meth:`DataFrame.filter` when ``items`` is given (:issue:`52941`)
103+
-
102104

103105
.. ---------------------------------------------------------------------------
104106
.. _whatsnew_210.notable_bug_fixes:

pandas/core/generic.py

+6
Original file line numberDiff line numberDiff line change
@@ -2154,6 +2154,7 @@ def to_excel(
21542154
inf_rep: str = "inf",
21552155
freeze_panes: tuple[int, int] | None = None,
21562156
storage_options: StorageOptions = None,
2157+
engine_kwargs: dict[str, Any] | None = None,
21572158
) -> None:
21582159
"""
21592160
Write {klass} to an Excel sheet.
@@ -2210,6 +2211,8 @@ def to_excel(
22102211
{storage_options}
22112212
22122213
.. versionadded:: {storage_options_versionadded}
2214+
engine_kwargs : dict, optional
2215+
Arbitrary keyword arguments passed to excel engine.
22132216
22142217
See Also
22152218
--------
@@ -2262,6 +2265,8 @@ def to_excel(
22622265
22632266
>>> df1.to_excel('output1.xlsx', engine='xlsxwriter') # doctest: +SKIP
22642267
"""
2268+
if engine_kwargs is None:
2269+
engine_kwargs = {}
22652270

22662271
df = self if isinstance(self, ABCDataFrame) else self.to_frame()
22672272

@@ -2286,6 +2291,7 @@ def to_excel(
22862291
freeze_panes=freeze_panes,
22872292
engine=engine,
22882293
storage_options=storage_options,
2294+
engine_kwargs=engine_kwargs,
22892295
)
22902296

22912297
@final

pandas/io/excel/_xlsxwriter.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,11 @@ def __init__(
212212
engine_kwargs=engine_kwargs,
213213
)
214214

215-
self._book = Workbook(self._handles.handle, **engine_kwargs)
215+
try:
216+
self._book = Workbook(self._handles.handle, **engine_kwargs)
217+
except TypeError:
218+
self._handles.handle.close()
219+
raise
216220

217221
@property
218222
def book(self):

pandas/io/formats/excel.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,7 @@ def write(
901901
freeze_panes: tuple[int, int] | None = None,
902902
engine: str | None = None,
903903
storage_options: StorageOptions = None,
904+
engine_kwargs: dict | None = None,
904905
) -> None:
905906
"""
906907
writer : path-like, file-like, or ExcelWriter object
@@ -922,6 +923,8 @@ def write(
922923
{storage_options}
923924
924925
.. versionadded:: 1.2.0
926+
engine_kwargs: dict, optional
927+
Arbitrary keyword arguments passed to excel engine.
925928
"""
926929
from pandas.io.excel import ExcelWriter
927930

@@ -932,14 +935,20 @@ def write(
932935
f"Max sheet size is: {self.max_rows}, {self.max_cols}"
933936
)
934937

938+
if engine_kwargs is None:
939+
engine_kwargs = {}
940+
935941
formatted_cells = self.get_formatted_cells()
936942
if isinstance(writer, ExcelWriter):
937943
need_save = False
938944
else:
939945
# error: Cannot instantiate abstract class 'ExcelWriter' with abstract
940946
# attributes 'engine', 'save', 'supported_extensions' and 'write_cells'
941947
writer = ExcelWriter( # type: ignore[abstract]
942-
writer, engine=engine, storage_options=storage_options
948+
writer,
949+
engine=engine,
950+
storage_options=storage_options,
951+
engine_kwargs=engine_kwargs,
943952
)
944953
need_save = True
945954

pandas/tests/io/excel/test_writers.py

+33
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import numpy as np
1212
import pytest
1313

14+
from pandas.compat._constants import PY310
1415
import pandas.util._test_decorators as td
1516

1617
import pandas as pd
@@ -1115,6 +1116,38 @@ def test_bytes_io(self, engine):
11151116
reread_df = pd.read_excel(bio, index_col=0)
11161117
tm.assert_frame_equal(df, reread_df)
11171118

1119+
def test_engine_kwargs(self, engine, path):
1120+
# GH#52368
1121+
df = DataFrame([{"A": 1, "B": 2}, {"A": 3, "B": 4}])
1122+
1123+
msgs = {
1124+
"odf": r"OpenDocumentSpreadsheet() got an unexpected keyword "
1125+
r"argument 'foo'",
1126+
"openpyxl": r"__init__() got an unexpected keyword argument 'foo'",
1127+
"xlsxwriter": r"__init__() got an unexpected keyword argument 'foo'",
1128+
}
1129+
1130+
if PY310:
1131+
msgs[
1132+
"openpyxl"
1133+
] = "Workbook.__init__() got an unexpected keyword argument 'foo'"
1134+
msgs[
1135+
"xlsxwriter"
1136+
] = "Workbook.__init__() got an unexpected keyword argument 'foo'"
1137+
1138+
# Handle change in error message for openpyxl (write and append mode)
1139+
if engine == "openpyxl" and not os.path.exists(path):
1140+
msgs[
1141+
"openpyxl"
1142+
] = r"load_workbook() got an unexpected keyword argument 'foo'"
1143+
1144+
with pytest.raises(TypeError, match=re.escape(msgs[engine])):
1145+
df.to_excel(
1146+
path,
1147+
engine=engine,
1148+
engine_kwargs={"foo": "bar"},
1149+
)
1150+
11181151
def test_write_lists_dict(self, path):
11191152
# see gh-8188.
11201153
df = DataFrame(

0 commit comments

Comments
 (0)