diff --git a/.travis.yml b/.travis.yml index 357c6e33..91cae33b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,6 @@ language: python sudo: false env: - - python=2.7 - iris=v1.10.0 - - python=2.7 iris=master diff --git a/environment.yml b/environment.yml index 5d540bc9..2ca4cf11 100644 --- a/environment.yml +++ b/environment.yml @@ -1,5 +1,5 @@ channels: - scitools dependencies: - - iris=1.* + - iris=2.* - ecmwf_grib=1.14 diff --git a/iris_grib/__init__.py b/iris_grib/__init__.py index e9dc1654..91213255 100644 --- a/iris_grib/__init__.py +++ b/iris_grib/__init__.py @@ -29,7 +29,6 @@ import math # for fmod import warnings -import biggus import cartopy.crs as ccrs import cf_units import gribapi @@ -37,6 +36,7 @@ import numpy.ma as ma import iris +from iris._lazy_data import as_lazy_data, convert_nans_array import iris.coord_systems as coord_systems from iris.exceptions import TranslationError, NotYetImplementedError @@ -99,12 +99,11 @@ class GribDataProxy(object): """A reference to the data payload of a single Grib message.""" - __slots__ = ('shape', 'dtype', 'fill_value', 'path', 'offset') + __slots__ = ('shape', 'dtype', 'path', 'offset') - def __init__(self, shape, dtype, fill_value, path, offset): + def __init__(self, shape, dtype, path, offset): self.shape = shape self.dtype = dtype - self.fill_value = fill_value self.path = path self.offset = offset @@ -123,7 +122,7 @@ def __getitem__(self, keys): def __repr__(self): msg = '<{self.__class__.__name__} shape={self.shape} ' \ - 'dtype={self.dtype!r} fill_value={self.fill_value!r} ' \ + 'dtype={self.dtype!r} ' \ 'path={self.path!r} offset={self.offset}>' return msg.format(self=self) @@ -146,6 +145,7 @@ class GribWrapper(object): def __init__(self, grib_message, grib_fh=None): """Store the grib message and compute our extra keys.""" self.grib_message = grib_message + self.realised_dtype = np.array([0.]).dtype if self.edition != 1: emsg = 'GRIB edition {} is not supported by {!r}.' @@ -184,12 +184,14 @@ def __init__(self, grib_message, grib_fh=None): # The byte offset requires to be reset back to the first byte # of this message. The file pointer offset is always at the end # of the current message due to the grib-api reading the message. - proxy = GribDataProxy(shape, np.zeros(0).dtype, np.nan, - grib_fh.name, + proxy = GribDataProxy(shape, self.realised_dtype, grib_fh.name, offset - message_length) - self._data = biggus.NumpyArrayAdapter(proxy) + self._data = as_lazy_data(proxy) else: - self.data = _message_values(grib_message, shape) + values_array = _message_values(grib_message, shape) + # mask where the values are nan + self.data = convert_nans_array(values_array, + nans_replacement=ma.masked) def _confirm_in_scope(self): """Ensure we have a grib flavour that we choose to support.""" @@ -640,6 +642,19 @@ def _get_verification_date(self): # Return validity_time = (reference_time + start_offset*time_unit). return reference_date_time + interval_delta + @property + def bmdi(self): + # Not sure of any cases where GRIB provides a fill value. + # Default for fill value is None. + return None + + def core_data(self): + try: + data = self._data + except AttributeError: + data = self.data + return data + def phenomenon_points(self, time_unit): """ Return the phenomenon time point offset from the epoch time reference @@ -683,10 +698,6 @@ def _message_values(grib_message, shape): data = gribapi.grib_get_double_array(grib_message, 'values') data = data.reshape(shape) - # Handle missing values in a sensible way. - mask = np.isnan(data) - if mask.any(): - data = ma.array(data, mask=mask, fill_value=np.nan) return data diff --git a/iris_grib/_load_convert.py b/iris_grib/_load_convert.py index b85dbd36..9ae9abd5 100644 --- a/iris_grib/_load_convert.py +++ b/iris_grib/_load_convert.py @@ -414,7 +414,7 @@ def ellipsoid(shapeOfTheEarth, major, minor, radius): """ # Supported shapeOfTheEarth values. - if shapeOfTheEarth not in (0, 1, 3, 6, 7): + if shapeOfTheEarth not in (0, 1, 2, 3, 4, 5, 6, 7): msg = 'Grid definition section 3 contains an unsupported ' \ 'shape of the earth [{}]'.format(shapeOfTheEarth) raise TranslationError(msg) @@ -425,25 +425,36 @@ def ellipsoid(shapeOfTheEarth, major, minor, radius): elif shapeOfTheEarth == 1: # Earth assumed spherical with radius specified (in m) by # data producer. - if radius is ma.masked: + if ma.is_masked(radius): msg = 'Ellipsoid for shape of the earth {} requires a' \ 'radius to be specified.'.format(shapeOfTheEarth) raise ValueError(msg) result = icoord_systems.GeogCS(radius) + elif shapeOfTheEarth == 2: + # Earth assumed oblate spheroid with size as determined by IAU in 1965. + result = icoord_systems.GeogCS(6378160, inverse_flattening=297.0) elif shapeOfTheEarth in [3, 7]: # Earth assumed oblate spheroid with major and minor axes # specified (in km)/(in m) by data producer. emsg_oblate = 'Ellipsoid for shape of the earth [{}] requires a' \ 'semi-{} axis to be specified.' - if major is ma.masked: + if ma.is_masked(major): raise ValueError(emsg_oblate.format(shapeOfTheEarth, 'major')) - if minor is ma.masked: + if ma.is_masked(minor): raise ValueError(emsg_oblate.format(shapeOfTheEarth, 'minor')) # Check whether to convert from km to m. if shapeOfTheEarth == 3: major *= 1000 minor *= 1000 result = icoord_systems.GeogCS(major, minor) + elif shapeOfTheEarth == 4: + # Earth assumed oblate spheroid as defined in IAG-GRS80 model. + result = icoord_systems.GeogCS(6378137, + inverse_flattening=298.257222101) + elif shapeOfTheEarth == 5: + # Earth assumed represented by WGS84 (as used by ICAO since 1998). + result = icoord_systems.GeogCS(6378137, + inverse_flattening=298.257223563) elif shapeOfTheEarth == 6: # Earth assumed spherical with radius of 6 371 229.0m result = icoord_systems.GeogCS(6371229) diff --git a/iris_grib/_save_rules.py b/iris_grib/_save_rules.py index 87274d7a..cd29b764 100644 --- a/iris_grib/_save_rules.py +++ b/iris_grib/_save_rules.py @@ -1156,22 +1156,18 @@ def product_definition_section(cube, grib): def data_section(cube, grib): # Masked data? - if isinstance(cube.data, ma.core.MaskedArray): - # What missing value shall we use? - if not np.isnan(cube.data.fill_value): - # Use the data's fill value. - fill_value = float(cube.data.fill_value) - else: - # We can't use the data's fill value if it's NaN, + if ma.isMaskedArray(cube.data): + fill_value = cube.fill_value + if fill_value is None or np.isnan(cube.fill_value): + # We can't use the cube's fill value if it's NaN, # the GRIB API doesn't like it. # Calculate an MDI outside the data range. min, max = cube.data.min(), cube.data.max() fill_value = min - (max - min) * 0.1 - # Prepare the unmaksed data array, using fill_value as the MDI. - data = cube.data.filled(fill_value) else: fill_value = None - data = cube.data + + data = cube.data # units scaling grib2_info = gptx.cf_phenom_to_grib2_info(cube.standard_name, diff --git a/iris_grib/message.py b/iris_grib/message.py index f2863fb1..a84a0d40 100644 --- a/iris_grib/message.py +++ b/iris_grib/message.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2014 - 2016, Met Office +# (C) British Crown Copyright 2014 - 2017, Met Office # # This file is part of iris-grib. # @@ -26,10 +26,11 @@ from collections import namedtuple import re -import biggus import gribapi import numpy as np +import numpy.ma as ma +from iris._lazy_data import array_masked_to_nans, as_lazy_data from iris.exceptions import TranslationError @@ -88,7 +89,7 @@ def __init__(self, raw_message, recreate_raw, file_ref=None): """ # A RawGribMessage giving gribapi access to the original grib message. self._raw_message = raw_message - # A _MessageLocation which biggus uses to read the message data array, + # A _MessageLocation which dask uses to read the message data array, # by which time this message may be dead and the original grib file # closed. self._recreate_raw = recreate_raw @@ -113,10 +114,23 @@ def sections(self): """ return self._raw_message.sections + @property + def bmdi(self): + # Not sure of any cases where GRIB provides a fill value. + # Default for fill value is None. + return None + + @property + def realised_dtype(self): + return np.dtype('f8') + + def core_data(self): + return self.data + @property def data(self): """ - The data array from the GRIB message as a biggus Array. + The data array from the GRIB message as a dask Array. The shape of the array will match the logical shape of the message's grid. For example, a simple global grid would be @@ -150,9 +164,9 @@ def data(self): shape = (grid_section['numberOfDataPoints'],) else: shape = (grid_section['Nj'], grid_section['Ni']) - proxy = _DataProxy(shape, np.dtype('f8'), np.nan, + proxy = _DataProxy(shape, self.realised_dtype, np.nan, self._recreate_raw) - data = biggus.NumpyArrayAdapter(proxy) + data = as_lazy_data(proxy) else: fmt = 'Grid definition template {} is not supported' raise TranslationError(fmt.format(template)) @@ -180,12 +194,13 @@ def __call__(self): class _DataProxy(object): """A reference to the data payload of a single GRIB message.""" - __slots__ = ('shape', 'dtype', 'fill_value', 'recreate_raw') + __slots__ = ('shape', 'dtype', 'recreate_raw') def __init__(self, shape, dtype, fill_value, recreate_raw): + # TODO: I (@pelson) have no idea why fill_value remains an argument as + # it isn't used. It was copied verbatim from iris' dask branch. self.shape = shape self.dtype = dtype - self.fill_value = fill_value self.recreate_raw = recreate_raw @property @@ -245,20 +260,21 @@ def __getitem__(self, keys): # Only the non-masked values are included in codedValues. _data = np.empty(shape=bitmap.shape) _data[bitmap.astype(bool)] = data - # `np.ma.masked_array` masks where input = 1, the opposite of - # the behaviour specified by the GRIB spec. - data = np.ma.masked_array(_data, mask=np.logical_not(bitmap)) + # Use nan where input = 1, the opposite of the behaviour + # specified by the GRIB spec. + _data[np.logical_not(bitmap.astype(bool))] = np.nan + data = _data else: msg = 'Shapes of data and bitmap do not match.' raise TranslationError(msg) data = data.reshape(self.shape) + return data.__getitem__(keys) def __repr__(self): msg = '<{self.__class__.__name__} shape={self.shape} ' \ - 'dtype={self.dtype!r} fill_value={self.fill_value!r} ' \ - 'recreate_raw={self.recreate_raw!r} ' + 'dtype={self.dtype!r} recreate_raw={self.recreate_raw!r} ' return msg.format(self=self) def __getstate__(self): diff --git a/iris_grib/tests/results/unit/load_cubes/load_cubes/reduced_raw.cml b/iris_grib/tests/results/unit/load_cubes/load_cubes/reduced_raw.cml index 1c3ea906..0252a129 100644 --- a/iris_grib/tests/results/unit/load_cubes/load_cubes/reduced_raw.cml +++ b/iris_grib/tests/results/unit/load_cubes/load_cubes/reduced_raw.cml @@ -1,6 +1,6 @@ - + diff --git a/iris_grib/tests/unit/grib1_load_rules/test_grib1_convert.py b/iris_grib/tests/unit/grib1_load_rules/test_grib1_convert.py index 0dde03ed..9224b176 100644 --- a/iris_grib/tests/unit/grib1_load_rules/test_grib1_convert.py +++ b/iris_grib/tests/unit/grib1_load_rules/test_grib1_convert.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2013 - 2016, Met Office +# (C) British Crown Copyright 2013 - 2017, Met Office # # This file is part of iris-grib. # @@ -61,7 +61,9 @@ def assert_bounded_message(self, **kwargs): '_forecastTimeUnit': 'hours', 'phenomenon_bounds': lambda u: (80, 120), '_phenomenonDateTime': -1, - 'table2Version': 9999} + 'table2Version': 9999, + '_originatingCentre': 'xxx', + } attributes.update(kwargs) message = mock.Mock(**attributes) self._test_for_coord(message, grib1_convert, self.is_forecast_period, diff --git a/iris_grib/tests/unit/load_convert/test_ellipsoid.py b/iris_grib/tests/unit/load_convert/test_ellipsoid.py index c0401bbd..ed1d0eba 100644 --- a/iris_grib/tests/unit/load_convert/test_ellipsoid.py +++ b/iris_grib/tests/unit/load_convert/test_ellipsoid.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2014 - 2016, Met Office +# (C) British Crown Copyright 2014 - 2017, Met Office # # This file is part of iris-grib. # @@ -42,7 +42,7 @@ class Test(tests.IrisGribTest): def test_shape_unsupported(self): - unsupported = [2, 4, 5, 8, 9, 10, MDI] + unsupported = [8, 9, 10, MDI] emsg = 'unsupported shape of the earth' for shape in unsupported: with self.assertRaisesRegexp(TranslationError, emsg): diff --git a/iris_grib/tests/unit/message/test_GribMessage.py b/iris_grib/tests/unit/message/test_GribMessage.py index ec6959dd..a98a06ad 100644 --- a/iris_grib/tests/unit/message/test_GribMessage.py +++ b/iris_grib/tests/unit/message/test_GribMessage.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2014 - 2016, Met Office +# (C) British Crown Copyright 2014 - 2017, Met Office # # This file is part of iris-grib. # @@ -29,10 +29,11 @@ from abc import ABCMeta, abstractmethod -import biggus import mock import numpy as np +import numpy.ma as ma +from iris._lazy_data import as_concrete_data, is_lazy_data from iris.exceptions import TranslationError from iris_grib.message import GribMessage @@ -86,23 +87,45 @@ def test_no_bitmap(self): message = _make_test_message({3: self._section_3, 6: SECTION_6_NO_BITMAP, 7: {'codedValues': values}}) - result = message.data.ndarray() + result = as_concrete_data(message.data) expected = values.reshape(self.shape) self.assertEqual(result.shape, self.shape) self.assertArrayEqual(result, expected) - def test_bitmap_present(self): + def test_bitmap_present_int_data(self): # Test the behaviour where bitmap and codedValues shapes - # are not equal. + # are not equal, and codedValues is integer data. + data_type = np.int64 input_values = np.arange(5) - output_values = np.array([-1, -1, 0, 1, -1, -1, -1, 2, -1, 3, -1, 4]) + output_values = np.array([-1, -1, 0, 1, -1, -1, -1, 2, -1, 3, -1, 4], + dtype=data_type) message = _make_test_message({3: self._section_3, 6: {'bitMapIndicator': 0, 'bitmap': self.bitmap}, 7: {'codedValues': input_values}}) - result = message.data.masked_array() - expected = np.ma.masked_array(output_values, - np.logical_not(self.bitmap)) + result = as_concrete_data(message.data, nans_replacement=ma.masked, + result_dtype=data_type) + expected = ma.masked_array(output_values, + np.logical_not(self.bitmap)) + expected = expected.reshape(self.shape) + self.assertMaskedArrayEqual(result, expected) + self.assertEqual(result.dtype, data_type) + + def test_bitmap_present_float_data(self): + # Test the behaviour where bitmap and codedValues shapes + # are not equal, and codedValues is float data. + data_type = np.float32 + input_values = np.arange(5, dtype=np.float32) + 5 + output_values = np.array([-1, -1, 5, 6, -1, -1, -1, 7, -1, 8, -1, 9], + dtype=data_type) + message = _make_test_message({3: self._section_3, + 6: {'bitMapIndicator': 0, + 'bitmap': self.bitmap}, + 7: {'codedValues': input_values}}) + result = as_concrete_data(message.data, nans_replacement=ma.masked, + result_dtype=data_type) + expected = ma.masked_array(output_values, + np.logical_not(self.bitmap)) expected = expected.reshape(self.shape) self.assertMaskedArrayEqual(result, expected) @@ -115,7 +138,7 @@ def test_bitmap__shapes_mismatch(self): 'bitmap': self.bitmap}, 7: {'codedValues': values}}) with self.assertRaisesRegexp(TranslationError, 'do not match'): - message.data.masked_array() + as_concrete_data(message.data) def test_bitmap__invalid_indicator(self): values = np.arange(12) @@ -124,7 +147,7 @@ def test_bitmap__invalid_indicator(self): 'bitmap': None}, 7: {'codedValues': values}}) with self.assertRaisesRegexp(TranslationError, 'unsupported bitmap'): - message.data.ndarray() + as_concrete_data(message.data) class Test_data__unsupported(tests.IrisGribTest): @@ -183,11 +206,11 @@ def _test(self, scanning_mode): 6: SECTION_6_NO_BITMAP, 7: {'codedValues': np.arange(12)}}) data = message.data - self.assertIsInstance(data, biggus.Array) + self.assertTrue(is_lazy_data(data)) self.assertEqual(data.shape, (3, 4)) self.assertEqual(data.dtype, np.floating) - self.assertIs(data.fill_value, np.nan) - self.assertArrayEqual(data.ndarray(), np.arange(12).reshape(3, 4)) + self.assertArrayEqual(as_concrete_data(data), + np.arange(12).reshape(3, 4)) def test_regular_mode_0(self): self._test(0) 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 d038c39c..57ddbbe1 100644 --- a/iris_grib/tests/unit/save_rules/test_data_section.py +++ b/iris_grib/tests/unit/save_rules/test_data_section.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2014 - 2016, Met Office +# (C) British Crown Copyright 2014 - 2017, Met Office # # This file is part of iris-grib. # @@ -94,8 +94,8 @@ def test_simple(self): def test_masked_with_finite_fill_value(self): cube = iris.cube.Cube(np.ma.MaskedArray([1.0, 2.0, 3.0, 1.0, 2.0, 3.0], - mask=[0, 0, 0, 1, 1, 1], - fill_value=2000)) + mask=[0, 0, 0, 1, 1, 1]), + fill_value=2000) grib_message = mock.sentinel.GRIB_MESSAGE with mock.patch(GRIB_API) as grib_api: data_section(cube, grib_message) @@ -107,8 +107,8 @@ def test_masked_with_finite_fill_value(self): def test_masked_with_nan_fill_value(self): cube = iris.cube.Cube(np.ma.MaskedArray([1.0, 2.0, 3.0, 1.0, 2.0, 3.0], - mask=[0, 0, 0, 1, 1, 1], - fill_value=np.nan)) + mask=[0, 0, 0, 1, 1, 1]), + fill_value=np.nan) grib_message = mock.sentinel.GRIB_MESSAGE with mock.patch(GRIB_API) as grib_api: data_section(cube, grib_message) @@ -135,8 +135,8 @@ def test_scaled_with_finite_fill_value(self): # When re-scaling masked data with a finite fill value, ensure # the fill value and any filled values are also re-scaled. cube = iris.cube.Cube(np.ma.MaskedArray([1.0, 2.0, 3.0, 1.0, 2.0, 3.0], - mask=[0, 0, 0, 1, 1, 1], - fill_value=2000), + mask=[0, 0, 0, 1, 1, 1]), + fill_value=2000, standard_name='geopotential_height', units='km') grib_message = mock.sentinel.GRIB_MESSAGE with mock.patch(GRIB_API) as grib_api: @@ -152,8 +152,8 @@ def test_scaled_with_nan_fill_value(self): # a fill value is chosen which allows for the scaling, and any # filled values match the chosen fill value. cube = iris.cube.Cube(np.ma.MaskedArray([-1.0, 2.0, -1.0, 2.0], - mask=[0, 0, 1, 1], - fill_value=np.nan), + mask=[0, 0, 1, 1]), + fill_value=np.nan, standard_name='geopotential_height', units='km') grib_message = mock.sentinel.GRIB_MESSAGE with mock.patch(GRIB_API) as grib_api: diff --git a/iris_grib/tests/unit/test_GribWrapper.py b/iris_grib/tests/unit/test_GribWrapper.py index d8963327..7c0546a3 100644 --- a/iris_grib/tests/unit/test_GribWrapper.py +++ b/iris_grib/tests/unit/test_GribWrapper.py @@ -1,4 +1,4 @@ -# (C) British Crown Copyright 2014 - 2016, Met Office +# (C) British Crown Copyright 2014 - 2017, Met Office # # This file is part of iris-grib. # @@ -26,13 +26,13 @@ # importing anything else. import iris_grib.tests as tests -from biggus import NumpyArrayAdapter import mock import numpy as np +from iris._lazy_data import as_concrete_data, is_lazy_data from iris.exceptions import TranslationError -from iris_grib import GribWrapper, GribDataProxy +from iris_grib import GribWrapper, GribDataProxy, _load_generate _message_length = 1000 @@ -89,89 +89,53 @@ def test_edition_1(self): self.assertEqual(wrapper.grib_message, grib_message) -class Test_deferred(tests.IrisGribTest): +@tests.skip_data +class Test_deferred_data(tests.IrisTest): + def test_regular_data(self): + filename = tests.get_data_path(('GRIB', 'gaussian', + 'regular_gg.grib1')) + messages = list(_load_generate(filename)) + self.assertTrue(is_lazy_data(messages[0]._data)) + + def test_reduced_data(self): + filename = tests.get_data_path(('GRIB', 'reduced', + 'reduced_ll.grib1')) + messages = list(_load_generate(filename)) + self.assertTrue(is_lazy_data(messages[0]._data)) + + +class Test_deferred_proxy_args(tests.IrisTest): def setUp(self): - confirm_patch = mock.patch( - 'iris_grib.GribWrapper._confirm_in_scope') - compute_patch = mock.patch( - 'iris_grib.GribWrapper._compute_extra_keys') - long_patch = mock.patch('gribapi.grib_get_long', _mock_grib_get_long) - string_patch = mock.patch('gribapi.grib_get_string', - _mock_grib_get_string) - native_patch = mock.patch('gribapi.grib_get_native_type', - _mock_grib_get_native_type) - confirm_patch.start() - compute_patch.start() - long_patch.start() - string_patch.start() - native_patch.start() - self.addCleanup(confirm_patch.stop) - self.addCleanup(compute_patch.stop) - self.addCleanup(long_patch.stop) - self.addCleanup(string_patch.stop) - self.addCleanup(native_patch.stop) - - def test_regular_sequential(self): - tell_tale = np.arange(1, 5) * _message_length - grib_fh = mock.Mock(tell=mock.Mock(side_effect=tell_tale)) - grib_message = 'regular_ll' - for i, _ in enumerate(tell_tale): - gw = GribWrapper(grib_message, grib_fh) - self.assertIsInstance(gw._data, NumpyArrayAdapter) - proxy = gw._data.concrete - self.assertIsInstance(proxy, GribDataProxy) - self.assertEqual(proxy.shape, (10, 20)) - self.assertEqual(proxy.dtype, np.float) - self.assertIs(proxy.fill_value, np.nan) - self.assertEqual(proxy.path, grib_fh.name) - self.assertEqual(proxy.offset, _message_length * i) - - def test_regular_mixed(self): + self.patch('iris_grib.GribWrapper._confirm_in_scope') + self.patch('iris_grib.GribWrapper._compute_extra_keys') + self.patch('gribapi.grib_get_long', _mock_grib_get_long) + self.patch('gribapi.grib_get_string', _mock_grib_get_string) + self.patch('gribapi.grib_get_native_type', _mock_grib_get_native_type) tell_tale = np.arange(1, 5) * _message_length - expected = tell_tale - _message_length - grib_fh = mock.Mock(tell=mock.Mock(side_effect=tell_tale)) + self.expected = tell_tale - _message_length + self.grib_fh = mock.Mock(tell=mock.Mock(side_effect=tell_tale)) + self.dtype = np.float64 + self.path = self.grib_fh.name + self.lookup = _mock_grib_get_long + + def test_regular_proxy_args(self): grib_message = 'regular_ll' - for offset in expected: - gw = GribWrapper(grib_message, grib_fh) - self.assertIsInstance(gw._data, NumpyArrayAdapter) - proxy = gw._data.concrete - self.assertIsInstance(proxy, GribDataProxy) - self.assertEqual(proxy.shape, (10, 20)) - self.assertEqual(proxy.dtype, np.float) - self.assertIs(proxy.fill_value, np.nan) - self.assertEqual(proxy.path, grib_fh.name) - self.assertEqual(proxy.offset, offset) - - def test_reduced_sequential(self): - tell_tale = np.arange(1, 5) * _message_length - grib_fh = mock.Mock(tell=mock.Mock(side_effect=tell_tale)) - grib_message = 'reduced_gg' - for i, _ in enumerate(tell_tale): - gw = GribWrapper(grib_message, grib_fh) - self.assertIsInstance(gw._data, NumpyArrayAdapter) - proxy = gw._data.concrete - self.assertIsInstance(proxy, GribDataProxy) - self.assertEqual(proxy.shape, (200,)) - self.assertEqual(proxy.dtype, np.float) - self.assertIs(proxy.fill_value, np.nan) - self.assertEqual(proxy.path, grib_fh.name) - self.assertEqual(proxy.offset, _message_length * i) - - def test_reduced_mixed(self): - tell_tale = np.arange(1, 5) * _message_length - expected = tell_tale - _message_length - grib_fh = mock.Mock(tell=mock.Mock(side_effect=tell_tale)) + shape = (self.lookup(grib_message, 'Nj'), + self.lookup(grib_message, 'Ni')) + for offset in self.expected: + with mock.patch('iris_grib.GribDataProxy') as mock_gdp: + gw = GribWrapper(grib_message, self.grib_fh) + mock_gdp.assert_called_once_with(shape, self.dtype, + self.path, offset) + + def test_reduced_proxy_args(self): grib_message = 'reduced_gg' - for offset in expected: - gw = GribWrapper(grib_message, grib_fh) - self.assertIsInstance(gw._data, NumpyArrayAdapter) - proxy = gw._data.concrete - self.assertIsInstance(proxy, GribDataProxy) - self.assertEqual(proxy.shape, (200,)) - self.assertEqual(proxy.dtype, np.float) - self.assertIs(proxy.fill_value, np.nan) - self.assertEqual(proxy.path, grib_fh.name) - self.assertEqual(proxy.offset, offset) + shape = (self.lookup(grib_message, 'numberOfValues')) + for offset in self.expected: + with mock.patch('iris_grib.GribDataProxy') as mock_gdp: + gw = GribWrapper(grib_message, self.grib_fh) + mock_gdp.assert_called_once_with((shape,), self.dtype, + self.path, offset) if __name__ == '__main__': diff --git a/setup.py b/setup.py index 1b7afa55..2b77be7d 100644 --- a/setup.py +++ b/setup.py @@ -80,7 +80,7 @@ def file_walk_relative(top, remove=''): 'Programming Language :: Python :: 3.5', ], install_requires = [ - 'iris>=1.9,<2', + # 'iris>=1.9,<2', # Also: the ECMWF GRIB API ], extras_require = {