Skip to content

Commit f267148

Browse files
committed
fix test failures
1 parent dfc651d commit f267148

File tree

4 files changed

+45
-42
lines changed

4 files changed

+45
-42
lines changed

doc/whats-new.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,16 @@ Bug fixes
3434
~~~~~~~~~
3535

3636
- Xarray objects opened from file-like objects with ``engine='h5netcdf'`` can
37-
now be pickled, as long as the underlying file-like object also support
37+
now be pickled, as long as the underlying file-like object also supports
3838
pickle
39-
(:pull:`10624`).
39+
(:issue:`10712`).
4040
By `Stephan Hoyer <https://github.com/shoyer>`_.
41-
- Closing Xarray objects opened from file-like objects with ```engine='scipy'``
41+
- Closing Xarray objects opened from file-like objects with ```engine='scipy'``
4242
no longer closes the underlying file, consistent the h5netcdf backend
4343
(:pull:`10624`).
4444
By `Stephan Hoyer <https://github.com/shoyer>`_.
4545

4646

47-
4847
Documentation
4948
~~~~~~~~~~~~~
5049

xarray/backends/locks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def _get_threaded_lock(key: str) -> threading.Lock:
100100
return lock
101101

102102

103-
def _get_multiprocessing_lock(key: str) -> multiprocessing.Lock:
103+
def _get_multiprocessing_lock(key: str) -> Lock:
104104
# TODO: make use of the key -- maybe use locket.py?
105105
# https://github.com/mwilliamson/locket.py
106106
del key # unused

xarray/backends/scipy_.py

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,6 @@
3535
)
3636
from xarray.core.variable import Variable
3737

38-
try:
39-
from scipy.io import netcdf_file as netcdf_file_base
40-
except ImportError:
41-
netcdf_file_base = object
42-
43-
4438
if TYPE_CHECKING:
4539
import scipy.io
4640

@@ -110,30 +104,31 @@ def __setitem__(self, key, value):
110104
raise
111105

112106

113-
class flush_only_netcdf_file(netcdf_file_base):
114-
# scipy.io.netcdf_file.close() incorrectly closes file objects that
115-
# were passed in as constructor arguments:
116-
# https://github.com/scipy/scipy/issues/13905
117-
118-
# Instead of closing such files, only call flush(), which is
119-
# equivalent as long as the netcdf_file object is not mmapped.
120-
# This suffices to keep BytesIO objects open long enough to read
121-
# their contents from to_netcdf(), but underlying files still get
122-
# closed when the netcdf_file is garbage collected (via __del__),
123-
# and will need to be fixed upstream in scipy.
124-
def close(self):
125-
if hasattr(self, "fp") and not self.fp.closed:
126-
self.flush()
127-
128-
def __del__(self):
129-
# Remove the __del__ method, which in scipy is aliased to close().
130-
# These files need to be closed explicitly by xarray.
131-
pass
132-
133-
134107
def _open_scipy_netcdf(filename, mode, mmap, version, flush_only=False):
135108
import scipy.io
136109

110+
# define inside a helper function to ensure the scipy import is lazy
111+
class flush_only_netcdf_file(scipy.io.netcdf_file):
112+
# scipy.io.netcdf_file.close() incorrectly closes file objects that
113+
# were passed in as constructor arguments:
114+
# https://github.com/scipy/scipy/issues/13905
115+
116+
# Instead of closing such files, only call flush(), which is
117+
# equivalent as long as the netcdf_file object is not mmapped.
118+
# This suffices to keep BytesIO objects open long enough to read
119+
# their contents from to_netcdf(), but underlying files still get
120+
# closed when the netcdf_file is garbage collected (via __del__),
121+
# and will need to be fixed upstream in scipy.
122+
def close(self):
123+
if hasattr(self, "fp") and not self.fp.closed:
124+
self.flush()
125+
self.fp.seek(0) # allow file to be read again
126+
127+
def __del__(self):
128+
# Remove the __del__ method, which in scipy is aliased to close().
129+
# These files need to be closed explicitly by xarray.
130+
pass
131+
137132
netcdf_file = flush_only_netcdf_file if flush_only else scipy.io.netcdf_file
138133

139134
# if the string ends with .gz, then gunzip and open as netcdf file

xarray/tests/test_backends.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,12 @@
116116
with contextlib.suppress(ImportError):
117117
import netCDF4 as nc4
118118

119-
try:
119+
with contextlib.suppress(ImportError):
120120
import dask
121121
import dask.array as da
122-
except ImportError:
123-
pass
124122

123+
with contextlib.suppress(ImportError):
124+
import fsspec
125125

126126
if has_zarr:
127127
import zarr
@@ -633,16 +633,13 @@ def test_pickle(self) -> None:
633633
with pickle.loads(raw_pickle) as unpickled_ds:
634634
assert_identical(expected, unpickled_ds)
635635

636-
@pytest.mark.filterwarnings("ignore:deallocating CachingFileManager")
637636
def test_pickle_dataarray(self) -> None:
638637
expected = Dataset({"foo": ("x", [42])})
639638
with self.roundtrip(expected, allow_cleanup_failure=ON_WINDOWS) as roundtripped:
640639
with roundtripped:
641640
raw_pickle = pickle.dumps(roundtripped["foo"])
642-
# TODO: figure out how to explicitly close the file for the
643-
# unpickled DataArray?
644-
unpickled = pickle.loads(raw_pickle)
645-
assert_identical(expected["foo"], unpickled)
641+
with pickle.loads(raw_pickle) as unpickled:
642+
assert_identical(expected["foo"], unpickled)
646643

647644
def test_dataset_caching(self) -> None:
648645
expected = Dataset({"foo": ("x", [5, 6, 7])})
@@ -658,7 +655,6 @@ def test_dataset_caching(self) -> None:
658655
_ = actual.foo.values # no caching
659656
assert not actual.foo.variable._in_memory
660657

661-
@pytest.mark.filterwarnings("ignore:deallocating CachingFileManager")
662658
def test_roundtrip_None_variable(self) -> None:
663659
expected = Dataset({None: (("x", "y"), [[0, 1], [2, 3]])})
664660
with self.roundtrip(expected) as actual:
@@ -5113,7 +5109,6 @@ def test_open_badbytes(self) -> None:
51135109

51145110
def test_open_twice(self) -> None:
51155111
expected = create_test_data()
5116-
expected.attrs["foo"] = "bar"
51175112
with create_tmp_file() as tmp_file:
51185113
expected.to_netcdf(tmp_file, engine=self.engine)
51195114
with open(tmp_file, "rb") as f:
@@ -5154,6 +5149,20 @@ def test_open_fileobj(self) -> None:
51545149
with open_dataset(f): # ensure file gets closed
51555150
pass
51565151

5152+
@requires_fsspec
5153+
def test_fsspec(self) -> None:
5154+
expected = create_test_data()
5155+
with create_tmp_file() as tmp_file:
5156+
expected.to_netcdf(tmp_file, engine="h5netcdf")
5157+
5158+
with fsspec.open(tmp_file, "rb") as f:
5159+
with open_dataset(f, engine="h5netcdf") as actual:
5160+
assert_identical(actual, expected)
5161+
5162+
# fsspec.open() creates a pickleable file, unlike open()
5163+
with pickle.loads(pickle.dumps(actual)) as unpickled:
5164+
assert_identical(unpickled, expected)
5165+
51575166

51585167
@requires_h5netcdf
51595168
class TestH5NetCDFInMemoryData(InMemoryNetCDFWithGroups):

0 commit comments

Comments
 (0)