diff --git a/environment.yml b/environment.yml index cf1cb7b9..2c41b6dc 100644 --- a/environment.yml +++ b/environment.yml @@ -2,5 +2,5 @@ channels: - conda-forge dependencies: - iris>=2.4 - - python-eccodes>=0.9.1,<2 + - python-eccodes - pep8 diff --git a/iris_grib/_save_rules.py b/iris_grib/_save_rules.py index e3c35293..f3eeb940 100644 --- a/iris_grib/_save_rules.py +++ b/iris_grib/_save_rules.py @@ -1489,6 +1489,13 @@ def data_section(cube, grib): # Enable missing values in the grib message. gribapi.grib_set(grib, "bitmapPresent", 1) gribapi.grib_set_double(grib, "missingValue", fill_value) + + # A segmentation fault is raised by `gribapi.grib_set_double_array` if it + # tries to cast large data to float64. As a temporary fix we cast the data + # upfront + # TODO: remove the `astype` command once eccodes (gribapi) has been fixed. + if data.dtype != np.float64: + data = data.astype(np.float64) gribapi.grib_set_double_array(grib, "values", data.flatten()) # todo: check packing accuracy? diff --git a/iris_grib/tests/unit/save_rules/test_data_section.py b/iris_grib/tests/unit/save_rules/test_data_section.py index c309ec20..6379c942 100644 --- a/iris_grib/tests/unit/save_rules/test_data_section.py +++ b/iris_grib/tests/unit/save_rules/test_data_section.py @@ -14,6 +14,7 @@ from unittest import mock +import gribapi import iris.cube import numpy as np @@ -151,5 +152,27 @@ def test_scaled_with_nan_fill_value(self): self.assertValues(grib_api, [-1000, 2000, FILL, FILL]) +class TestNonDoubleData(tests.IrisGribTest): + # When saving to GRIB, data that is not float64 is cast to float64. This + # test checks that non-float64 data is saved without raising a segmentation + # fault. + def check(self, dtype): + data = np.random.random(1920 * 2560).astype(dtype) + cube = iris.cube.Cube(data, + standard_name='geopotential_height', units='km') + grib_message = gribapi.grib_new_from_samples("GRIB2") + data_section(cube, grib_message) + gribapi.grib_release(grib_message) + + def test_float32(self): + self.check(dtype=np.float32) + + def test_int32(self): + self.check(dtype=np.int32) + + def test_int64(self): + self.check(dtype=np.int64) + + if __name__ == "__main__": tests.main() diff --git a/iris_grib/tests/unit/test_save_messages.py b/iris_grib/tests/unit/test_save_messages.py index f0c6e115..974647f2 100644 --- a/iris_grib/tests/unit/test_save_messages.py +++ b/iris_grib/tests/unit/test_save_messages.py @@ -23,22 +23,14 @@ def setUp(self): def test_save(self): m = mock.mock_open() with mock.patch('builtins.open', m, create=True): - # sending a MagicMock object to gribapi raises an AssertionError - # as the gribapi code does a type check - # this is deemed acceptable within the scope of this unit test - with self.assertRaises((AssertionError, TypeError)): - iris_grib.save_messages([self.grib_message], 'foo.grib2') + iris_grib.save_messages([self.grib_message], 'foo.grib2') self.assertTrue(mock.call('foo.grib2', 'wb') in m.mock_calls) def test_save_append(self): m = mock.mock_open() with mock.patch('builtins.open', m, create=True): - # sending a MagicMock object to gribapi raises an AssertionError - # as the gribapi code does a type check - # this is deemed acceptable within the scope of this unit test - with self.assertRaises((AssertionError, TypeError)): - iris_grib.save_messages([self.grib_message], 'foo.grib2', - append=True) + iris_grib.save_messages([self.grib_message], 'foo.grib2', + append=True) self.assertTrue(mock.call('foo.grib2', 'ab') in m.mock_calls)