From 4b73d6a3aa83a7e5b466a81bfcc65f4c40e838bd Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Mon, 1 Mar 2021 21:31:27 +0000 Subject: [PATCH 01/15] ENH: Add if_exists parameter to ExcelWriter --- doc/source/whatsnew/v1.3.0.rst | 1 + pandas/io/excel/_base.py | 23 +++++++++++++ pandas/io/excel/_openpyxl.py | 25 ++++++++++++-- pandas/tests/io/excel/test_openpyxl.py | 47 ++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index f7204ceb9d412..2f99a28275741 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -125,6 +125,7 @@ Other enhancements - :func:`to_numeric` now supports downcasting of nullable ``ExtensionDtype`` objects (:issue:`33013`) - Add support for dict-like names in :class:`MultiIndex.set_names` and :class:`MultiIndex.rename` (:issue:`20421`) - :func:`pandas.read_excel` can now auto detect .xlsb files (:issue:`35416`) +- :class:`pandas.ExcelWriter` now accepts an ``if_exists`` parameter to control the behaviour of append mode when writing to existing sheets (:issue:`40230`) - :meth:`.Rolling.sum`, :meth:`.Expanding.sum`, :meth:`.Rolling.mean`, :meth:`.Expanding.mean`, :meth:`.Rolling.median`, :meth:`.Expanding.median`, :meth:`.Rolling.max`, :meth:`.Expanding.max`, :meth:`.Rolling.min`, and :meth:`.Expanding.min` now support ``Numba`` execution with the ``engine`` keyword (:issue:`38895`) - :meth:`DataFrame.apply` can now accept NumPy unary operators as strings, e.g. ``df.apply("sqrt")``, which was already the case for :meth:`Series.apply` (:issue:`39116`) - :meth:`DataFrame.apply` can now accept non-callable DataFrame properties as strings, e.g. ``df.apply("size")``, which was already the case for :meth:`Series.apply` (:issue:`39116`) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 9ad589d4583c6..c777d9326382e 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -666,6 +666,16 @@ class ExcelWriter(metaclass=abc.ABCMeta): be parsed by ``fsspec``, e.g., starting "s3://", "gcs://". .. versionadded:: 1.2.0 + if_exists : {'new_sheet', 'overwrite_sheet', 'overwrite_cells'}, default 'new_sheet' + How to behave when trying to write to a sheet that already + exists (append mode only). + + * new_sheet: Create a new sheet with a different name. + * overwrite_sheet: Delete the contents of the sheet, then write to it. + * overwrite_cells: Write directly to the named sheet + without deleting the previous contents. + + .. versionadded:: 1.3.0 Attributes ---------- @@ -834,6 +844,7 @@ def __init__( datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, + if_exists: Optional[str] = None, **engine_kwargs, ): # validate that this engine can handle the extension @@ -868,6 +879,18 @@ def __init__( self.mode = mode + if if_exists and "r+" not in mode: + raise ValueError("if_exists is only valid in append mode (mode='a')") + if if_exists is not None and if_exists not in { + "new_sheet", + "overwrite_sheet", + "overwrite_cells", + }: + raise ValueError(f"'{if_exists}' is not valid for if_exists") + if if_exists is None and "r+" in mode: + if_exists = "new_sheet" + self.if_exists = if_exists + def __fspath__(self): return getattr(self.handles.handle, "name", "") diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index be2c9b919a5c3..7ae4e866bb235 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -37,13 +37,18 @@ def __init__( engine=None, mode: str = "w", storage_options: StorageOptions = None, + if_exists: Optional[str] = None, **engine_kwargs, ): # 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, + if_exists=if_exists, + **engine_kwargs, ) # ExcelWriter replaced "a" by "r+" to allow us to first read the excel file from @@ -53,6 +58,8 @@ def __init__( self.book = load_workbook(self.handles.handle) self.handles.handle.seek(0) + self.sheets = {name: self.book[name] for name in self.book.sheetnames} + else: # Create workbook object with default optimized_write=True. self.book = Workbook() @@ -412,7 +419,21 @@ def write_cells( _style_cache: Dict[str, Dict[str, Serialisable]] = {} if sheet_name in self.sheets: - wks = self.sheets[sheet_name] + if "r+" in self.mode: + if self.if_exists == "new_sheet": + wks = self.book.create_sheet() + # openpyxl will create a name for the new sheet by appending digits + wks.title = sheet_name + self.sheets[wks.title] = wks + elif self.if_exists == "overwrite_sheet": + wks = self.sheets[sheet_name] + wks.delete_cols(1, wks.max_column) + elif self.if_exists == "overwrite_cells" or self.if_exists is None: + wks = self.sheets[sheet_name] + else: + raise ValueError(f"'{self.if_exists}' is not valid for if_exists") + else: + wks = self.sheets[sheet_name] else: wks = self.book.create_sheet() wks.title = sheet_name diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index 9010f978d268d..3fd7d6727d14b 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -1,4 +1,5 @@ from pathlib import Path +import re import numpy as np import pytest @@ -109,6 +110,52 @@ def test_write_append_mode(ext, mode, expected): assert wb2.worksheets[index]["A1"].value == cell_value +@pytest.mark.parametrize( + "if_exists,num_sheets,expected", + [ + ("new_sheet", 2, ["apple", "banana"]), + ("overwrite_sheet", 1, ["pear"]), + ("overwrite_cells", 1, ["pear", "banana"]), + ], +) +def test_if_exists_append_modes(ext, if_exists, num_sheets, expected): + # GH 40230 + df1 = DataFrame({"fruit": ["apple", "banana"]}) + df2 = DataFrame({"fruit": ["pear"]}) + + with tm.ensure_clean(ext) as f: + with pd.ExcelWriter(f, engine="openpyxl", mode="w") as writer: + df1.to_excel(writer, sheet_name="foo", index=False) + with pd.ExcelWriter( + f, engine="openpyxl", mode="a", if_exists=if_exists + ) as writer: + df2.to_excel(writer, sheet_name="foo", index=False) + + wb = openpyxl.load_workbook(f) + assert len(wb.sheetnames) == num_sheets + assert wb.sheetnames[0] == "foo" + result = pd.read_excel(wb, "foo", engine="openpyxl") + assert list(result["fruit"]) == expected + if len(wb.sheetnames) == 2: + # atm the name given for the second sheet will be "foo1" + # but we don't want the test to fail if openpyxl changes this + result = pd.read_excel(wb, wb.sheetnames[1], engine="openpyxl") + assert result.equals(df2) + wb.close() + + +def test_if_exists_raises(ext): + if_exists_msg = "if_exists is only valid in append mode (mode='a')" + invalid_msg = "'invalid' is not valid for if_exists" + + with tm.ensure_clean(ext) as f: + with pytest.raises(ValueError, match=re.escape(if_exists_msg)): + ExcelWriter(f, engine="openpyxl", mode="w", if_exists="new_sheet") + with tm.ensure_clean(ext) as f: + with pytest.raises(ValueError, match=invalid_msg): + ExcelWriter(f, engine="openpyxl", mode="a", if_exists="invalid") + + def test_to_excel_with_openpyxl_engine(ext): # GH 29854 with tm.ensure_clean(ext) as filename: From e8716c7eda81dfba7a2485a4a35ca92fa7052861 Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Fri, 5 Mar 2021 19:26:33 +0000 Subject: [PATCH 02/15] Rename parameter to if_sheet_exists --- doc/source/whatsnew/v1.3.0.rst | 2 +- pandas/io/excel/_base.py | 38 +++++++++++++------------ pandas/io/excel/_openpyxl.py | 16 +++++++---- pandas/tests/io/excel/test_openpyxl.py | 39 +++++++++++++++++--------- 4 files changed, 56 insertions(+), 39 deletions(-) diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 4e1bda437d5e4..6e4a22205b745 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -125,7 +125,7 @@ Other enhancements - :func:`to_numeric` now supports downcasting of nullable ``ExtensionDtype`` objects (:issue:`33013`) - Add support for dict-like names in :class:`MultiIndex.set_names` and :class:`MultiIndex.rename` (:issue:`20421`) - :func:`pandas.read_excel` can now auto detect .xlsb files (:issue:`35416`) -- :class:`pandas.ExcelWriter` now accepts an ``if_exists`` parameter to control the behaviour of append mode when writing to existing sheets (:issue:`40230`) +- :class:`pandas.ExcelWriter` now accepts an ``if_sheet_exists`` parameter to control the behaviour of append mode when writing to existing sheets (:issue:`40230`) - :meth:`.Rolling.sum`, :meth:`.Expanding.sum`, :meth:`.Rolling.mean`, :meth:`.Expanding.mean`, :meth:`.Rolling.median`, :meth:`.Expanding.median`, :meth:`.Rolling.max`, :meth:`.Expanding.max`, :meth:`.Rolling.min`, and :meth:`.Expanding.min` now support ``Numba`` execution with the ``engine`` keyword (:issue:`38895`) - :meth:`DataFrame.apply` can now accept NumPy unary operators as strings, e.g. ``df.apply("sqrt")``, which was already the case for :meth:`Series.apply` (:issue:`39116`) - :meth:`DataFrame.apply` can now accept non-callable DataFrame properties as strings, e.g. ``df.apply("size")``, which was already the case for :meth:`Series.apply` (:issue:`39116`) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index c777d9326382e..d96a65d574c49 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -666,14 +666,15 @@ class ExcelWriter(metaclass=abc.ABCMeta): be parsed by ``fsspec``, e.g., starting "s3://", "gcs://". .. versionadded:: 1.2.0 - if_exists : {'new_sheet', 'overwrite_sheet', 'overwrite_cells'}, default 'new_sheet' - How to behave when trying to write to a sheet that already - exists (append mode only). + if_sheet_exists : {'avoid', 'replace', 'overwrite', 'fail'}, default 'avoid' + How to behave when trying to write to a sheet that already + exists (append mode only). - * new_sheet: Create a new sheet with a different name. - * overwrite_sheet: Delete the contents of the sheet, then write to it. - * overwrite_cells: Write directly to the named sheet - without deleting the previous contents. + * avoid: Create a new sheet with a different name. + * replace: Delete the contents of the sheet before writing to it. + * overwrite: Write directly to the named sheet + without deleting the previous contents. + * fail: raise a ValueError. .. versionadded:: 1.3.0 @@ -844,7 +845,7 @@ def __init__( datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, - if_exists: Optional[str] = None, + if_sheet_exists: Optional[str] = None, **engine_kwargs, ): # validate that this engine can handle the extension @@ -879,17 +880,18 @@ def __init__( self.mode = mode - if if_exists and "r+" not in mode: - raise ValueError("if_exists is only valid in append mode (mode='a')") - if if_exists is not None and if_exists not in { - "new_sheet", - "overwrite_sheet", - "overwrite_cells", + if if_sheet_exists and "r+" not in mode: + raise ValueError("if_sheet_exists is only valid in append mode (mode='a')") + if if_sheet_exists is not None and if_sheet_exists not in { + "avoid", + "replace", + "overwrite", + "fail", }: - raise ValueError(f"'{if_exists}' is not valid for if_exists") - if if_exists is None and "r+" in mode: - if_exists = "new_sheet" - self.if_exists = if_exists + raise ValueError(f"'{if_sheet_exists}' is not valid for if_sheet_exists") + if if_sheet_exists is None and "r+" in mode: + if_sheet_exists = "avoid" + self.if_sheet_exists = if_sheet_exists def __fspath__(self): return getattr(self.handles.handle, "name", "") diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index 7ae4e866bb235..8170797e98e7c 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -37,7 +37,7 @@ def __init__( engine=None, mode: str = "w", storage_options: StorageOptions = None, - if_exists: Optional[str] = None, + if_sheet_exists: Optional[str] = None, **engine_kwargs, ): # Use the openpyxl module as the Excel writer. @@ -47,7 +47,7 @@ def __init__( path, mode=mode, storage_options=storage_options, - if_exists=if_exists, + if_sheet_exists=if_sheet_exists, **engine_kwargs, ) @@ -420,18 +420,22 @@ def write_cells( if sheet_name in self.sheets: if "r+" in self.mode: - if self.if_exists == "new_sheet": + if self.if_sheet_exists == "avoid": wks = self.book.create_sheet() # openpyxl will create a name for the new sheet by appending digits wks.title = sheet_name self.sheets[wks.title] = wks - elif self.if_exists == "overwrite_sheet": + elif self.if_sheet_exists == "replace": wks = self.sheets[sheet_name] wks.delete_cols(1, wks.max_column) - elif self.if_exists == "overwrite_cells" or self.if_exists is None: + elif self.if_sheet_exists == "overwrite": wks = self.sheets[sheet_name] + elif self.if_sheet_exists == "fail": + raise ValueError(f"Sheet '{sheet_name}' already exists.") else: - raise ValueError(f"'{self.if_exists}' is not valid for if_exists") + raise ValueError( + f"'{self.if_sheet_exists}' is not valid for if_sheet_exists" + ) else: wks = self.sheets[sheet_name] else: diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index 3fd7d6727d14b..06a6276fa3a60 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -111,23 +111,23 @@ def test_write_append_mode(ext, mode, expected): @pytest.mark.parametrize( - "if_exists,num_sheets,expected", + "if_sheet_exists,num_sheets,expected", [ - ("new_sheet", 2, ["apple", "banana"]), - ("overwrite_sheet", 1, ["pear"]), - ("overwrite_cells", 1, ["pear", "banana"]), + ("avoid", 2, ["apple", "banana"]), + (None, 2, ["apple", "banana"]), + ("replace", 1, ["pear"]), + ("overwrite", 1, ["pear", "banana"]), ], ) -def test_if_exists_append_modes(ext, if_exists, num_sheets, expected): +def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected): # GH 40230 df1 = DataFrame({"fruit": ["apple", "banana"]}) df2 = DataFrame({"fruit": ["pear"]}) with tm.ensure_clean(ext) as f: - with pd.ExcelWriter(f, engine="openpyxl", mode="w") as writer: - df1.to_excel(writer, sheet_name="foo", index=False) + df1.to_excel(f, engine="openpyxl", sheet_name="foo", index=False) with pd.ExcelWriter( - f, engine="openpyxl", mode="a", if_exists=if_exists + f, engine="openpyxl", mode="a", if_sheet_exists=if_sheet_exists ) as writer: df2.to_excel(writer, sheet_name="foo", index=False) @@ -144,16 +144,27 @@ def test_if_exists_append_modes(ext, if_exists, num_sheets, expected): wb.close() -def test_if_exists_raises(ext): - if_exists_msg = "if_exists is only valid in append mode (mode='a')" - invalid_msg = "'invalid' is not valid for if_exists" +def test_if_sheet_exists_raises(ext): + mode_msg = "if_sheet_exists is only valid in append mode (mode='a')" + invalid_msg = "'invalid' is not valid for if_sheet_exists" + fail_msg = "Sheet 'foo' already exists." + df = DataFrame({"fruit": ["pear"]}) with tm.ensure_clean(ext) as f: - with pytest.raises(ValueError, match=re.escape(if_exists_msg)): - ExcelWriter(f, engine="openpyxl", mode="w", if_exists="new_sheet") + with pytest.raises(ValueError, match=re.escape(mode_msg)): + ExcelWriter(f, engine="openpyxl", mode="w", if_sheet_exists="new_sheet") + with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=invalid_msg): - ExcelWriter(f, engine="openpyxl", mode="a", if_exists="invalid") + ExcelWriter(f, engine="openpyxl", mode="a", if_sheet_exists="invalid") + + with tm.ensure_clean(ext) as f: + with pytest.raises(ValueError, match=fail_msg): + df.to_excel(f, "foo", engine="openpyxl") + with pd.ExcelWriter( + f, engine="openpyxl", mode="a", if_sheet_exists="fail" + ) as writer: + df.to_excel(writer, sheet_name="foo") def test_to_excel_with_openpyxl_engine(ext): From 71104f6723e806253d85ee7245bb9fb2e496c5f3 Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Sat, 6 Mar 2021 13:23:05 +0000 Subject: [PATCH 03/15] Update avoid -> new in ExcelWriter if_sheet_exists --- pandas/io/excel/_base.py | 16 ++++++---------- pandas/io/excel/_openpyxl.py | 2 +- pandas/tests/io/excel/test_openpyxl.py | 4 ++-- pandas/tests/io/excel/test_xlsxwriter.py | 4 ++++ 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index d96a65d574c49..f89aa3d7f55bb 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -666,11 +666,11 @@ class ExcelWriter(metaclass=abc.ABCMeta): be parsed by ``fsspec``, e.g., starting "s3://", "gcs://". .. versionadded:: 1.2.0 - if_sheet_exists : {'avoid', 'replace', 'overwrite', 'fail'}, default 'avoid' + if_sheet_exists : {'new', 'replace', 'overwrite', 'fail'}, default 'new' How to behave when trying to write to a sheet that already exists (append mode only). - * avoid: Create a new sheet with a different name. + * new: Create a new sheet with a different name. * replace: Delete the contents of the sheet before writing to it. * overwrite: Write directly to the named sheet without deleting the previous contents. @@ -880,17 +880,13 @@ def __init__( self.mode = mode + ise_valid = [None, "new", "replace", "overwrite", "fail"] + if if_sheet_exists not in ise_valid: + raise ValueError(f"'{if_sheet_exists}' is not valid for if_sheet_exists") if if_sheet_exists and "r+" not in mode: raise ValueError("if_sheet_exists is only valid in append mode (mode='a')") - if if_sheet_exists is not None and if_sheet_exists not in { - "avoid", - "replace", - "overwrite", - "fail", - }: - raise ValueError(f"'{if_sheet_exists}' is not valid for if_sheet_exists") if if_sheet_exists is None and "r+" in mode: - if_sheet_exists = "avoid" + if_sheet_exists = "new" self.if_sheet_exists = if_sheet_exists def __fspath__(self): diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index 8170797e98e7c..086ce2d72c0dd 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -420,7 +420,7 @@ def write_cells( if sheet_name in self.sheets: if "r+" in self.mode: - if self.if_sheet_exists == "avoid": + if self.if_sheet_exists in "new": wks = self.book.create_sheet() # openpyxl will create a name for the new sheet by appending digits wks.title = sheet_name diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index 06a6276fa3a60..1171beb048fa5 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -113,7 +113,7 @@ def test_write_append_mode(ext, mode, expected): @pytest.mark.parametrize( "if_sheet_exists,num_sheets,expected", [ - ("avoid", 2, ["apple", "banana"]), + ("new", 2, ["apple", "banana"]), (None, 2, ["apple", "banana"]), ("replace", 1, ["pear"]), ("overwrite", 1, ["pear", "banana"]), @@ -152,7 +152,7 @@ def test_if_sheet_exists_raises(ext): with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=re.escape(mode_msg)): - ExcelWriter(f, engine="openpyxl", mode="w", if_sheet_exists="new_sheet") + ExcelWriter(f, engine="openpyxl", mode="w", if_sheet_exists="new") with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=invalid_msg): diff --git a/pandas/tests/io/excel/test_xlsxwriter.py b/pandas/tests/io/excel/test_xlsxwriter.py index 6de378f6a3d3e..08514c6b39a4d 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 @@ -57,7 +58,10 @@ def test_column_format(ext): def test_write_append_mode_raises(ext): msg = "Append mode is not supported with xlsxwriter!" + ise_msg = "if_sheet_exists is only valid in append mode (mode='a')" with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=msg): ExcelWriter(f, engine="xlsxwriter", mode="a") + with pytest.raises(ValueError, match=re.escape(ise_msg)): + ExcelWriter(f, engine="xlsxwriter", if_sheet_exists="replace") From ae5b385d2b0f207c074f997467ea215d6fd9c96a Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Sat, 6 Mar 2021 13:46:10 +0000 Subject: [PATCH 04/15] use assert_frame_equal instead of result.equals --- pandas/tests/io/excel/test_openpyxl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index 1171beb048fa5..b4c5bc907c0d4 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -140,7 +140,7 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected # atm the name given for the second sheet will be "foo1" # but we don't want the test to fail if openpyxl changes this result = pd.read_excel(wb, wb.sheetnames[1], engine="openpyxl") - assert result.equals(df2) + tm.assert_frame_equal(result, df2) wb.close() From 6baec67643923ae93b685bdec01105b51f1c0a94 Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Sat, 6 Mar 2021 14:38:49 +0000 Subject: [PATCH 05/15] fix mypy error --- pandas/io/excel/_openpyxl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index 086ce2d72c0dd..3d663f49c35c6 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -420,7 +420,7 @@ def write_cells( if sheet_name in self.sheets: if "r+" in self.mode: - if self.if_sheet_exists in "new": + if self.if_sheet_exists == "new": wks = self.book.create_sheet() # openpyxl will create a name for the new sheet by appending digits wks.title = sheet_name From 3a2a54f475bfd55f47eb6dcbbdc046c02ab9c6d6 Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:27:16 +0000 Subject: [PATCH 06/15] Update docstring to better describe 'new' behaviour --- pandas/io/excel/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index f89aa3d7f55bb..bcf04b73cac5a 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -670,7 +670,7 @@ class ExcelWriter(metaclass=abc.ABCMeta): How to behave when trying to write to a sheet that already exists (append mode only). - * new: Create a new sheet with a different name. + * new: Create a new sheet, with a name determined by the engine. * replace: Delete the contents of the sheet before writing to it. * overwrite: Write directly to the named sheet without deleting the previous contents. From fcbab057eb44b783d5617e5edc272ca04e30876c Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:32:53 +0000 Subject: [PATCH 07/15] Parameterize test --- pandas/tests/io/excel/test_openpyxl.py | 31 +++++++++++++------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index b4c5bc907c0d4..ab0c2b78cb3cd 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -144,25 +144,26 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected wb.close() -def test_if_sheet_exists_raises(ext): - mode_msg = "if_sheet_exists is only valid in append mode (mode='a')" - invalid_msg = "'invalid' is not valid for if_sheet_exists" - fail_msg = "Sheet 'foo' already exists." +@pytest.mark.parametrize( + "mode,if_sheet_exists,msg", + [ + ("w", "new", "if_sheet_exists is only valid in append mode (mode='a')"), + ("a", "invalid", "'invalid' is not valid for if_sheet_exists"), + ( + "a", + "fail", + "Sheet 'foo' already exists and if_sheet_exists is set to 'fail'.", + ), + ], +) +def test_if_sheet_exists_raises(ext, mode, if_sheet_exists, msg): + # GH 40230 df = DataFrame({"fruit": ["pear"]}) - - with tm.ensure_clean(ext) as f: - with pytest.raises(ValueError, match=re.escape(mode_msg)): - ExcelWriter(f, engine="openpyxl", mode="w", if_sheet_exists="new") - - with tm.ensure_clean(ext) as f: - with pytest.raises(ValueError, match=invalid_msg): - ExcelWriter(f, engine="openpyxl", mode="a", if_sheet_exists="invalid") - with tm.ensure_clean(ext) as f: - with pytest.raises(ValueError, match=fail_msg): + with pytest.raises(ValueError, match=re.escape(msg)): df.to_excel(f, "foo", engine="openpyxl") with pd.ExcelWriter( - f, engine="openpyxl", mode="a", if_sheet_exists="fail" + f, engine="openpyxl", mode=mode, if_sheet_exists=if_sheet_exists ) as writer: df.to_excel(writer, sheet_name="foo") From 5bcd6e3731e277fca26a714e6f9bd8be9554263e Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Mon, 8 Mar 2021 19:35:45 +0000 Subject: [PATCH 08/15] Add raises test for other engines --- pandas/tests/io/excel/test_writers.py | 25 ++++++++++++++++++++++++ pandas/tests/io/excel/test_xlsxwriter.py | 4 ---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index d8448736c7cc8..ea20857a5bc62 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 @@ -1398,3 +1399,27 @@ def test_excelwriter_fspath(self): with tm.ensure_clean("foo.xlsx") as path: with ExcelWriter(path) as writer: assert os.fspath(writer) == str(path) + + +@pytest.mark.parametrize( + "engine,ext", + [ + pytest.param( + "xlwt", ".xls", marks=[td.skip_if_no("xlwt"), td.skip_if_no("xlrd")] + ), + pytest.param( + "xlsxwriter", + ".xlsx", + marks=[td.skip_if_no("xlsxwriter"), td.skip_if_no("xlrd")], + ), + pytest.param("odf", ".ods", marks=td.skip_if_no("odf")), + ], +) +@pytest.mark.usefixtures("set_engine") +def test_if_sheet_exists_raises(ext): + # GH 40230 + msg = "if_sheet_exists is only valid in append mode (mode='a')" + + with tm.ensure_clean(ext) as f: + with pytest.raises(ValueError, match=re.escape(msg)): + ExcelWriter(f, if_sheet_exists="replace") diff --git a/pandas/tests/io/excel/test_xlsxwriter.py b/pandas/tests/io/excel/test_xlsxwriter.py index 08514c6b39a4d..6de378f6a3d3e 100644 --- a/pandas/tests/io/excel/test_xlsxwriter.py +++ b/pandas/tests/io/excel/test_xlsxwriter.py @@ -1,4 +1,3 @@ -import re import warnings import pytest @@ -58,10 +57,7 @@ def test_column_format(ext): def test_write_append_mode_raises(ext): msg = "Append mode is not supported with xlsxwriter!" - ise_msg = "if_sheet_exists is only valid in append mode (mode='a')" with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=msg): ExcelWriter(f, engine="xlsxwriter", mode="a") - with pytest.raises(ValueError, match=re.escape(ise_msg)): - ExcelWriter(f, engine="xlsxwriter", if_sheet_exists="replace") From 47527c6abfe17f8159cbb5afb6ca0c130b683fcd Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Mon, 8 Mar 2021 20:02:23 +0000 Subject: [PATCH 09/15] More detailed error message --- pandas/io/excel/_base.py | 5 ++++- pandas/io/excel/_openpyxl.py | 3 ++- pandas/tests/io/excel/test_openpyxl.py | 7 ++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index bcf04b73cac5a..faa711cc7f020 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -882,7 +882,10 @@ def __init__( ise_valid = [None, "new", "replace", "overwrite", "fail"] if if_sheet_exists not in ise_valid: - raise ValueError(f"'{if_sheet_exists}' is not valid for if_sheet_exists") + raise ValueError( + f"'{if_sheet_exists}' is not valid for if_sheet_exists. " + "Valid options are 'new', 'replace', 'overwrite' and 'fail'." + ) if if_sheet_exists and "r+" not in mode: raise ValueError("if_sheet_exists is only valid in append mode (mode='a')") if if_sheet_exists is None and "r+" in mode: diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index 3d663f49c35c6..c56e973b8663f 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -434,7 +434,8 @@ def write_cells( raise ValueError(f"Sheet '{sheet_name}' already exists.") else: raise ValueError( - f"'{self.if_sheet_exists}' is not valid for if_sheet_exists" + f"'{self.if_sheet_exists}' is not valid for if_sheet_exists. " + "Valid options are 'new', 'replace', 'overwrite' and 'fail'." ) else: wks = self.sheets[sheet_name] diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index ab0c2b78cb3cd..c0af852a074c8 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -148,7 +148,12 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected "mode,if_sheet_exists,msg", [ ("w", "new", "if_sheet_exists is only valid in append mode (mode='a')"), - ("a", "invalid", "'invalid' is not valid for if_sheet_exists"), + ( + "a", + "invalid", + "'invalid' is not valid for if_sheet_exists. Valid options " + "are 'new', 'replace', 'overwrite' and 'fail'.", + ), ( "a", "fail", From 060d754fded57eb32c95aebca7b9e91cdbef931a Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Mon, 8 Mar 2021 20:03:30 +0000 Subject: [PATCH 10/15] Remove redundant code, delete sheet when replacing --- pandas/io/excel/_openpyxl.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index c56e973b8663f..4a5d6c787c687 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -418,20 +418,21 @@ def write_cells( _style_cache: Dict[str, Dict[str, Serialisable]] = {} - if sheet_name in self.sheets: + if sheet_name in self.sheets and self.if_sheet_exists != "new": if "r+" in self.mode: - if self.if_sheet_exists == "new": - wks = self.book.create_sheet() - # openpyxl will create a name for the new sheet by appending digits - wks.title = sheet_name - self.sheets[wks.title] = wks - elif self.if_sheet_exists == "replace": - wks = self.sheets[sheet_name] - wks.delete_cols(1, wks.max_column) + if self.if_sheet_exists == "replace": + old_wks = self.sheets[sheet_name] + target_index = self.book.index(old_wks) + del self.book[sheet_name] + wks = self.book.create_sheet(sheet_name, target_index) + self.sheets[sheet_name] = wks elif self.if_sheet_exists == "overwrite": wks = self.sheets[sheet_name] elif self.if_sheet_exists == "fail": - raise ValueError(f"Sheet '{sheet_name}' already exists.") + raise ValueError( + f"Sheet '{sheet_name}' already exists and " + f"if_sheet_exists is set to 'fail'." + ) else: raise ValueError( f"'{self.if_sheet_exists}' is not valid for if_sheet_exists. " From d8f0be1dbc2ab43cf7454842be560b03a85d18d7 Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Tue, 9 Mar 2021 17:38:22 +0000 Subject: [PATCH 11/15] Tidy up raise tests --- pandas/tests/io/excel/test_openpyxl.py | 9 +++----- pandas/tests/io/excel/test_writers.py | 32 +++++++------------------- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index c0af852a074c8..8f00916d1bb4c 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -145,30 +145,27 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected @pytest.mark.parametrize( - "mode,if_sheet_exists,msg", + "if_sheet_exists,msg", [ - ("w", "new", "if_sheet_exists is only valid in append mode (mode='a')"), ( - "a", "invalid", "'invalid' is not valid for if_sheet_exists. Valid options " "are 'new', 'replace', 'overwrite' and 'fail'.", ), ( - "a", "fail", "Sheet 'foo' already exists and if_sheet_exists is set to 'fail'.", ), ], ) -def test_if_sheet_exists_raises(ext, mode, if_sheet_exists, msg): +def test_if_sheet_exists_raises(ext, if_sheet_exists, msg): # GH 40230 df = DataFrame({"fruit": ["pear"]}) with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=re.escape(msg)): df.to_excel(f, "foo", engine="openpyxl") with pd.ExcelWriter( - f, engine="openpyxl", mode=mode, if_sheet_exists=if_sheet_exists + f, engine="openpyxl", mode="a", if_sheet_exists=if_sheet_exists ) as writer: df.to_excel(writer, sheet_name="foo") diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index ea20857a5bc62..2c3a7a9648cf1 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -1325,6 +1325,14 @@ def test_excel_duplicate_columns_with_names(self, path): expected = DataFrame([[0, 10, 0], [1, 11, 1]], columns=["A", "B", "A.1"]) tm.assert_frame_equal(result, expected) + def test_if_sheet_exists_raises(self, ext): + # GH 40230 + msg = "if_sheet_exists is only valid in append mode (mode='a')" + + with tm.ensure_clean(ext) as f: + with pytest.raises(ValueError, match=re.escape(msg)): + ExcelWriter(f, if_sheet_exists="replace") + class TestExcelWriterEngineTests: @pytest.mark.parametrize( @@ -1399,27 +1407,3 @@ def test_excelwriter_fspath(self): with tm.ensure_clean("foo.xlsx") as path: with ExcelWriter(path) as writer: assert os.fspath(writer) == str(path) - - -@pytest.mark.parametrize( - "engine,ext", - [ - pytest.param( - "xlwt", ".xls", marks=[td.skip_if_no("xlwt"), td.skip_if_no("xlrd")] - ), - pytest.param( - "xlsxwriter", - ".xlsx", - marks=[td.skip_if_no("xlsxwriter"), td.skip_if_no("xlrd")], - ), - pytest.param("odf", ".ods", marks=td.skip_if_no("odf")), - ], -) -@pytest.mark.usefixtures("set_engine") -def test_if_sheet_exists_raises(ext): - # GH 40230 - msg = "if_sheet_exists is only valid in append mode (mode='a')" - - with tm.ensure_clean(ext) as f: - with pytest.raises(ValueError, match=re.escape(msg)): - ExcelWriter(f, if_sheet_exists="replace") From e31a59edd30e25f6242a152192975397bbc9dd4f Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Sat, 13 Mar 2021 17:07:39 +0000 Subject: [PATCH 12/15] Fix inconsistent namespacing --- pandas/tests/io/excel/test_openpyxl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index 8f00916d1bb4c..8366b890e7fe3 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -126,7 +126,7 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected with tm.ensure_clean(ext) as f: df1.to_excel(f, engine="openpyxl", sheet_name="foo", index=False) - with pd.ExcelWriter( + with ExcelWriter( f, engine="openpyxl", mode="a", if_sheet_exists=if_sheet_exists ) as writer: df2.to_excel(writer, sheet_name="foo", index=False) @@ -164,7 +164,7 @@ def test_if_sheet_exists_raises(ext, if_sheet_exists, msg): with tm.ensure_clean(ext) as f: with pytest.raises(ValueError, match=re.escape(msg)): df.to_excel(f, "foo", engine="openpyxl") - with pd.ExcelWriter( + with ExcelWriter( f, engine="openpyxl", mode="a", if_sheet_exists=if_sheet_exists ) as writer: df.to_excel(writer, sheet_name="foo") From 338b6d5e8186af4f07221b084eeb868a730a2cee Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Sun, 21 Mar 2021 15:38:43 +0000 Subject: [PATCH 13/15] Rename fail to error --- pandas/io/excel/_base.py | 10 +++++----- pandas/io/excel/_openpyxl.py | 6 +++--- pandas/tests/io/excel/test_openpyxl.py | 15 ++++++++++----- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index a3932773a14e3..5526ad1109a81 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -667,7 +667,7 @@ class ExcelWriter(metaclass=abc.ABCMeta): be parsed by ``fsspec``, e.g., starting "s3://", "gcs://". .. versionadded:: 1.2.0 - if_sheet_exists : {'new', 'replace', 'overwrite', 'fail'}, default 'new' + if_sheet_exists : {'new', 'replace', 'overwrite', 'error'}, default 'error' How to behave when trying to write to a sheet that already exists (append mode only). @@ -675,7 +675,7 @@ class ExcelWriter(metaclass=abc.ABCMeta): * replace: Delete the contents of the sheet before writing to it. * overwrite: Write directly to the named sheet without deleting the previous contents. - * fail: raise a ValueError. + * error: raise a ValueError. .. versionadded:: 1.3.0 engine_kwargs : dict, optional @@ -911,16 +911,16 @@ def __init__( self.mode = mode - ise_valid = [None, "new", "replace", "overwrite", "fail"] + ise_valid = [None, "new", "replace", "overwrite", "error"] if if_sheet_exists not in ise_valid: raise ValueError( f"'{if_sheet_exists}' is not valid for if_sheet_exists. " - "Valid options are 'new', 'replace', 'overwrite' and 'fail'." + "Valid options are 'new', 'replace', 'overwrite' and 'error'." ) if if_sheet_exists and "r+" not in mode: raise ValueError("if_sheet_exists is only valid in append mode (mode='a')") if if_sheet_exists is None and "r+" in mode: - if_sheet_exists = "new" + if_sheet_exists = "error" self.if_sheet_exists = if_sheet_exists def __fspath__(self): diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index 038b7f97374a9..2bfc2a88599f7 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -431,15 +431,15 @@ def write_cells( self.sheets[sheet_name] = wks elif self.if_sheet_exists == "overwrite": wks = self.sheets[sheet_name] - elif self.if_sheet_exists == "fail": + elif self.if_sheet_exists == "error": raise ValueError( f"Sheet '{sheet_name}' already exists and " - f"if_sheet_exists is set to 'fail'." + f"if_sheet_exists is set to 'error'." ) else: raise ValueError( f"'{self.if_sheet_exists}' is not valid for if_sheet_exists. " - "Valid options are 'new', 'replace', 'overwrite' and 'fail'." + "Valid options are 'new', 'replace', 'overwrite' and 'error'." ) else: wks = self.sheets[sheet_name] diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index 8366b890e7fe3..a03400ba03550 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -114,7 +114,6 @@ def test_write_append_mode(ext, mode, expected): "if_sheet_exists,num_sheets,expected", [ ("new", 2, ["apple", "banana"]), - (None, 2, ["apple", "banana"]), ("replace", 1, ["pear"]), ("overwrite", 1, ["pear", "banana"]), ], @@ -150,11 +149,15 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected ( "invalid", "'invalid' is not valid for if_sheet_exists. Valid options " - "are 'new', 'replace', 'overwrite' and 'fail'.", + "are 'new', 'replace', 'overwrite' and 'error'.", ), ( - "fail", - "Sheet 'foo' already exists and if_sheet_exists is set to 'fail'.", + "error", + "Sheet 'foo' already exists and if_sheet_exists is set to 'error'.", + ), + ( + None, + "Sheet 'foo' already exists and if_sheet_exists is set to 'error'.", ), ], ) @@ -236,7 +239,9 @@ def test_append_mode_file(ext): with tm.ensure_clean(ext) as f: df.to_excel(f, engine="openpyxl") - with ExcelWriter(f, mode="a", engine="openpyxl") as writer: + with ExcelWriter( + f, mode="a", engine="openpyxl", if_sheet_exists="new" + ) as writer: df.to_excel(writer) # make sure that zip files are not concatenated by making sure that From b2ad90bc0de7d4781b561cbe14459b4de9c027c8 Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Tue, 13 Apr 2021 20:22:43 +0100 Subject: [PATCH 14/15] Remove overwrite option --- pandas/io/excel/_base.py | 12 +++++------- pandas/io/excel/_openpyxl.py | 4 +--- pandas/tests/io/excel/test_openpyxl.py | 5 +---- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 3fff4124caa91..1536c760cd4b8 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -664,15 +664,13 @@ class ExcelWriter(metaclass=abc.ABCMeta): be parsed by ``fsspec``, e.g., starting "s3://", "gcs://". .. versionadded:: 1.2.0 - if_sheet_exists : {'new', 'replace', 'overwrite', 'error'}, default 'error' + if_sheet_exists : {'error', 'new', 'replace'}, default 'error' How to behave when trying to write to a sheet that already exists (append mode only). + * error: raise a ValueError. * new: Create a new sheet, with a name determined by the engine. * replace: Delete the contents of the sheet before writing to it. - * overwrite: Write directly to the named sheet - without deleting the previous contents. - * error: raise a ValueError. .. versionadded:: 1.3.0 engine_kwargs : dict, optional @@ -771,6 +769,7 @@ def __new__( datetime_format=None, mode: str = "w", storage_options: StorageOptions = None, + if_sheet_exists: str | None = None, engine_kwargs: dict | None = None, **kwargs, ): @@ -908,11 +907,10 @@ def __init__( self.mode = mode - ise_valid = [None, "new", "replace", "overwrite", "error"] - if if_sheet_exists not in ise_valid: + if if_sheet_exists not in [None, "error", "new", "replace"]: raise ValueError( f"'{if_sheet_exists}' is not valid for if_sheet_exists. " - "Valid options are 'new', 'replace', 'overwrite' and 'error'." + "Valid options are 'error', 'new' and 'replace'." ) if if_sheet_exists and "r+" not in mode: raise ValueError("if_sheet_exists is only valid in append mode (mode='a')") diff --git a/pandas/io/excel/_openpyxl.py b/pandas/io/excel/_openpyxl.py index 555dd6cca9671..a99f8e2625602 100644 --- a/pandas/io/excel/_openpyxl.py +++ b/pandas/io/excel/_openpyxl.py @@ -426,8 +426,6 @@ def write_cells( del self.book[sheet_name] wks = self.book.create_sheet(sheet_name, target_index) self.sheets[sheet_name] = wks - elif self.if_sheet_exists == "overwrite": - wks = self.sheets[sheet_name] elif self.if_sheet_exists == "error": raise ValueError( f"Sheet '{sheet_name}' already exists and " @@ -436,7 +434,7 @@ def write_cells( else: raise ValueError( f"'{self.if_sheet_exists}' is not valid for if_sheet_exists. " - "Valid options are 'new', 'replace', 'overwrite' and 'error'." + "Valid options are 'error', 'new' and 'replace'." ) else: wks = self.sheets[sheet_name] diff --git a/pandas/tests/io/excel/test_openpyxl.py b/pandas/tests/io/excel/test_openpyxl.py index a03400ba03550..62f567457c3ab 100644 --- a/pandas/tests/io/excel/test_openpyxl.py +++ b/pandas/tests/io/excel/test_openpyxl.py @@ -115,7 +115,6 @@ def test_write_append_mode(ext, mode, expected): [ ("new", 2, ["apple", "banana"]), ("replace", 1, ["pear"]), - ("overwrite", 1, ["pear", "banana"]), ], ) def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected): @@ -136,8 +135,6 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected result = pd.read_excel(wb, "foo", engine="openpyxl") assert list(result["fruit"]) == expected if len(wb.sheetnames) == 2: - # atm the name given for the second sheet will be "foo1" - # but we don't want the test to fail if openpyxl changes this result = pd.read_excel(wb, wb.sheetnames[1], engine="openpyxl") tm.assert_frame_equal(result, df2) wb.close() @@ -149,7 +146,7 @@ def test_if_sheet_exists_append_modes(ext, if_sheet_exists, num_sheets, expected ( "invalid", "'invalid' is not valid for if_sheet_exists. Valid options " - "are 'new', 'replace', 'overwrite' and 'error'.", + "are 'error', 'new' and 'replace'.", ), ( "error", From 49c4d3be5f5e614d45c63443e152c7b2d06e4b24 Mon Sep 17 00:00:00 2001 From: Mike Roberts <42875462+mrob95@users.noreply.github.com> Date: Wed, 21 Apr 2021 20:53:20 +0100 Subject: [PATCH 15/15] Remove redundant mode check --- pandas/io/excel/_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/io/excel/_base.py b/pandas/io/excel/_base.py index 1536c760cd4b8..5796d77a2d027 100644 --- a/pandas/io/excel/_base.py +++ b/pandas/io/excel/_base.py @@ -914,7 +914,7 @@ def __init__( ) if if_sheet_exists and "r+" not in mode: raise ValueError("if_sheet_exists is only valid in append mode (mode='a')") - if if_sheet_exists is None and "r+" in mode: + if if_sheet_exists is None: if_sheet_exists = "error" self.if_sheet_exists = if_sheet_exists