Skip to content

Commit ad43b7f

Browse files
FIX: pandas.ExcelFile should not close stream passed as parameter on destruction
Regression test added Bonus: refactor closing of openpyxl file into openpyxl class instead of anti-pattern check in ExcelFile.close() for openpyxl engine
1 parent edb863e commit ad43b7f

File tree

6 files changed

+37
-12
lines changed

6 files changed

+37
-12
lines changed

pandas/io/excel/_base.py

+5-8
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,10 @@ def _workbook_class(self):
366366
def load_workbook(self, filepath_or_buffer):
367367
pass
368368

369+
@abc.abstractmethod
370+
def close(self):
371+
pass
372+
369373
@property
370374
@abc.abstractmethod
371375
def sheet_names(self):
@@ -895,14 +899,7 @@ def sheet_names(self):
895899

896900
def close(self):
897901
"""close io if necessary"""
898-
if self.engine == "openpyxl":
899-
# https://stackoverflow.com/questions/31416842/
900-
# openpyxl-does-not-close-excel-workbook-in-read-only-mode
901-
wb = self.book
902-
wb._archive.close()
903-
904-
if hasattr(self.io, "close"):
905-
self.io.close()
902+
self._reader.close()
906903

907904
def __enter__(self):
908905
return self

pandas/io/excel/_odfreader.py

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ def load_workbook(self, filepath_or_buffer: FilePathOrBuffer):
3333

3434
return load(filepath_or_buffer)
3535

36+
def close(self):
37+
pass
38+
3639
@property
3740
def empty_value(self) -> str:
3841
"""Property for compat with other readers."""

pandas/io/excel/_openpyxl.py

+5
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,11 @@ def load_workbook(self, filepath_or_buffer: FilePathOrBuffer):
492492
filepath_or_buffer, read_only=True, data_only=True, keep_links=False
493493
)
494494

495+
def close(self):
496+
# https://stackoverflow.com/questions/31416842/
497+
# openpyxl-does-not-close-excel-workbook-in-read-only-mode
498+
self.book.close()
499+
495500
@property
496501
def sheet_names(self) -> List[str]:
497502
return self.book.sheetnames

pandas/io/excel/_pyxlsb.py

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def load_workbook(self, filepath_or_buffer: FilePathOrBuffer):
3636

3737
return open_workbook(filepath_or_buffer)
3838

39+
def close(self):
40+
pass
41+
3942
@property
4043
def sheet_names(self) -> List[str]:
4144
return self.book.sheets

pandas/io/excel/_xlrd.py

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ def load_workbook(self, filepath_or_buffer):
3636
else:
3737
return open_workbook(filepath_or_buffer)
3838

39+
def close(self):
40+
pass
41+
3942
@property
4043
def sheet_names(self):
4144
return self.book.sheet_names()

pandas/tests/io/excel/test_readers.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,20 @@ def test_read_from_py_localpath(self, read_ext):
629629

630630
tm.assert_frame_equal(expected, actual)
631631

632+
@td.skip_if_no("py.path")
633+
@td.check_file_leaks
634+
def test_close_from_py_localpath(self, read_ext):
635+
636+
# GH31467
637+
from py.path import local as LocalPath
638+
639+
str_path = os.path.join("test1" + read_ext)
640+
with open(str_path, 'rb') as f:
641+
x = pd.read_excel(f, "Sheet1", index_col=0)
642+
del x
643+
# should not throw an exception because the passed file was closed
644+
f.read()
645+
632646
def test_reader_seconds(self, read_ext):
633647
if pd.read_excel.keywords["engine"] == "pyxlsb":
634648
pytest.xfail("Sheets containing datetimes not supported by pyxlsb")
@@ -1020,10 +1034,10 @@ def test_excel_read_buffer(self, engine, read_ext):
10201034
tm.assert_frame_equal(expected, actual)
10211035

10221036
def test_reader_closes_file(self, engine, read_ext):
1023-
f = open("test1" + read_ext, "rb")
1024-
with pd.ExcelFile(f) as xlsx:
1025-
# parses okay
1026-
pd.read_excel(xlsx, "Sheet1", index_col=0, engine=engine)
1037+
with open("test1" + read_ext, "rb") as f:
1038+
with pd.ExcelFile(f) as xlsx:
1039+
# parses okay
1040+
pd.read_excel(xlsx, "Sheet1", index_col=0, engine=engine)
10271041

10281042
assert f.closed
10291043

0 commit comments

Comments
 (0)