Skip to content

Backport PR #48943 on branch 1.5.x (REGR: ExcelWriter.book not settable) #48959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.5.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`)

.. ---------------------------------------------------------------------------

Expand Down
28 changes: 26 additions & 2 deletions pandas/io/excel/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand All @@ -1202,6 +1204,16 @@ def book(self):
"""
pass

# 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:
"""
Set book instance. Class type will depend on the engine used.
"""
pass

def write_cells(
self,
cells,
Expand Down Expand Up @@ -1331,12 +1343,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:
"""
Expand Down
10 changes: 10 additions & 0 deletions pandas/io/excel/_odswriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
)

if TYPE_CHECKING:
from odf.opendocument import OpenDocumentSpreadsheet

from pandas.io.formats.excel import ExcelCell


Expand Down Expand Up @@ -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."""
Expand Down
8 changes: 8 additions & 0 deletions pandas/io/excel/_openpyxl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
16 changes: 15 additions & 1 deletion pandas/io/excel/_xlsxwriter.py
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
15 changes: 13 additions & 2 deletions pandas/io/excel/_xlwt.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
)

if TYPE_CHECKING:
from xlwt import XFStyle
from xlwt import (
Workbook,
XFStyle,
)


class XlwtWriter(ExcelWriter):
Expand Down Expand Up @@ -64,14 +67,22 @@ 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.

This attribute can be used to access engine-specific features.
"""
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."""
Expand Down
11 changes: 11 additions & 0 deletions pandas/tests/io/excel/test_writers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down