diff --git a/lib/iris/fileformats/_pyke_rules/fc_rules_cf.krb b/lib/iris/fileformats/_pyke_rules/fc_rules_cf.krb index 03f6c3ecbe..adf8a88d0e 100644 --- a/lib/iris/fileformats/_pyke_rules/fc_rules_cf.krb +++ b/lib/iris/fileformats/_pyke_rules/fc_rules_cf.krb @@ -897,6 +897,7 @@ fc_extras import iris.coord_systems import iris.fileformats.cf as cf import iris.fileformats.netcdf + from iris.fileformats.netcdf import parse_cell_methods, UnknownCellMethodWarning import iris.fileformats.pp as pp import iris.exceptions import iris.std_names @@ -1024,8 +1025,18 @@ fc_extras # Incorporate cell methods nc_att_cell_methods = getattr(cf_var, CF_ATTR_CELL_METHODS, None) - cube.cell_methods = iris.fileformats.netcdf.parse_cell_methods( - cf_var.cf_name, nc_att_cell_methods) + with warnings.catch_warnings(record=True) as warning_records: + cube.cell_methods = parse_cell_methods(nc_att_cell_methods) + # Filter to get the warning we are interested in. + warning_records = [record for record in warning_records + if issubclass(record.category, UnknownCellMethodWarning)] + if len(warning_records) > 0: + # Output an enhanced warning message. + warn_record = warning_records[0] + name = '{}'.format(cf_var.cf_name) + msg = warn_record.message.args[0] + msg = msg.replace('variable', 'variable {!r}'.format(name)) + warnings.warn(message=msg, category=UnknownCellMethodWarning) # Set the cube global attributes. for attr_name, attr_value in six.iteritems(cf_var.cf_group.global_attributes): diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index addd5c0b98..9237944829 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -158,20 +158,29 @@ ''', re.VERBOSE) -def parse_cell_methods(cf_var_name, nc_cell_methods): +class UnknownCellMethodWarning(Warning): + pass + + +def parse_cell_methods(nc_cell_methods): """ Parse a CF cell_methods attribute string into a tuple of zero or more CellMethod instances. Args: - * cf_var_name (str): - The name of the netCDF variable that contains this cell methods - attribute. - * nc_cell_methods (str): The value of the cell methods attribute to be parsed. + Returns: + + * cell_methods + An iterable of :class:`iris.coords.CellMethod`. + + Multiple coordinates, intervals and comments are supported. + If a method has a non-standard name a warning will be issued, but the + results are not affected. + """ cell_methods = [] @@ -184,10 +193,9 @@ def parse_cell_methods(cf_var_name, nc_cell_methods): # e.g. mean over years. method_words = method.split() if method_words[0].lower() not in _CM_KNOWN_METHODS: - msg = 'NetCDF variable {!r} contains unknown cell ' \ - 'method {!r}' - warnings.warn(msg.format('{}'.format(cf_var_name), - '{}'.format(method_words[0]))) + msg = 'NetCDF variable contains unknown cell method {!r}' + warnings.warn(msg.format('{}'.format(method_words[0])), + UnknownCellMethodWarning) d[_CM_METHOD] = method name = d[_CM_NAME] name = name.replace(' ', '') diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index fcc0aa8805..ce58043686 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -24,12 +24,19 @@ import iris.tests as tests from contextlib import contextmanager +import os.path +import shutil +import tempfile +import warnings + import numpy as np import iris +from iris.coords import CellMethod from iris.cube import Cube, CubeList from iris.fileformats.netcdf import CF_CONVENTIONS_VERSION from iris.fileformats.netcdf import Saver +from iris.fileformats.netcdf import UnknownCellMethodWarning from iris.tests import mock import iris.tests.stock as stock @@ -270,5 +277,29 @@ def test_print(self): ' - - ' ' x x') in printed) + +class TestCellMethod_unknown(tests.IrisTest): + def test_unknown_method(self): + cube = Cube([1, 2], long_name='odd_phenomenon') + cube.add_cell_method(CellMethod(method='oddity', coords=('x',))) + temp_dirpath = tempfile.mkdtemp() + try: + temp_filepath = os.path.join(temp_dirpath, 'tmp.nc') + iris.save(cube, temp_filepath) + with warnings.catch_warnings(record=True) as warning_records: + iris.load(temp_filepath) + # Filter to get the warning we are interested in. + warning_messages = [record.message for record in warning_records] + warning_messages = [warn for warn in warning_messages + if isinstance(warn, UnknownCellMethodWarning)] + self.assertEqual(len(warning_messages), 1) + message = warning_messages[0].args[0] + msg = ("NetCDF variable 'odd_phenomenon' contains unknown cell " + "method 'oddity'") + self.assertIn(msg, message) + finally: + shutil.rmtree(temp_dirpath) + + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py b/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py index 91f865b6e6..d682f7b476 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py @@ -39,7 +39,7 @@ def test_simple(self): ] expected = (CellMethod(method='mean', coords='time'),) for cell_method_str in cell_method_strings: - res = parse_cell_methods('test_var', cell_method_str) + res = parse_cell_methods(cell_method_str) self.assertEqual(res, expected) def test_with_interval(self): @@ -50,7 +50,7 @@ def test_with_interval(self): expected = (CellMethod(method='variance', coords='time', intervals='1 hr'),) for cell_method_str in cell_method_strings: - res = parse_cell_methods('test_var', cell_method_str) + res = parse_cell_methods(cell_method_str) self.assertEqual(res, expected) def test_multiple(self): @@ -65,7 +65,7 @@ def test_multiple(self): CellMethod(method='mean', coords='time', intervals='1 day')) for cell_method_str in cell_method_strings: - res = parse_cell_methods('test_var', cell_method_str) + res = parse_cell_methods(cell_method_str) self.assertEqual(res, expected) def test_comment(self): @@ -84,7 +84,7 @@ def test_comment(self): CellMethod(method='mean', coords='time', intervals='1 day', comments='second bit')) for cell_method_str in cell_method_strings: - res = parse_cell_methods('test_var', cell_method_str) + res = parse_cell_methods(cell_method_str) self.assertEqual(res, expected) def test_portions_of_cells(self): @@ -95,7 +95,7 @@ def test_portions_of_cells(self): expected = (CellMethod(method='mean where sea_ice over sea', coords='area'),) for cell_method_str in cell_method_strings: - res = parse_cell_methods('test_var', cell_method_str) + res = parse_cell_methods(cell_method_str) self.assertEqual(res, expected) def test_climatology(self): @@ -108,7 +108,7 @@ def test_climatology(self): expected = (CellMethod(method='minimum within days', coords='time'), CellMethod(method='mean over days', coords='time')) for cell_method_str in cell_method_strings: - res = parse_cell_methods('test_var', cell_method_str) + res = parse_cell_methods(cell_method_str) self.assertEqual(res, expected) def test_climatology_with_unknown_method(self): @@ -122,9 +122,8 @@ def test_climatology_with_unknown_method(self): CellMethod(method='mean over days', coords='time')) for cell_method_str in cell_method_strings: with mock.patch('warnings.warn') as warn: - res = parse_cell_methods('test_var', cell_method_str) - self.assertIn("NetCDF variable 'test_var' contains unknown " - "cell method 'min'", + res = parse_cell_methods(cell_method_str) + self.assertIn("NetCDF variable contains unknown cell method 'min'", warn.call_args[0][0]) self.assertEqual(res, expected)