diff --git a/pandas/io/excel/_odswriter.py b/pandas/io/excel/_odswriter.py index efef86329314b..fa2779b01d681 100644 --- a/pandas/io/excel/_odswriter.py +++ b/pandas/io/excel/_odswriter.py @@ -29,6 +29,7 @@ def __init__( storage_options: StorageOptions = None, if_sheet_exists: str | None = None, engine_kwargs: dict[str, Any] | None = None, + **kwargs, ): from odf.opendocument import OpenDocumentSpreadsheet diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index bc067e216760c..03c46f139eeca 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -19,7 +19,10 @@ BaseExcelReader, ExcelWriter, ) -from pandas.io.excel._util import validate_freeze_panes +from pandas.io.excel._util import ( + combine_kwargs, + validate_freeze_panes, +) if TYPE_CHECKING: from openpyxl.descriptors.serialisable import Serialisable @@ -39,10 +42,13 @@ def __init__( storage_options: StorageOptions = None, if_sheet_exists: str | None = None, engine_kwargs: dict[str, Any] | None = None, + **kwargs, ): # Use the openpyxl module as the Excel writer. from openpyxl.workbook import Workbook + engine_kwargs = combine_kwargs(engine_kwargs, kwargs) + super().__init__( path, mode=mode, diff --git a/pandas/io/excel/_util.py b/pandas/io/excel/_util.py index 7d8028de23257..66a66fbbcd78a 100644 --- a/pandas/io/excel/_util.py +++ b/pandas/io/excel/_util.py @@ -1,6 +1,9 @@ from __future__ import annotations -from typing import MutableMapping +from typing import ( + Any, + MutableMapping, +) from pandas.compat._optional import import_optional_dependency @@ -246,3 +249,30 @@ def pop_header_name(row, index_col): header_name = None if header_name == "" else header_name return header_name, row[:i] + [""] + row[i + 1 :] + + +def combine_kwargs(engine_kwargs: dict[str, Any] | None, kwargs: dict) -> dict: + """ + Used to combine two sources of kwargs for the backend engine. + + Use of kwargs is deprecated, this function is solely for use in 1.3 and should + be removed in 1.4/2.0. Also _base.ExcelWriter.__new__ ensures either engine_kwargs + or kwargs must be None or empty respectively. + + Parameters + ---------- + engine_kwargs: dict + kwargs to be passed through to the engine. + kwargs: dict + kwargs to be psased through to the engine (deprecated) + + Returns + ------- + engine_kwargs combined with kwargs + """ + if engine_kwargs is None: + result = {} + else: + result = engine_kwargs.copy() + result.update(kwargs) + return result diff --git a/pandas/io/excel/_xlsxwriter.py b/pandas/io/excel/_xlsxwriter.py index 7500a33b1f097..06c73f2c6199e 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -6,7 +6,10 @@ from pandas._typing import StorageOptions from pandas.io.excel._base import ExcelWriter -from pandas.io.excel._util import validate_freeze_panes +from pandas.io.excel._util import ( + combine_kwargs, + validate_freeze_panes, +) class _XlsxStyler: @@ -175,11 +178,12 @@ def __init__( storage_options: StorageOptions = None, if_sheet_exists: str | None = None, engine_kwargs: dict[str, Any] | None = None, + **kwargs, ): # Use the xlsxwriter module as the Excel writer. from xlsxwriter import Workbook - engine_kwargs = engine_kwargs or {} + engine_kwargs = combine_kwargs(engine_kwargs, kwargs) if mode == "a": raise ValueError("Append mode is not supported with xlsxwriter!") diff --git a/pandas/io/excel/_xlwt.py b/pandas/io/excel/_xlwt.py index 8a7605b80f6b4..4dadf64b44515 100644 --- a/pandas/io/excel/_xlwt.py +++ b/pandas/io/excel/_xlwt.py @@ -9,7 +9,10 @@ from pandas._typing import StorageOptions from pandas.io.excel._base import ExcelWriter -from pandas.io.excel._util import validate_freeze_panes +from pandas.io.excel._util import ( + combine_kwargs, + validate_freeze_panes, +) if TYPE_CHECKING: from xlwt import XFStyle @@ -30,10 +33,13 @@ def __init__( storage_options: StorageOptions = None, if_sheet_exists: str | None = None, engine_kwargs: dict[str, Any] | None = None, + **kwargs, ): # Use the xlwt module as the Excel writer. import xlwt + engine_kwargs = combine_kwargs(engine_kwargs, kwargs) + if mode == "a": raise ValueError("Append mode is not supported with xlwt!") diff --git a/pandas/tests/io/excel/test_odswriter.py b/pandas/tests/io/excel/test_odswriter.py index b50c641ebf0c0..4bf6051fd36ef 100644 --- a/pandas/tests/io/excel/test_odswriter.py +++ b/pandas/tests/io/excel/test_odswriter.py @@ -1,3 +1,5 @@ +import re + import pytest import pandas._testing as tm @@ -15,3 +17,25 @@ def test_write_append_mode_raises(ext): with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=msg): ExcelWriter(f, engine="odf", mode="a") + + +@pytest.mark.parametrize("nan_inf_to_errors", [True, False]) +def test_kwargs(ext, nan_inf_to_errors): + # GH 42286 + # odswriter doesn't utilize kwargs, nothing to check except that it works + kwargs = {"options": {"nan_inf_to_errors": nan_inf_to_errors}} + with tm.ensure_clean(ext) as f: + msg = re.escape("Use of **kwargs is deprecated") + with tm.assert_produces_warning(FutureWarning, match=msg): + with ExcelWriter(f, engine="odf", **kwargs) as _: + pass + + +@pytest.mark.parametrize("nan_inf_to_errors", [True, False]) +def test_engine_kwargs(ext, nan_inf_to_errors): + # GH 42286 + # odswriter doesn't utilize engine_kwargs, nothing to check except that it works + engine_kwargs = {"options": {"nan_inf_to_errors": nan_inf_to_errors}} + with tm.ensure_clean(ext) as f: + with ExcelWriter(f, engine="odf", engine_kwargs=engine_kwargs) as _: + pass diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index 62f567457c3ab..cd773957c9043 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -85,6 +85,30 @@ def test_write_cells_merge_styled(ext): assert xcell_a2.font == openpyxl_sty_merged +@pytest.mark.parametrize("write_only", [True, False]) +def test_kwargs(ext, write_only): + # GH 42286 + # openpyxl doesn't utilize kwargs, only test that supplying a kwarg works + kwargs = {"write_only": write_only} + with tm.ensure_clean(ext) as f: + msg = re.escape("Use of **kwargs is deprecated") + with tm.assert_produces_warning(FutureWarning, match=msg): + with ExcelWriter(f, engine="openpyxl", **kwargs) as writer: + # ExcelWriter won't allow us to close without writing something + DataFrame().to_excel(writer) + + +@pytest.mark.parametrize("write_only", [True, False]) +def test_engine_kwargs(ext, write_only): + # GH 42286 + # openpyxl doesn't utilize kwargs, only test that supplying a engine_kwarg works + engine_kwargs = {"write_only": write_only} + with tm.ensure_clean(ext) as f: + with ExcelWriter(f, engine="openpyxl", engine_kwargs=engine_kwargs) as writer: + # ExcelWriter won't allow us to close without writing something + DataFrame().to_excel(writer) + + @pytest.mark.parametrize( "mode,expected", [("w", ["baz"]), ("a", ["foo", "bar", "baz"])] ) diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 77837bea3e48a..508e767a47004 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -1399,25 +1399,6 @@ def check_called(func): with tm.ensure_clean("something.xls") as filepath: check_called(lambda: df.to_excel(filepath, engine="dummy")) - @pytest.mark.parametrize( - "ext", - [ - pytest.param(".xlsx", marks=td.skip_if_no("xlsxwriter")), - pytest.param(".xlsx", marks=td.skip_if_no("openpyxl")), - pytest.param(".ods", marks=td.skip_if_no("odf")), - ], - ) - def test_kwargs_deprecated(self, ext): - # GH 40430 - msg = re.escape("Use of **kwargs is deprecated") - with tm.assert_produces_warning(FutureWarning, match=msg): - with tm.ensure_clean(ext) as path: - try: - with ExcelWriter(path, kwarg=1): - pass - except TypeError: - pass - @pytest.mark.parametrize( "ext", [ diff --git a/pandas/tests/io/excel/test_xlsxwriter.py b/pandas/tests/io/excel/test_xlsxwriter.py index 6de378f6a3d3e..79d2f55a9b8ff 100644 --- a/pandas/tests/io/excel/test_xlsxwriter.py +++ b/pandas/tests/io/excel/test_xlsxwriter.py @@ -1,3 +1,4 @@ +import re import warnings import pytest @@ -61,3 +62,23 @@ def test_write_append_mode_raises(ext): with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=msg): ExcelWriter(f, engine="xlsxwriter", mode="a") + + +@pytest.mark.parametrize("nan_inf_to_errors", [True, False]) +def test_kwargs(ext, nan_inf_to_errors): + # GH 42286 + kwargs = {"options": {"nan_inf_to_errors": nan_inf_to_errors}} + with tm.ensure_clean(ext) as f: + msg = re.escape("Use of **kwargs is deprecated") + with tm.assert_produces_warning(FutureWarning, match=msg): + with ExcelWriter(f, engine="xlsxwriter", **kwargs) as writer: + assert writer.book.nan_inf_to_errors == nan_inf_to_errors + + +@pytest.mark.parametrize("nan_inf_to_errors", [True, False]) +def test_engine_kwargs(ext, nan_inf_to_errors): + # GH 42286 + engine_kwargs = {"options": {"nan_inf_to_errors": nan_inf_to_errors}} + with tm.ensure_clean(ext) as f: + with ExcelWriter(f, engine="xlsxwriter", engine_kwargs=engine_kwargs) as writer: + assert writer.book.nan_inf_to_errors == nan_inf_to_errors diff --git a/pandas/tests/io/excel/test_xlwt.py b/pandas/tests/io/excel/test_xlwt.py index 7e1787d8c55d4..c58b9763f9618 100644 --- a/pandas/tests/io/excel/test_xlwt.py +++ b/pandas/tests/io/excel/test_xlwt.py @@ -1,3 +1,5 @@ +import re + import numpy as np import pytest @@ -97,3 +99,27 @@ def test_option_xls_writer_deprecated(ext): check_stacklevel=False, ): options.io.excel.xls.writer = "xlwt" + + +@pytest.mark.parametrize("write_only", [True, False]) +def test_kwargs(ext, write_only): + # GH 42286 + # xlwt doesn't utilize kwargs, only test that supplying a kwarg works + kwargs = {"write_only": write_only} + with tm.ensure_clean(ext) as f: + msg = re.escape("Use of **kwargs is deprecated") + with tm.assert_produces_warning(FutureWarning, match=msg): + with ExcelWriter(f, engine="openpyxl", **kwargs) as writer: + # xlwt won't allow us to close without writing something + DataFrame().to_excel(writer) + + +@pytest.mark.parametrize("write_only", [True, False]) +def test_engine_kwargs(ext, write_only): + # GH 42286 + # xlwt doesn't utilize kwargs, only test that supplying a engine_kwarg works + engine_kwargs = {"write_only": write_only} + with tm.ensure_clean(ext) as f: + with ExcelWriter(f, engine="openpyxl", engine_kwargs=engine_kwargs) as writer: + # xlwt won't allow us to close without writing something + DataFrame().to_excel(writer)