From 322a5b5a1e40bd359e3550167cca30e324e3ec06 Mon Sep 17 00:00:00 2001 From: Richard Shadrach <45562402+rhshadrach@users.noreply.github.com> Date: Wed, 5 Oct 2022 14:04:01 -0400 Subject: [PATCH 1/2] Backport PR #48943: REGR: ExcelWriter.book not settable --- doc/source/whatsnew/v1.5.1.rst | 1 + pandas/io/excel/_base.py | 22 +++++++++++++++++++++- pandas/io/excel/_odswriter.py | 10 ++++++++++ pandas/io/excel/_openpyxl.py | 8 ++++++++ pandas/io/excel/_xlsxwriter.py | 16 +++++++++++++++- pandas/io/excel/_xlwt.py | 15 +++++++++++++-- pandas/tests/io/excel/test_writers.py | 11 +++++++++++ 7 files changed, 79 insertions(+), 4 deletions(-) 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 79dc5ee3789e3..ef48d9baf095c 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -1202,6 +1202,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, @@ -1331,12 +1339,24 @@ def _deprecate(self, attr: str): 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( From 95df8be09fd3da8f22d13b8f898bb786c2926783 Mon Sep 17 00:00:00 2001 From: Richard Shadrach <45562402+rhshadrach@users.noreply.github.com> Date: Wed, 5 Oct 2022 18:08:55 -0400 Subject: [PATCH 2/2] type ignores --- pandas/io/excel/_base.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index ef48d9baf095c..f6a66307e2191 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -1192,7 +1192,9 @@ def sheets(self) -> dict[str, Any]: """Mapping of sheet names to sheet objects.""" pass - @property + # mypy doesn't handle abstract setters prior to 0.981 + # https://github.com/python/mypy/issues/4165 + @property # type: ignore[misc] @abc.abstractmethod def book(self): """ @@ -1202,7 +1204,9 @@ def book(self): """ pass - @book.setter + # mypy doesn't handle abstract setters prior to 0.981 + # https://github.com/python/mypy/issues/4165 + @book.setter # type: ignore[misc] @abc.abstractmethod def book(self, other) -> None: """