88
99import 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-
1611from xarray .backends .common import (
1712 BACKEND_ENTRYPOINTS ,
1813 BackendArray ,
5449V = TypeVar ("V" )
5550
5651HAS_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
153170def _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