diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index ebab20c806..574689ddee 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -58,6 +58,11 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ added handling for string stash codes when saving pp files. (:issue:`6239`, :pull:`6289`) +#. `@trexfeathers`_ and `@jrackham-mo`_ added a check for dtype castability when + saving NetCDF ``valid_range``, ``valid_min`` and ``valid_max`` attributes - + older NetCDF formats e.g. ``NETCDF4_CLASSIC`` support a maximum precision of + 32-bit. (:issue:`6178`, :pull:`6343`) + 💣 Incompatible Changes ======================= @@ -70,7 +75,7 @@ This document explains the changes made to Iris for this release 🚀 Performance Enhancements =========================== -#. `@bouweandela`_ made loading :class:`~iris.cube.Cube`s from NetCDF files +#. `@bouweandela`_ made loading :class:`~iris.cube.Cube`\s from NetCDF files faster. (:pull:`6229` and :pull:`6252`) #. `@fnattino`_ enabled lazy cube interpolation using the linear and diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index fa434bd439..6badeb6a0b 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -655,8 +655,7 @@ def write( msg = "cf_profile is available but no {} defined.".format("cf_patch") warnings.warn(msg, category=iris.warnings.IrisCfSaveWarning) - @staticmethod - def check_attribute_compliance(container, data_dtype): + def check_attribute_compliance(self, container, data_dtype): """Check attributte complliance.""" def _coerce_value(val_attr, val_attr_value, data_dtype): @@ -681,6 +680,7 @@ def _coerce_value(val_attr, val_attr_value, data_dtype): val_attr_value = container.attributes.get(val_attr) if val_attr_value is not None: val_attr_value = np.asarray(val_attr_value) + self._ensure_valid_dtype(val_attr_value, val_attr, val_attr_value) if data_dtype.itemsize == 1: # Allow signed integral type if val_attr_value.dtype.kind == "i": diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver.py b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver.py index 1c57323301..890e706c05 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver.py +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver.py @@ -601,9 +601,11 @@ def assertAttribute(self, value): np.asarray(self.container.attributes[self.attribute]).dtype, value ) - def check_attribute_compliance_call(self, value): + def check_attribute_compliance_call(self, value, file_type="NETCDF4"): self.set_attribute(value) - with Saver("nonexistent test file", "NETCDF4") as saver: + with Saver("nonexistent test file", file_type) as saver: + # Get the Mock to work properly. + saver._dataset.file_format = file_type saver.check_attribute_compliance(self.container, self.data_dtype) @@ -638,6 +640,12 @@ def test_valid_range_not_numpy_array(self): self.check_attribute_compliance_call(value) self.assertAttribute(np.int64) + def test_uncastable_dtype(self): + self.data_dtype = np.dtype("int64") + value = [0, np.iinfo(self.data_dtype).max] + with self.assertRaisesRegex(ValueError, "cannot be safely cast"): + self.check_attribute_compliance_call(value, file_type="NETCDF4_CLASSIC") + class Test_check_attribute_compliance__valid_min( _Common__check_attribute_compliance, tests.IrisTest @@ -670,6 +678,12 @@ def test_valid_range_not_numpy_array(self): self.check_attribute_compliance_call(value) self.assertAttribute(np.int64) + def test_uncastable_dtype(self): + self.data_dtype = np.dtype("int64") + value = np.iinfo(self.data_dtype).min + with self.assertRaisesRegex(ValueError, "cannot be safely cast"): + self.check_attribute_compliance_call(value, file_type="NETCDF4_CLASSIC") + class Test_check_attribute_compliance__valid_max( _Common__check_attribute_compliance, tests.IrisTest @@ -702,6 +716,12 @@ def test_valid_range_not_numpy_array(self): self.check_attribute_compliance_call(value) self.assertAttribute(np.int64) + def test_uncastable_dtype(self): + self.data_dtype = np.dtype("int64") + value = np.iinfo(self.data_dtype).max + with self.assertRaisesRegex(ValueError, "cannot be safely cast"): + self.check_attribute_compliance_call(value, file_type="NETCDF4_CLASSIC") + class Test_check_attribute_compliance__exception_handling( _Common__check_attribute_compliance, tests.IrisTest