From 2c2f86356c571556577a29d020505a8c991ec58c Mon Sep 17 00:00:00 2001 From: Richard Shadrach Date: Sun, 14 Mar 2021 09:54:31 -0400 Subject: [PATCH 1/5] DEPR: **kwargs in ExcelWriter --- doc/source/whatsnew/v1.3.0.rst | 1 + pandas/io/excel/_base.py | 32 +++++++++++++++++++++++++-- pandas/io/excel/_odswriter.py | 11 +++++---- pandas/io/excel/_openpyxl.py | 9 ++++++-- pandas/io/excel/_xlsxwriter.py | 4 ++-- pandas/io/excel/_xlwt.py | 11 +++++---- pandas/tests/io/excel/test_writers.py | 19 ++++++++++++++++ 7 files changed, 73 insertions(+), 14 deletions(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 56a5412d4ecfc..379740eb9d22e 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -369,6 +369,7 @@ Deprecations - Deprecated casting ``datetime.date`` objects to ``datetime64`` when used as ``fill_value`` in :meth:`DataFrame.unstack`, :meth:`DataFrame.shift`, :meth:`Series.shift`, and :meth:`DataFrame.reindex`, pass ``pd.Timestamp(dateobj)`` instead (:issue:`39767`) - Deprecated :meth:`.Styler.set_na_rep` and :meth:`.Styler.set_precision` in favour of :meth:`.Styler.format` with ``na_rep`` and ``precision`` as existing and new input arguments respectively (:issue:`40134`) - Deprecated allowing partial failure in :meth:`Series.transform` and :meth:`DataFrame.transform` when ``func`` is list-like or dict-like; will raise if any function fails on a column in a future version (:issue:`40211`) +- Deprecated the use of ``**kwargs`` in :class:`.ExcelWriter`; use the keyword argument ``engine_kwargs`` instead (:issue:`40430`) .. --------------------------------------------------------------------------- diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 2bf70a18e810f..f17fa4d06f9b9 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -667,6 +667,14 @@ class ExcelWriter(metaclass=abc.ABCMeta): be parsed by ``fsspec``, e.g., starting "s3://", "gcs://". .. versionadded:: 1.2.0 + engine_kwargs : dict, optional + Keyword arguments to be passed into the engine. + **kwargs : dict, optional + Keyword arguments to be passed into the engine. + + .. deprecated:: 1.3.0 + + Use engine_kwargs instead. Attributes ---------- @@ -745,7 +753,26 @@ class ExcelWriter(metaclass=abc.ABCMeta): # You also need to register the class with ``register_writer()``. # Technically, ExcelWriter implementations don't need to subclass # ExcelWriter. - def __new__(cls, path, engine=None, **kwargs): + def __new__( + cls, + path: Union[FilePathOrBuffer, ExcelWriter], + engine=None, + date_format=None, + datetime_format=None, + mode: str = "w", + storage_options: StorageOptions = None, + engine_kwargs: Optional[Dict] = None, + **kwargs, + ): + if kwargs: + if engine_kwargs is not None: + raise ValueError("Cannot use both engine_kwargs and **kwargs") + warnings.warn( + "Use of **kwargs is deprecated, use engine_kwargs instead.", + FutureWarning, + stacklevel=2, + ) + # only switch class if generic(ExcelWriter) if cls is ExcelWriter: @@ -835,7 +862,8 @@ def __init__( datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, - **engine_kwargs, + engine_kwargs: Optional[Dict] = None, + **kwargs, ): # validate that this engine can handle the extension if isinstance(path, str): diff --git a/pandas/io/excel/_odswriter.py b/pandas/io/excel/_odswriter.py index d00e600b4e5d4..7f64c0544b8dc 100644 --- a/pandas/io/excel/_odswriter.py +++ b/pandas/io/excel/_odswriter.py @@ -26,19 +26,22 @@ def __init__( self, path: str, engine: Optional[str] = None, + date_format=None, + datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, - **engine_kwargs, + engine_kwargs: Dict = None, ): from odf.opendocument import OpenDocumentSpreadsheet - engine_kwargs["engine"] = engine - if mode == "a": raise ValueError("Append mode is not supported with odf!") super().__init__( - path, mode=mode, storage_options=storage_options, **engine_kwargs + path, + mode=mode, + storage_options=storage_options, + engine_kwargs=engine_kwargs, ) self.book = OpenDocumentSpreadsheet() diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index be2c9b919a5c3..faca87488e201 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -35,15 +35,20 @@ def __init__( self, path, engine=None, + date_format=None, + datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, - **engine_kwargs, + engine_kwargs: Dict = None, ): # Use the openpyxl module as the Excel writer. from openpyxl.workbook import Workbook super().__init__( - path, mode=mode, storage_options=storage_options, **engine_kwargs + path, + mode=mode, + storage_options=storage_options, + engine_kwargs=engine_kwargs, ) # ExcelWriter replaced "a" by "r+" to allow us to first read the excel file from diff --git a/pandas/io/excel/_xlsxwriter.py b/pandas/io/excel/_xlsxwriter.py index 849572cff813a..6426582d80a41 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -175,7 +175,7 @@ def __init__( datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, - **engine_kwargs, + engine_kwargs: Dict = None, ): # Use the xlsxwriter module as the Excel writer. from xlsxwriter import Workbook @@ -190,7 +190,7 @@ def __init__( datetime_format=datetime_format, mode=mode, storage_options=storage_options, - **engine_kwargs, + engine_kwargs=engine_kwargs, ) self.book = Workbook(self.handles.handle, **engine_kwargs) diff --git a/pandas/io/excel/_xlwt.py b/pandas/io/excel/_xlwt.py index a8386242faf72..7fa82da7d6fd6 100644 --- a/pandas/io/excel/_xlwt.py +++ b/pandas/io/excel/_xlwt.py @@ -21,21 +21,24 @@ def __init__( self, path, engine=None, + date_format=None, + datetime_format=None, encoding=None, mode: str = "w", storage_options: StorageOptions = None, - **engine_kwargs, + engine_kwargs: Dict = None, ): # Use the xlwt module as the Excel writer. import xlwt - engine_kwargs["engine"] = engine - if mode == "a": raise ValueError("Append mode is not supported with xlwt!") super().__init__( - path, mode=mode, storage_options=storage_options, **engine_kwargs + path, + mode=mode, + storage_options=storage_options, + engine_kwargs=engine_kwargs, ) if encoding is None: diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 3a1c93bdfee29..63488c55417d2 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -6,6 +6,7 @@ from functools import partial from io import BytesIO import os +import re import numpy as np import pytest @@ -1382,6 +1383,24 @@ 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): + 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 + @td.skip_if_no("xlrd") @td.skip_if_no("openpyxl") From a95c31f73a125095e7235b37da736c2a595f845f Mon Sep 17 00:00:00 2001 From: Richard Shadrach Date: Sun, 14 Mar 2021 10:17:24 -0400 Subject: [PATCH 2/5] GH # in test --- pandas/tests/io/excel/test_writers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 63488c55417d2..34b56d9bf4bb9 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -1392,6 +1392,7 @@ def check_called(func): ], ) 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: From e96145254e6fe0ee1e424b25888139ef389a2bca Mon Sep 17 00:00:00 2001 From: Richard Shadrach Date: Sun, 14 Mar 2021 11:43:43 -0400 Subject: [PATCH 3/5] fixup --- pandas/io/excel/_xlsxwriter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/io/excel/_xlsxwriter.py b/pandas/io/excel/_xlsxwriter.py index 6426582d80a41..c99c4e441c185 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -180,6 +180,8 @@ def __init__( # Use the xlsxwriter module as the Excel writer. from xlsxwriter import Workbook + engine_kwargs = engine_kwargs or {} + if mode == "a": raise ValueError("Append mode is not supported with xlsxwriter!") From 21f816821a6f3dc71c28acc8adb2fd48985a91b6 Mon Sep 17 00:00:00 2001 From: Richard Shadrach Date: Sun, 14 Mar 2021 14:30:27 -0400 Subject: [PATCH 4/5] Fix type-hint --- pandas/io/excel/_odswriter.py | 2 +- pandas/io/excel/_openpyxl.py | 3 ++- pandas/io/excel/_xlsxwriter.py | 4 +++- pandas/io/excel/_xlwt.py | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pandas/io/excel/_odswriter.py b/pandas/io/excel/_odswriter.py index 7f64c0544b8dc..bfd1bcf466a7a 100644 --- a/pandas/io/excel/_odswriter.py +++ b/pandas/io/excel/_odswriter.py @@ -30,7 +30,7 @@ def __init__( datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, - engine_kwargs: Dict = None, + engine_kwargs: Optional[Dict[str, Any]] = None, ): from odf.opendocument import OpenDocumentSpreadsheet diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index faca87488e201..72950db72e067 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -3,6 +3,7 @@ import mmap from typing import ( TYPE_CHECKING, + Any, Dict, List, Optional, @@ -39,7 +40,7 @@ def __init__( datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, - engine_kwargs: Dict = None, + engine_kwargs: Optional[Dict[str, Any]] = None, ): # Use the openpyxl module as the Excel writer. from openpyxl.workbook import Workbook diff --git a/pandas/io/excel/_xlsxwriter.py b/pandas/io/excel/_xlsxwriter.py index c99c4e441c185..6e1b064534707 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -1,6 +1,8 @@ from typing import ( + Any, Dict, List, + Optional, Tuple, ) @@ -175,7 +177,7 @@ def __init__( datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, - engine_kwargs: Dict = None, + engine_kwargs: Optional[Dict[str, Any]] = None, ): # Use the xlsxwriter module as the Excel writer. from xlsxwriter import Workbook diff --git a/pandas/io/excel/_xlwt.py b/pandas/io/excel/_xlwt.py index 7fa82da7d6fd6..776baf66536b1 100644 --- a/pandas/io/excel/_xlwt.py +++ b/pandas/io/excel/_xlwt.py @@ -1,6 +1,8 @@ from typing import ( TYPE_CHECKING, + Any, Dict, + Optional, ) import pandas._libs.json as json @@ -26,7 +28,7 @@ def __init__( encoding=None, mode: str = "w", storage_options: StorageOptions = None, - engine_kwargs: Dict = None, + engine_kwargs: Optional[Dict[str, Any]] = None, ): # Use the xlwt module as the Excel writer. import xlwt From 3dfea8f30c591e8531b5305b1a1b2b978f4433f4 Mon Sep 17 00:00:00 2001 From: Richard Shadrach Date: Mon, 15 Mar 2021 16:52:44 -0400 Subject: [PATCH 5/5] Added test and versionadded --- pandas/io/excel/_base.py | 2 ++ pandas/tests/io/excel/test_writers.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index f17fa4d06f9b9..c0904c0393af6 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -669,6 +669,8 @@ class ExcelWriter(metaclass=abc.ABCMeta): .. versionadded:: 1.2.0 engine_kwargs : dict, optional Keyword arguments to be passed into the engine. + + .. versionadded:: 1.3.0 **kwargs : dict, optional Keyword arguments to be passed into the engine. diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 34b56d9bf4bb9..cce8c3d01025d 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -1402,6 +1402,21 @@ def test_kwargs_deprecated(self, ext): except TypeError: pass + @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_engine_kwargs_and_kwargs_raises(self, ext): + # GH 40430 + msg = re.escape("Cannot use both engine_kwargs and **kwargs") + with pytest.raises(ValueError, match=msg): + with ExcelWriter("", engine_kwargs={"a": 1}, b=2): + pass + @td.skip_if_no("xlrd") @td.skip_if_no("openpyxl")