1111 Sequence ,
1212)
1313from functools import partial
14- from io import BytesIO
14+ from io import IOBase
1515from itertools import starmap
1616from numbers import Number
1717from typing import (
3131from xarray .backends .common import (
3232 AbstractDataStore ,
3333 ArrayWriter ,
34+ BytesIOProxy ,
35+ T_PathFileOrDataStore ,
3436 _find_absolute_paths ,
3537 _normalize_path ,
3638)
@@ -503,7 +505,7 @@ def _datatree_from_backend_datatree(
503505
504506
505507def open_dataset (
506- filename_or_obj : str | os . PathLike [ Any ] | ReadBuffer | AbstractDataStore ,
508+ filename_or_obj : T_PathFileOrDataStore ,
507509 * ,
508510 engine : T_Engine = None ,
509511 chunks : T_Chunks = None ,
@@ -533,12 +535,13 @@ def open_dataset(
533535
534536 Parameters
535537 ----------
536- filename_or_obj : str, Path, file-like or DataStore
538+ filename_or_obj : str, Path, file-like, bytes, memoryview or DataStore
537539 Strings and Path objects are interpreted as a path to a netCDF file
538540 or an OpenDAP URL and opened with python-netCDF4, unless the filename
539541 ends with .gz, in which case the file is gunzipped and opened with
540- scipy.io.netcdf (only netCDF3 supported). Byte-strings or file-like
541- objects are opened by scipy.io.netcdf (netCDF3) or h5py (netCDF4/HDF).
542+ scipy.io.netcdf (only netCDF3 supported). Bytes, memoryview and
543+ file-like objects are opened by scipy.io.netcdf (netCDF3) or h5netcdf
544+ (netCDF4).
542545 engine : {"netcdf4", "scipy", "pydap", "h5netcdf", "zarr", None}\
543546 , installed backend \
544547 or subclass of xarray.backends.BackendEntrypoint, optional
@@ -743,7 +746,7 @@ def open_dataset(
743746
744747
745748def open_dataarray (
746- filename_or_obj : str | os . PathLike [ Any ] | ReadBuffer | AbstractDataStore ,
749+ filename_or_obj : T_PathFileOrDataStore ,
747750 * ,
748751 engine : T_Engine = None ,
749752 chunks : T_Chunks = None ,
@@ -774,12 +777,13 @@ def open_dataarray(
774777
775778 Parameters
776779 ----------
777- filename_or_obj : str, Path, file-like or DataStore
780+ filename_or_obj : str, Path, file-like, bytes, memoryview or DataStore
778781 Strings and Path objects are interpreted as a path to a netCDF file
779782 or an OpenDAP URL and opened with python-netCDF4, unless the filename
780783 ends with .gz, in which case the file is gunzipped and opened with
781- scipy.io.netcdf (only netCDF3 supported). Byte-strings or file-like
782- objects are opened by scipy.io.netcdf (netCDF3) or h5py (netCDF4/HDF).
784+ scipy.io.netcdf (only netCDF3 supported). Bytes, memoryview and
785+ file-like objects are opened by scipy.io.netcdf (netCDF3) or h5netcdf
786+ (netCDF4).
783787 engine : {"netcdf4", "scipy", "pydap", "h5netcdf", "zarr", None}\
784788 , installed backend \
785789 or subclass of xarray.backends.BackendEntrypoint, optional
@@ -970,7 +974,7 @@ def open_dataarray(
970974
971975
972976def open_datatree (
973- filename_or_obj : str | os . PathLike [ Any ] | ReadBuffer | AbstractDataStore ,
977+ filename_or_obj : T_PathFileOrDataStore ,
974978 * ,
975979 engine : T_Engine = None ,
976980 chunks : T_Chunks = None ,
@@ -1001,8 +1005,10 @@ def open_datatree(
10011005
10021006 Parameters
10031007 ----------
1004- filename_or_obj : str, Path, file-like, or DataStore
1005- Strings and Path objects are interpreted as a path to a netCDF file or Zarr store.
1008+ filename_or_obj : str, Path, file-like, bytes or DataStore
1009+ Strings and Path objects are interpreted as a path to a netCDF file or
1010+ Zarr store. Bytes and memoryview objects are interpreted as file
1011+ contents.
10061012 engine : {"netcdf4", "h5netcdf", "zarr", None}, \
10071013 installed backend or xarray.backends.BackendEntrypoint, optional
10081014 Engine to use when reading files. If not provided, the default engine
@@ -1208,7 +1214,7 @@ def open_datatree(
12081214
12091215
12101216def open_groups (
1211- filename_or_obj : str | os . PathLike [ Any ] | ReadBuffer | AbstractDataStore ,
1217+ filename_or_obj : T_PathFileOrDataStore ,
12121218 * ,
12131219 engine : T_Engine = None ,
12141220 chunks : T_Chunks = None ,
@@ -1243,8 +1249,10 @@ def open_groups(
12431249
12441250 Parameters
12451251 ----------
1246- filename_or_obj : str, Path, file-like, or DataStore
1247- Strings and Path objects are interpreted as a path to a netCDF file or Zarr store.
1252+ filename_or_obj : str, Path, file-like, bytes, memoryview or DataStore
1253+ Strings and Path objects are interpreted as a path to a netCDF file or
1254+ Zarr store. Bytes and memoryview objects are interpreted as file
1255+ contents.
12481256 engine : {"netcdf4", "h5netcdf", "zarr", None}, \
12491257 installed backend or xarray.backends.BackendEntrypoint, optional
12501258 Engine to use when reading files. If not provided, the default engine
@@ -1780,7 +1788,7 @@ def to_netcdf(
17801788) -> tuple [ArrayWriter , AbstractDataStore ]: ...
17811789
17821790
1783- # path=None writes to bytes
1791+ # path=None writes to bytes or memoryview, depending on store
17841792@overload
17851793def to_netcdf (
17861794 dataset : Dataset ,
@@ -1795,7 +1803,7 @@ def to_netcdf(
17951803 multifile : Literal [False ] = False ,
17961804 invalid_netcdf : bool = False ,
17971805 auto_complex : bool | None = None ,
1798- ) -> bytes : ...
1806+ ) -> bytes | memoryview : ...
17991807
18001808
18011809# compute=False returns dask.Delayed
@@ -1821,7 +1829,7 @@ def to_netcdf(
18211829@overload
18221830def to_netcdf (
18231831 dataset : Dataset ,
1824- path_or_file : str | os .PathLike ,
1832+ path_or_file : str | os .PathLike | IOBase ,
18251833 mode : NetcdfWriteModes = "w" ,
18261834 format : T_NetcdfTypes | None = None ,
18271835 group : str | None = None ,
@@ -1877,7 +1885,7 @@ def to_netcdf(
18771885@overload
18781886def to_netcdf (
18791887 dataset : Dataset ,
1880- path_or_file : str | os .PathLike | None ,
1888+ path_or_file : str | os .PathLike | IOBase | None ,
18811889 mode : NetcdfWriteModes = "w" ,
18821890 format : T_NetcdfTypes | None = None ,
18831891 group : str | None = None ,
@@ -1888,12 +1896,12 @@ def to_netcdf(
18881896 multifile : bool = False ,
18891897 invalid_netcdf : bool = False ,
18901898 auto_complex : bool | None = None ,
1891- ) -> tuple [ArrayWriter , AbstractDataStore ] | bytes | Delayed | None : ...
1899+ ) -> tuple [ArrayWriter , AbstractDataStore ] | bytes | memoryview | Delayed | None : ...
18921900
18931901
18941902def to_netcdf (
18951903 dataset : Dataset ,
1896- path_or_file : str | os .PathLike | None = None ,
1904+ path_or_file : str | os .PathLike | IOBase | None = None ,
18971905 mode : NetcdfWriteModes = "w" ,
18981906 format : T_NetcdfTypes | None = None ,
18991907 group : str | None = None ,
@@ -1904,7 +1912,7 @@ def to_netcdf(
19041912 multifile : bool = False ,
19051913 invalid_netcdf : bool = False ,
19061914 auto_complex : bool | None = None ,
1907- ) -> tuple [ArrayWriter , AbstractDataStore ] | bytes | Delayed | None :
1915+ ) -> tuple [ArrayWriter , AbstractDataStore ] | bytes | memoryview | Delayed | None :
19081916 """This function creates an appropriate datastore for writing a dataset to
19091917 disk as a netCDF file
19101918
@@ -1918,26 +1926,27 @@ def to_netcdf(
19181926 if encoding is None :
19191927 encoding = {}
19201928
1921- if path_or_file is None :
1929+ if isinstance (path_or_file , str ):
1930+ if engine is None :
1931+ engine = _get_default_engine (path_or_file )
1932+ path_or_file = _normalize_path (path_or_file )
1933+ else :
1934+ # writing to bytes/memoryview or a file-like object
19221935 if engine is None :
1936+ # TODO: only use 'scipy' if format is None or a netCDF3 format
19231937 engine = "scipy"
1924- elif engine != "scipy" :
1938+ elif engine not in ( "scipy" , "h5netcdf" ) :
19251939 raise ValueError (
1926- "invalid engine for creating bytes with "
1927- f"to_netcdf: { engine !r} . Only the default engine "
1928- "or engine='scipy' is supported"
1940+ "invalid engine for creating bytes/memoryview or writing to a "
1941+ f"file-like object with to_netcdf: { engine !r} . Only "
1942+ "engine=None, engine='scipy' and engine='h5netcdf' is "
1943+ "supported."
19291944 )
19301945 if not compute :
19311946 raise NotImplementedError (
19321947 "to_netcdf() with compute=False is not yet implemented when "
19331948 "returning bytes"
19341949 )
1935- elif isinstance (path_or_file , str ):
1936- if engine is None :
1937- engine = _get_default_engine (path_or_file )
1938- path_or_file = _normalize_path (path_or_file )
1939- else : # file-like object
1940- engine = "scipy"
19411950
19421951 # validate Dataset keys, DataArray names, and attr keys/values
19431952 _validate_dataset_names (dataset )
@@ -1962,7 +1971,11 @@ def to_netcdf(
19621971 f"is not currently supported with dask's { scheduler } scheduler"
19631972 )
19641973
1965- target = path_or_file if path_or_file is not None else BytesIO ()
1974+ if path_or_file is None :
1975+ target = BytesIOProxy ()
1976+ else :
1977+ target = path_or_file # type: ignore[assignment]
1978+
19661979 kwargs = dict (autoclose = True ) if autoclose else {}
19671980 if invalid_netcdf :
19681981 if engine == "h5netcdf" :
@@ -2002,17 +2015,19 @@ def to_netcdf(
20022015
20032016 writes = writer .sync (compute = compute )
20042017
2005- if isinstance (target , BytesIO ):
2006- store .sync ()
2007- return target .getvalue ()
20082018 finally :
20092019 if not multifile and compute : # type: ignore[redundant-expr]
20102020 store .close ()
20112021
2022+ if path_or_file is None :
2023+ assert isinstance (target , BytesIOProxy ) # created in this function
2024+ return target .getvalue_or_getbuffer ()
2025+
20122026 if not compute :
20132027 import dask
20142028
20152029 return dask .delayed (_finalize_store )(writes , store )
2030+
20162031 return None
20172032
20182033
0 commit comments