From 94e29349e938247b1ad35b41054c9c3d64cc5cab Mon Sep 17 00:00:00 2001 From: Dan Nowacki Date: Mon, 9 Apr 2018 16:48:29 -0400 Subject: [PATCH] ENH: setncattr_string support (GH2044) --- doc/whats-new.rst | 3 +++ xarray/backends/netCDF4_.py | 29 +++++++++++++++++++++++++++-- xarray/tests/test_backends.py | 17 +++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index e4007b0a7e9..d042e1df1e9 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -34,6 +34,9 @@ v0.10.4 (unreleased) Enhancements ~~~~~~~~~~~~ +- Support writing lists of strings as netCDF attributes (:issue:`2044`). + By `Dan Nowacki `_. + Bug fixes ~~~~~~~~~ diff --git a/xarray/backends/netCDF4_.py b/xarray/backends/netCDF4_.py index 89a0e72ef07..be714082d3b 100644 --- a/xarray/backends/netCDF4_.py +++ b/xarray/backends/netCDF4_.py @@ -229,6 +229,31 @@ def _disable_auto_decode_group(ds): _disable_auto_decode_variable(var) +def _is_list_of_strings(value): + if (np.asarray(value).dtype.kind in ['U', 'S'] and + np.asarray(value).size > 1): + return True + else: + return False + + +def _set_nc_attribute(obj, key, value): + if _is_list_of_strings(value): + # encode as NC_STRING if attr is list of strings + try: + obj.setncattr_string(key, value) + except AttributeError: + # Inform users with old netCDF that does not support + # NC_STRING that we can't serialize lists of strings + # as attrs + msg = ('Attributes which are lists of strings are not ' + 'supported with this version of netCDF. Please ' + 'upgrade to netCDF4-python 1.2.4 or greater.') + raise AttributeError(msg) + else: + obj.setncattr(key, value) + + class NetCDF4DataStore(WritableCFDataStore, DataStorePickleMixin): """Store for reading and writing data via the Python-NetCDF4 library. @@ -347,7 +372,7 @@ def set_attribute(self, key, value): with self.ensure_open(autoclose=False): if self.format != 'NETCDF4': value = encode_nc3_attr_value(value) - self.ds.setncattr(key, value) + _set_nc_attribute(self.ds, key, value) def set_variables(self, *args, **kwargs): with self.ensure_open(autoclose=False): @@ -402,7 +427,7 @@ def prepare_variable(self, name, variable, check_encoding=False, for k, v in iteritems(attrs): # set attributes one-by-one since netCDF4<1.0.10 can't handle # OrderedDict as the input to setncatts - nc4_var.setncattr(k, v) + _set_nc_attribute(nc4_var, k, v) target = NetCDF4ArrayWrapper(name, self) diff --git a/xarray/tests/test_backends.py b/xarray/tests/test_backends.py index c5e8d126e43..8b20f6148e7 100644 --- a/xarray/tests/test_backends.py +++ b/xarray/tests/test_backends.py @@ -1144,6 +1144,23 @@ def test_88_character_filename_segmentation_fault(self): # Need to construct 88 character filepath xr.Dataset().to_netcdf('a' * (88 - len(os.getcwd()) - 1)) + def test_setncattr_string(self): + list_of_strings = ['list', 'of', 'strings'] + one_element_list_of_strings = ['one element'] + one_string = 'one string' + attrs = {'foo': list_of_strings, + 'bar': one_element_list_of_strings, + 'baz': one_string} + ds = Dataset({'x': ('y', [1, 2, 3], attrs)}, + attrs=attrs) + + with self.roundtrip(ds) as actual: + for totest in [actual, actual['x']]: + assert_array_equal(list_of_strings, totest.attrs['foo']) + assert_array_equal(one_element_list_of_strings, + totest.attrs['bar']) + assert one_string == totest.attrs['baz'] + class NetCDF4DataStoreAutocloseTrue(NetCDF4DataTest): autoclose = True