Skip to content

Commit 087f464

Browse files
puneetdixit200Deepak kudi
authored andcommitted
Preserve lazy scipy import for pickle workaround
1 parent ddd75a3 commit 087f464

1 file changed

Lines changed: 42 additions & 25 deletions

File tree

xarray/backends/scipy_.py

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@
88

99
import numpy as np
1010

11-
try:
12-
from scipy.io import netcdf_file as netcdf_file_base
13-
except ImportError:
14-
netcdf_file_base = object # type: ignore[assignment,misc,unused-ignore] # scipy is optional
15-
1611
from xarray.backends.common import (
1712
BACKEND_ENTRYPOINTS,
1813
BackendArray,
@@ -54,6 +49,7 @@
5449
V = TypeVar("V")
5550

5651
HAS_NUMPY_2_0 = module_available("numpy", minversion="2.0.0.dev0")
52+
_FLUSH_ONLY_NETCDF_FILE: type[scipy.io.netcdf_file] | None = None
5753

5854

5955
@overload
@@ -127,27 +123,48 @@ def __setitem__(self, key, value):
127123
raise
128124

129125

130-
# TODO: Make the scipy import lazy again after upstreaming these fixes.
131-
class flush_only_netcdf_file(netcdf_file_base):
132-
# scipy.io.netcdf_file.close() incorrectly closes file objects that
133-
# were passed in as constructor arguments:
134-
# https://github.com/scipy/scipy/issues/13905
126+
def _get_flush_only_netcdf_file() -> type[scipy.io.netcdf_file]:
127+
global _FLUSH_ONLY_NETCDF_FILE
128+
129+
if _FLUSH_ONLY_NETCDF_FILE is None:
130+
import scipy.io
131+
132+
# TODO: Remove this after upstreaming these fixes.
133+
class flush_only_netcdf_file(scipy.io.netcdf_file):
134+
# scipy.io.netcdf_file.close() incorrectly closes file objects that
135+
# were passed in as constructor arguments:
136+
# https://github.com/scipy/scipy/issues/13905
137+
138+
# Instead of closing such files, only call flush(), which is
139+
# equivalent as long as the netcdf_file object is not mmapped.
140+
# This suffices to keep BytesIO objects open long enough to read
141+
# their contents from to_netcdf(), but underlying files still get
142+
# closed when the netcdf_file is garbage collected (via __del__),
143+
# and will need to be fixed upstream in scipy.
144+
def close(self):
145+
if hasattr(self, "fp") and not self.fp.closed:
146+
self.flush()
147+
self.fp.seek(0) # allow file to be read again
148+
149+
def __del__(self):
150+
# Remove the __del__ method, which in scipy is aliased to close().
151+
# These files need to be closed explicitly by xarray.
152+
pass
153+
154+
flush_only_netcdf_file.__module__ = __name__
155+
flush_only_netcdf_file.__qualname__ = flush_only_netcdf_file.__name__
156+
_FLUSH_ONLY_NETCDF_FILE = flush_only_netcdf_file
157+
globals()[flush_only_netcdf_file.__name__] = flush_only_netcdf_file
158+
159+
assert _FLUSH_ONLY_NETCDF_FILE is not None
160+
return _FLUSH_ONLY_NETCDF_FILE
161+
135162

136-
# Instead of closing such files, only call flush(), which is
137-
# equivalent as long as the netcdf_file object is not mmapped.
138-
# This suffices to keep BytesIO objects open long enough to read
139-
# their contents from to_netcdf(), but underlying files still get
140-
# closed when the netcdf_file is garbage collected (via __del__),
141-
# and will need to be fixed upstream in scipy.
142-
def close(self):
143-
if hasattr(self, "fp") and not self.fp.closed:
144-
self.flush()
145-
self.fp.seek(0) # allow file to be read again
163+
def __getattr__(name: str) -> Any:
164+
if name == "flush_only_netcdf_file":
165+
return _get_flush_only_netcdf_file()
146166

147-
def __del__(self):
148-
# Remove the __del__ method, which in scipy is aliased to close().
149-
# These files need to be closed explicitly by xarray.
150-
pass
167+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
151168

152169

153170
def _open_scipy_netcdf(
@@ -159,7 +176,7 @@ def _open_scipy_netcdf(
159176
) -> scipy.io.netcdf_file:
160177
import scipy.io
161178

162-
netcdf_file = flush_only_netcdf_file if flush_only else scipy.io.netcdf_file
179+
netcdf_file = _get_flush_only_netcdf_file() if flush_only else scipy.io.netcdf_file
163180

164181
# if the string ends with .gz, then gunzip and open as netcdf file
165182
if isinstance(filename, str) and filename.endswith(".gz"):

0 commit comments

Comments
 (0)