Skip to content

Commit a5eb94d

Browse files
authored
BUG/CLN: Minimize number of ResourceWarnings (pandas-dev#38168)
1 parent 03771a2 commit a5eb94d

File tree

9 files changed

+71
-61
lines changed

9 files changed

+71
-61
lines changed

pandas/io/excel/_base.py

+3
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,9 @@ class ExcelWriter(metaclass=abc.ABCMeta):
553553
Default is to use xlwt for xls, openpyxl for xlsx, odf for ods.
554554
See DataFrame.to_excel for typical usage.
555555
556+
The writer should be used as a context manager. Otherwise, call `close()` to save
557+
and close any opened file handles.
558+
556559
Parameters
557560
----------
558561
path : str or typing.BinaryIO

pandas/io/excel/_xlwt.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ def save(self):
4545
"""
4646
Save workbook to disk.
4747
"""
48-
self.book.save(self.handles.handle)
48+
if self.sheets:
49+
# fails when the ExcelWriter is just opened and then closed
50+
self.book.save(self.handles.handle)
4951

5052
def write_cells(
5153
self, cells, sheet_name=None, startrow=0, startcol=0, freeze_panes=None

pandas/io/parsers.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1881,7 +1881,11 @@ def __init__(self, src: FilePathOrBuffer, **kwds):
18811881
# no attribute "mmap" [union-attr]
18821882
self.handles.handle = self.handles.handle.mmap # type: ignore[union-attr]
18831883

1884-
self._reader = parsers.TextReader(self.handles.handle, **kwds)
1884+
try:
1885+
self._reader = parsers.TextReader(self.handles.handle, **kwds)
1886+
except Exception:
1887+
self.handles.close()
1888+
raise
18851889
self.unnamed_cols = self._reader.unnamed_cols
18861890

18871891
passed_names = self.names is None

pandas/io/sas/sasreader.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from pandas._typing import FilePathOrBuffer, Label
88

9-
from pandas.io.common import stringify_path
9+
from pandas.io.common import IOHandles, stringify_path
1010

1111
if TYPE_CHECKING:
1212
from pandas import DataFrame
@@ -18,6 +18,8 @@ class ReaderBase(metaclass=ABCMeta):
1818
Protocol for XportReader and SAS7BDATReader classes.
1919
"""
2020

21+
handles: IOHandles
22+
2123
@abstractmethod
2224
def read(self, nrows=None):
2325
pass
@@ -134,4 +136,5 @@ def read_sas(
134136
if iterator or chunksize:
135137
return reader
136138

137-
return reader.read()
139+
with reader.handles:
140+
return reader.read()

pandas/tests/io/excel/test_openpyxl.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,11 @@ def test_write_cells_merge_styled(ext):
6868
]
6969

7070
with tm.ensure_clean(ext) as path:
71-
writer = _OpenpyxlWriter(path)
72-
writer.write_cells(initial_cells, sheet_name=sheet_name)
73-
writer.write_cells(merge_cells, sheet_name=sheet_name)
71+
with _OpenpyxlWriter(path) as writer:
72+
writer.write_cells(initial_cells, sheet_name=sheet_name)
73+
writer.write_cells(merge_cells, sheet_name=sheet_name)
7474

75-
wks = writer.sheets[sheet_name]
75+
wks = writer.sheets[sheet_name]
7676
xcell_b1 = wks["B1"]
7777
xcell_a2 = wks["A2"]
7878
assert xcell_b1.font == openpyxl_sty_merged
@@ -93,9 +93,8 @@ def test_write_append_mode(ext, mode, expected):
9393
wb.worksheets[1]["A1"].value = "bar"
9494
wb.save(f)
9595

96-
writer = ExcelWriter(f, engine="openpyxl", mode=mode)
97-
df.to_excel(writer, sheet_name="baz", index=False)
98-
writer.save()
96+
with ExcelWriter(f, engine="openpyxl", mode=mode) as writer:
97+
df.to_excel(writer, sheet_name="baz", index=False)
9998

10099
wb2 = openpyxl.load_workbook(f)
101100
result = [sheet.title for sheet in wb2.worksheets]

pandas/tests/io/excel/test_style.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,14 @@ def custom_converter(css):
6868

6969
df = DataFrame(np.random.randn(11, 3))
7070
with tm.ensure_clean(".xlsx" if engine != "xlwt" else ".xls") as path:
71-
writer = ExcelWriter(path, engine=engine)
72-
df.to_excel(writer, sheet_name="frame")
73-
df.style.to_excel(writer, sheet_name="unstyled")
74-
styled = df.style.apply(style, axis=None)
75-
styled.to_excel(writer, sheet_name="styled")
76-
ExcelFormatter(styled, style_converter=custom_converter).write(
77-
writer, sheet_name="custom"
78-
)
79-
writer.save()
71+
with ExcelWriter(path, engine=engine) as writer:
72+
df.to_excel(writer, sheet_name="frame")
73+
df.style.to_excel(writer, sheet_name="unstyled")
74+
styled = df.style.apply(style, axis=None)
75+
styled.to_excel(writer, sheet_name="styled")
76+
ExcelFormatter(styled, style_converter=custom_converter).write(
77+
writer, sheet_name="custom"
78+
)
8079

8180
if engine not in ("openpyxl", "xlsxwriter"):
8281
# For other engines, we only smoke test

pandas/tests/io/excel/test_writers.py

+21-23
Original file line numberDiff line numberDiff line change
@@ -522,10 +522,9 @@ def test_sheets(self, frame, tsframe, path):
522522
frame.to_excel(path, "test1", index=False)
523523

524524
# Test writing to separate sheets
525-
writer = ExcelWriter(path)
526-
frame.to_excel(writer, "test1")
527-
tsframe.to_excel(writer, "test2")
528-
writer.close()
525+
with ExcelWriter(path) as writer:
526+
frame.to_excel(writer, "test1")
527+
tsframe.to_excel(writer, "test2")
529528
reader = ExcelFile(path)
530529
recons = pd.read_excel(reader, sheet_name="test1", index_col=0)
531530
tm.assert_frame_equal(frame, recons)
@@ -1199,17 +1198,16 @@ def test_datetimes(self, path):
11991198

12001199
def test_bytes_io(self, engine):
12011200
# see gh-7074
1202-
bio = BytesIO()
1203-
df = DataFrame(np.random.randn(10, 2))
1201+
with BytesIO() as bio:
1202+
df = DataFrame(np.random.randn(10, 2))
12041203

1205-
# Pass engine explicitly, as there is no file path to infer from.
1206-
writer = ExcelWriter(bio, engine=engine)
1207-
df.to_excel(writer)
1208-
writer.save()
1204+
# Pass engine explicitly, as there is no file path to infer from.
1205+
with ExcelWriter(bio, engine=engine) as writer:
1206+
df.to_excel(writer)
12091207

1210-
bio.seek(0)
1211-
reread_df = pd.read_excel(bio, index_col=0)
1212-
tm.assert_frame_equal(df, reread_df)
1208+
bio.seek(0)
1209+
reread_df = pd.read_excel(bio, index_col=0)
1210+
tm.assert_frame_equal(df, reread_df)
12131211

12141212
def test_write_lists_dict(self, path):
12151213
# see gh-8188.
@@ -1317,12 +1315,12 @@ class TestExcelWriterEngineTests:
13171315
)
13181316
def test_ExcelWriter_dispatch(self, klass, ext):
13191317
with tm.ensure_clean(ext) as path:
1320-
writer = ExcelWriter(path)
1321-
if ext == ".xlsx" and td.safe_import("xlsxwriter"):
1322-
# xlsxwriter has preference over openpyxl if both installed
1323-
assert isinstance(writer, _XlsxWriter)
1324-
else:
1325-
assert isinstance(writer, klass)
1318+
with ExcelWriter(path) as writer:
1319+
if ext == ".xlsx" and td.safe_import("xlsxwriter"):
1320+
# xlsxwriter has preference over openpyxl if both installed
1321+
assert isinstance(writer, _XlsxWriter)
1322+
else:
1323+
assert isinstance(writer, klass)
13261324

13271325
def test_ExcelWriter_dispatch_raises(self):
13281326
with pytest.raises(ValueError, match="No engine"):
@@ -1356,8 +1354,8 @@ def check_called(func):
13561354
path = "something.xlsx"
13571355
with tm.ensure_clean(path) as filepath:
13581356
register_writer(DummyClass)
1359-
writer = ExcelWriter(filepath)
1360-
assert isinstance(writer, DummyClass)
1357+
with ExcelWriter(filepath) as writer:
1358+
assert isinstance(writer, DummyClass)
13611359
df = tm.makeCustomDataframe(1, 1)
13621360
check_called(lambda: df.to_excel(filepath))
13631361
with tm.ensure_clean("something.xls") as filepath:
@@ -1377,5 +1375,5 @@ def test_excelfile_fspath(self):
13771375

13781376
def test_excelwriter_fspath(self):
13791377
with tm.ensure_clean("foo.xlsx") as path:
1380-
writer = ExcelWriter(path)
1381-
assert os.fspath(writer) == str(path)
1378+
with ExcelWriter(path) as writer:
1379+
assert os.fspath(writer) == str(path)

pandas/tests/io/excel/test_xlsxwriter.py

+9-10
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,15 @@ def test_column_format(ext):
2323
with tm.ensure_clean(ext) as path:
2424
frame = DataFrame({"A": [123456, 123456], "B": [123456, 123456]})
2525

26-
writer = ExcelWriter(path)
27-
frame.to_excel(writer)
28-
29-
# Add a number format to col B and ensure it is applied to cells.
30-
num_format = "#,##0"
31-
write_workbook = writer.book
32-
write_worksheet = write_workbook.worksheets()[0]
33-
col_format = write_workbook.add_format({"num_format": num_format})
34-
write_worksheet.set_column("B:B", None, col_format)
35-
writer.save()
26+
with ExcelWriter(path) as writer:
27+
frame.to_excel(writer)
28+
29+
# Add a number format to col B and ensure it is applied to cells.
30+
num_format = "#,##0"
31+
write_workbook = writer.book
32+
write_worksheet = write_workbook.worksheets()[0]
33+
col_format = write_workbook.add_format({"num_format": num_format})
34+
write_worksheet.set_column("B:B", None, col_format)
3635

3736
read_workbook = openpyxl.load_workbook(path)
3837
try:

pandas/tests/io/parser/test_multi_thread.py

+11-8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Tests multithreading behaviour for reading and
33
parsing files for each parser defined in parsers.py
44
"""
5+
from contextlib import ExitStack
56
from io import BytesIO
67
from multiprocessing.pool import ThreadPool
78

@@ -46,16 +47,18 @@ def test_multi_thread_string_io_read_csv(all_parsers):
4647
"\n".join([f"{i:d},{i:d},{i:d}" for i in range(max_row_range)]).encode()
4748
for _ in range(num_files)
4849
]
49-
files = [BytesIO(b) for b in bytes_to_df]
5050

5151
# Read all files in many threads.
52-
pool = ThreadPool(8)
52+
with ExitStack() as stack:
53+
files = [stack.enter_context(BytesIO(b)) for b in bytes_to_df]
5354

54-
results = pool.map(parser.read_csv, files)
55-
first_result = results[0]
55+
pool = stack.enter_context(ThreadPool(8))
5656

57-
for result in results:
58-
tm.assert_frame_equal(first_result, result)
57+
results = pool.map(parser.read_csv, files)
58+
first_result = results[0]
59+
60+
for result in results:
61+
tm.assert_frame_equal(first_result, result)
5962

6063

6164
def _generate_multi_thread_dataframe(parser, path, num_rows, num_tasks):
@@ -116,8 +119,8 @@ def reader(arg):
116119
(num_rows * i // num_tasks, num_rows // num_tasks) for i in range(num_tasks)
117120
]
118121

119-
pool = ThreadPool(processes=num_tasks)
120-
results = pool.map(reader, tasks)
122+
with ThreadPool(processes=num_tasks) as pool:
123+
results = pool.map(reader, tasks)
121124

122125
header = results[0].columns
123126

0 commit comments

Comments
 (0)