diff --git a/doc/source/whatsnew/v1.5.1.rst b/doc/source/whatsnew/v1.5.1.rst index ab80bdb9ed782..4d7576c013fd6 100644 --- a/doc/source/whatsnew/v1.5.1.rst +++ b/doc/source/whatsnew/v1.5.1.rst @@ -84,6 +84,7 @@ Fixed regressions - Fixed Regression in :meth:`DataFrameGroupBy.apply` when user defined function is called on an empty dataframe (:issue:`47985`) - Fixed regression in :meth:`DataFrame.apply` when passing non-zero ``axis`` via keyword argument (:issue:`48656`) - Fixed regression in :meth:`Series.groupby` and :meth:`DataFrame.groupby` when the grouper is a nullable data type (e.g. :class:`Int64`) or a PyArrow-backed string array, contains null values, and ``dropna=False`` (:issue:`48794`) +- Fixed regression in :class:`ExcelWriter` where the ``book`` attribute could no longer be set; however setting this attribute is now deprecated and this ability will be removed in a future version of pandas (:issue:`48780`) .. --------------------------------------------------------------------------- diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 5e5318a746b94..47353ce1c402e 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -1203,6 +1203,14 @@ def book(self): """ pass + @book.setter + @abc.abstractmethod + def book(self, other) -> None: + """ + Set book instance. Class type will depend on the engine used. + """ + pass + def write_cells( self, cells, @@ -1332,12 +1340,24 @@ def _deprecate(self, attr: str) -> None: Deprecate attribute or method for ExcelWriter. """ warnings.warn( - f"{attr} is not part of the public API, usage can give in unexpected " + f"{attr} is not part of the public API, usage can give unexpected " "results and will be removed in a future version", FutureWarning, stacklevel=find_stack_level(inspect.currentframe()), ) + def _deprecate_set_book(self) -> None: + """ + Deprecate setting the book attribute - GH#48780. + """ + warnings.warn( + "Setting the `book` attribute is not part of the public API, " + "usage can give unexpected or corrupted results and will be " + "removed in a future version", + FutureWarning, + stacklevel=find_stack_level(inspect.currentframe()), + ) + @property def date_format(self) -> str: """ diff --git a/pandas/io/excel/_odswriter.py b/pandas/io/excel/_odswriter.py index 185e93591cfe0..5603c601e2c45 100644 --- a/pandas/io/excel/_odswriter.py +++ b/pandas/io/excel/_odswriter.py @@ -24,6 +24,8 @@ ) if TYPE_CHECKING: + from odf.opendocument import OpenDocumentSpreadsheet + from pandas.io.formats.excel import ExcelCell @@ -70,6 +72,14 @@ def book(self): """ return self._book + @book.setter + def book(self, other: OpenDocumentSpreadsheet) -> None: + """ + Set book instance. Class type will depend on the engine used. + """ + self._deprecate_set_book() + self._book = other + @property def sheets(self) -> dict[str, Any]: """Mapping of sheet names to sheet objects.""" diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index c3cd3fbe9e853..6fde319b3a81e 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -88,6 +88,14 @@ def book(self) -> Workbook: """ return self._book + @book.setter + def book(self, other: Workbook) -> None: + """ + Set book instance. Class type will depend on the engine used. + """ + self._deprecate_set_book() + self._book = other + @property def sheets(self) -> dict[str, Any]: """Mapping of sheet names to sheet objects.""" diff --git a/pandas/io/excel/_xlsxwriter.py b/pandas/io/excel/_xlsxwriter.py index a3edccd3a5779..8d11896cb7374 100644 --- a/pandas/io/excel/_xlsxwriter.py +++ b/pandas/io/excel/_xlsxwriter.py @@ -1,6 +1,9 @@ from __future__ import annotations -from typing import Any +from typing import ( + TYPE_CHECKING, + Any, +) import pandas._libs.json as json from pandas._typing import ( @@ -15,6 +18,9 @@ validate_freeze_panes, ) +if TYPE_CHECKING: + from xlsxwriter import Workbook + class _XlsxStyler: # Map from openpyxl-oriented styles to flatter xlsxwriter representation @@ -218,6 +224,14 @@ def book(self): """ return self._book + @book.setter + def book(self, other: Workbook) -> None: + """ + Set book instance. Class type will depend on the engine used. + """ + self._deprecate_set_book() + self._book = other + @property def sheets(self) -> dict[str, Any]: result = self.book.sheetnames diff --git a/pandas/io/excel/_xlwt.py b/pandas/io/excel/_xlwt.py index 234d9e72de10d..f1455e472bb43 100644 --- a/pandas/io/excel/_xlwt.py +++ b/pandas/io/excel/_xlwt.py @@ -21,7 +21,10 @@ ) if TYPE_CHECKING: - from xlwt import XFStyle + from xlwt import ( + Workbook, + XFStyle, + ) class XlwtWriter(ExcelWriter): @@ -64,7 +67,7 @@ def __init__( self._fm_date = xlwt.easyxf(num_format_str=self._date_format) @property - def book(self): + def book(self) -> Workbook: """ Book instance of class xlwt.Workbook. @@ -72,6 +75,14 @@ def book(self): """ return self._book + @book.setter + def book(self, other: Workbook) -> None: + """ + Set book instance. Class type will depend on the engine used. + """ + self._deprecate_set_book() + self._book = other + @property def sheets(self) -> dict[str, Any]: """Mapping of sheet names to sheet objects.""" diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index 3f9ab78e720b9..d4b74ddbd66e0 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -1301,6 +1301,17 @@ def test_deprecated_method(self, engine, ext, attr, args): with tm.assert_produces_warning(FutureWarning, match=msg): getattr(writer, attr)(*args) + def test_deprecated_book_setter(self, engine, ext): + # GH#48780 + with tm.ensure_clean(ext) as path: + with ExcelWriter(path) as writer: + msg = "Setting the `book` attribute is not part of the public API" + # Some engines raise if nothing is written + DataFrame().to_excel(writer) + book = writer.book + with tm.assert_produces_warning(FutureWarning, match=msg): + writer.book = book + class TestExcelWriterEngineTests: @pytest.mark.parametrize(