Skip to content

Commit a189e8a

Browse files
authored
Fixes for Dataset.to_netcdf (#1368)
Fixes GH1320 (writing to file-like objects) Fixed GH1321 (explicitly setting ``engine='scipy'``)
1 parent 1f30b29 commit a189e8a

File tree

4 files changed

+63
-20
lines changed

4 files changed

+63
-20
lines changed

doc/whats-new.rst

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,20 @@ Enhancements
2626
data in distributed memory (:issue:`1344`).
2727
By `Matthew Rocklin <https://github.com/mrocklin>`_.
2828

29+
- New :py:meth:`~xarray.DataArray.expand_dims` method for ``DataArray`` and
30+
``Dataset`` (:issue:`1326`).
31+
By `Keisuke Fujii <https://github.com/fujiisoup>`_.
32+
2933
Bug fixes
3034
~~~~~~~~~
3135

36+
- Fixed writing to file-like objects with :py:meth:`~xarray.Dataset.to_netcdf`
37+
(:issue:`1320`).
38+
`Stephan Hoyer <https://github.com/shoyer>`_.
39+
- Fixed explicitly setting ``engine='scipy'`` with ``to_netcdf`` when not
40+
providing a path (:issue:`1321`).
41+
`Stephan Hoyer <https://github.com/shoyer>`_.
42+
3243
- Fixed open_dataarray does not pass properly its parameters to open_dataset
3344
(:issue:`1359`).
3445
`Stephan Hoyer <https://github.com/shoyer>`_.
@@ -47,8 +58,6 @@ The minor release includes bug-fixes and backwards compatible enhancements.
4758

4859
Enhancements
4960
~~~~~~~~~~~~
50-
- `expand_dims` on DataArray is newly supported (:issue:`1326`)
51-
By `Keisuke Fujii <https://github.com/fujiisoup>`_.
5261

5362
- ``rolling`` on Dataset is now supported (:issue:`859`).
5463

xarray/backends/api.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ def open_mfdataset(paths, chunks=None, concat_dim=_CONCAT_DIM_DEFAULT,
533533
'h5netcdf': backends.H5NetCDFStore}
534534

535535

536-
def to_netcdf(dataset, path=None, mode='w', format=None, group=None,
536+
def to_netcdf(dataset, path_or_file=None, mode='w', format=None, group=None,
537537
engine=None, writer=None, encoding=None, unlimited_dims=None):
538538
"""This function creates an appropriate datastore for writing a dataset to
539539
disk as a netCDF file
@@ -544,18 +544,19 @@ def to_netcdf(dataset, path=None, mode='w', format=None, group=None,
544544
"""
545545
if encoding is None:
546546
encoding = {}
547-
if path is None:
548-
path = BytesIO()
547+
if path_or_file is None:
549548
if engine is None:
550549
engine = 'scipy'
551-
elif engine is not None:
550+
elif engine != 'scipy':
552551
raise ValueError('invalid engine for creating bytes with '
553552
'to_netcdf: %r. Only the default engine '
554553
"or engine='scipy' is supported" % engine)
555-
else:
554+
elif isinstance(path_or_file, basestring):
556555
if engine is None:
557-
engine = _get_default_engine(path)
558-
path = _normalize_path(path)
556+
engine = _get_default_engine(path_or_file)
557+
path_or_file = _normalize_path(path_or_file)
558+
else: # file-like object
559+
engine = 'scipy'
559560

560561
# validate Dataset keys, DataArray names, and attr keys/values
561562
_validate_dataset_names(dataset)
@@ -572,17 +573,18 @@ def to_netcdf(dataset, path=None, mode='w', format=None, group=None,
572573
# if a writer is provided, store asynchronously
573574
sync = writer is None
574575

575-
store = store_cls(path, mode, format, group, writer)
576+
target = path_or_file if path_or_file is not None else BytesIO()
577+
store = store_cls(target, mode, format, group, writer)
576578

577579
if unlimited_dims is None:
578580
unlimited_dims = dataset.encoding.get('unlimited_dims', None)
579581
try:
580582
dataset.dump_to_store(store, sync=sync, encoding=encoding,
581583
unlimited_dims=unlimited_dims)
582-
if isinstance(path, BytesIO):
583-
return path.getvalue()
584+
if path_or_file is None:
585+
return target.getvalue()
584586
finally:
585-
if sync:
587+
if sync and isinstance(path_or_file, basestring):
586588
store.close()
587589

588590
if not sync:

xarray/core/dataset.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,7 @@ def dump_to_store(self, store, encoder=None, sync=True, encoding=None,
921921
store.store(variables, attrs, check_encoding,
922922
unlimited_dims=unlimited_dims)
923923
if sync:
924+
print('syncing')
924925
store.sync()
925926

926927
def to_netcdf(self, path=None, mode='w', format=None, group=None,
@@ -929,11 +930,12 @@ def to_netcdf(self, path=None, mode='w', format=None, group=None,
929930
930931
Parameters
931932
----------
932-
path : str, optional
933-
Path to which to save this dataset. If no path is provided, this
934-
function returns the resulting netCDF file as a bytes object; in
935-
this case, we need to use scipy.io.netcdf, which does not support
936-
netCDF version 4 (the default format becomes NETCDF3_64BIT).
933+
path : str or file-like object, optional
934+
Path to which to save this dataset. File-like objects are only
935+
supported by the scipy engine. If no path is provided, this
936+
function returns the resulting netCDF file as bytes; in this case,
937+
we need to use scipy, which does not support netCDF version 4 (the
938+
default format becomes NETCDF3_64BIT).
937939
mode : {'w', 'a'}, optional
938940
Write ('w') or append ('a') mode. If mode='w', any existing file at
939941
this location will be overwritten.

xarray/tests/test_backends.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,10 @@ def roundtrip(self, data, save_kwargs={}, open_kwargs={},
814814
autoclose=self.autoclose, **open_kwargs) as ds:
815815
yield ds
816816

817+
def test_to_netcdf_explicit_engine(self):
818+
# regression test for GH1321
819+
Dataset({'foo': 42}).to_netcdf(engine='scipy')
820+
817821
@pytest.mark.skipif(PY2, reason='cannot pickle BytesIO on Python 2')
818822
def test_bytesio_pickle(self):
819823
data = Dataset({'foo': ('x', [1, 2, 3])})
@@ -828,7 +832,33 @@ class ScipyInMemoryDataTestAutocloseTrue(ScipyInMemoryDataTest):
828832

829833

830834
@requires_scipy
831-
class ScipyOnDiskDataTest(CFEncodedDataTest, Only32BitTypes, TestCase):
835+
class ScipyFileObjectTest(CFEncodedDataTest, Only32BitTypes, TestCase):
836+
@contextlib.contextmanager
837+
def create_store(self):
838+
fobj = BytesIO()
839+
yield backends.ScipyDataStore(fobj, 'w')
840+
841+
@contextlib.contextmanager
842+
def roundtrip(self, data, save_kwargs={}, open_kwargs={},
843+
allow_cleanup_failure=False):
844+
with create_tmp_file() as tmp_file:
845+
with open(tmp_file, 'wb') as f:
846+
data.to_netcdf(f, **save_kwargs)
847+
with open(tmp_file, 'rb') as f:
848+
with open_dataset(f, engine='scipy', **open_kwargs) as ds:
849+
yield ds
850+
851+
@pytest.mark.skip(reason='cannot pickle file objects')
852+
def test_pickle(self):
853+
pass
854+
855+
@pytest.mark.skip(reason='cannot pickle file objects')
856+
def test_pickle_dataarray(self):
857+
pass
858+
859+
860+
@requires_scipy
861+
class ScipyFilePathTest(CFEncodedDataTest, Only32BitTypes, TestCase):
832862
@contextlib.contextmanager
833863
def create_store(self):
834864
with create_tmp_file() as tmp_file:
@@ -868,7 +898,7 @@ def test_netcdf3_endianness(self):
868898
self.assertTrue(var.dtype.isnative)
869899

870900

871-
class ScipyOnDiskDataTestAutocloseTrue(ScipyOnDiskDataTest):
901+
class ScipyFilePathTestAutocloseTrue(ScipyFilePathTest):
872902
autoclose = True
873903

874904

0 commit comments

Comments
 (0)