diff --git a/lib/iris/fileformats/grib/__init__.py b/lib/iris/fileformats/grib/__init__.py index 6b06e3661c..54f2bdbab0 100644 --- a/lib/iris/fileformats/grib/__init__.py +++ b/lib/iris/fileformats/grib/__init__.py @@ -58,10 +58,6 @@ hindcast_workaround = False -# rules for converting a grib message to a cm cube -_load_rules = None - - CENTRE_TITLES = {'egrr': 'U.K. Met Office - Exeter', 'ecmf': 'European Centre for Medium Range Weather Forecasts', 'rjtd': 'Tokyo, Japan Meteorological Agency', @@ -127,13 +123,8 @@ def reset_load_rules(): .. deprecated:: 1.7 """ - # Uses this module-level variable - global _load_rules - warnings.warn('reset_load_rules was deprecated in v1.7.') - _load_rules = None - class GribDataProxy(object): """A reference to the data payload of a single Grib message.""" @@ -903,7 +894,7 @@ def load_cubes(filenames, callback=None, auto_regularise=True): grib_loader = iris.fileformats.rules.Loader( _GribMessage.messages_from_filename, {}, - iris.fileformats.grib._load_convert.convert, None) + iris.fileformats.grib._load_convert.convert) else: if auto_regularise is not None: # The old loader supports the auto_regularise keyword, but in @@ -916,8 +907,7 @@ def load_cubes(filenames, callback=None, auto_regularise=True): grib_loader = iris.fileformats.rules.Loader( grib_generator, {'auto_regularise': auto_regularise}, - iris.fileformats.grib.load_rules.convert, - _load_rules) + iris.fileformats.grib.load_rules.convert) return iris.fileformats.rules.load_cubes(filenames, callback, grib_loader) diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index e990a4621d..be08eac4fe 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -63,8 +63,7 @@ EARTH_RADIUS = 6371229.0 -# PP->Cube and Cube->PP rules are loaded on first use -_load_rules = None +# Cube->PP rules are loaded on first use _save_rules = None @@ -1891,24 +1890,6 @@ def _field_gen(filename, read_data_bytes): yield pp_field -def _ensure_load_rules_loaded(): - """Makes sure the standard conversion and verification rules are loaded.""" - - # Uses these module-level variables - global _load_rules, _cross_reference_rules - - rules = iris.fileformats.rules - - if _load_rules is None: - basepath = iris.config.CONFIG_PATH - _load_rules = rules.RulesContainer(os.path.join(basepath, 'pp_rules.txt')) - - if _cross_reference_rules is None: - basepath = iris.config.CONFIG_PATH - _cross_reference_rules = rules.RulesContainer(os.path.join(basepath, 'pp_cross_reference_rules.txt'), - rule_type=rules.ObjectReturningRule) - - def reset_load_rules(): """ Resets the PP load process to use only the standard conversion rules. @@ -1916,13 +1897,8 @@ def reset_load_rules(): .. deprecated:: 1.7 """ - # Uses this module-level variable - global _load_rules - warnings.warn('reset_load_rules was deprecated in v1.7.') - _load_rules = None - def _ensure_save_rules_loaded(): """Makes sure the standard save rules are loaded.""" @@ -2059,7 +2035,7 @@ def _load_cubes_variable_loader(filenames, callback, loading_function, pp_filter = _convert_constraints(constraints) pp_loader = iris.fileformats.rules.Loader( loading_function, loading_function_kwargs or {}, - iris.fileformats.pp_rules.convert, _load_rules) + iris.fileformats.pp_rules.convert) return iris.fileformats.rules.load_cubes(filenames, callback, pp_loader, pp_filter) diff --git a/lib/iris/fileformats/rules.py b/lib/iris/fileformats/rules.py index b5d24af753..e7aa477985 100644 --- a/lib/iris/fileformats/rules.py +++ b/lib/iris/fileformats/rules.py @@ -779,9 +779,41 @@ def _ensure_aligned(regrid_cache, src_cube, target_cube): return result_cube -Loader = collections.namedtuple('Loader', - ('field_generator', 'field_generator_kwargs', - 'converter', 'legacy_custom_rules')) +_loader_attrs = ('field_generator', 'field_generator_kwargs', + 'converter', 'legacy_custom_rules') +class Loader(collections.namedtuple('Loader', _loader_attrs)): + def __new__(cls, field_generator, field_generator_kwargs, converter, + legacy_custom_rules=None): + """ + Create a definition of a field-based Cube loader. + + Args: + + * field_generator + A callable that accepts a filename as its first argument and + returns an iterable of field objects. + + * field_generator_kwargs + Additional arguments to be passed to the field_generator. + + * converter + A callable that converts a field object into a Cube. + + Kwargs: + + * legacy_custom_rules + An object with a callable `verify` attribute with two + parameters: (cube, field). Legacy method for modifying + Cubes during the load process. Default is None. + + .. deprecated:: 1.9 + + """ + if legacy_custom_rules is not None: + warnings.warn('The `legacy_custom_rules` attribute is ' + 'deprecated.') + return tuple.__new__(cls, (field_generator, field_generator_kwargs, + converter, legacy_custom_rules)) ConversionMetadata = collections.namedtuple('ConversionMetadata', @@ -846,6 +878,8 @@ def load_cubes(filenames, user_callback, loader, filter_function=None): # Run any custom user-provided rules. if loader.legacy_custom_rules: + warnings.warn('The `legacy_custom_rules` attribute of ' + 'the `loader` is deprecated.') loader.legacy_custom_rules.verify(cube, field) cube = iris.io.run_callback(user_callback, cube, field, filename) diff --git a/lib/iris/tests/integration/test_pp_constrained_load_cubes.py b/lib/iris/tests/integration/test_pp_constrained_load_cubes.py index 226ff412aa..995506d5a3 100644 --- a/lib/iris/tests/integration/test_pp_constrained_load_cubes.py +++ b/lib/iris/tests/integration/test_pp_constrained_load_cubes.py @@ -35,8 +35,7 @@ def test_pp_with_stash_constraint(self): filenames = [tests.get_data_path(('PP', 'globClim1', 'dec_subset.pp'))] stcon = iris.AttributeConstraint(STASH='m01s00i004') pp_constraints = pp._convert_constraints(stcon) - pp_loader = iris.fileformats.rules.Loader(pp.load, {}, - convert, pp._load_rules) + pp_loader = iris.fileformats.rules.Loader(pp.load, {}, convert) cubes = list(load_cubes(filenames, None, pp_loader, pp_constraints)) self.assertEqual(len(cubes), 38) @@ -46,8 +45,7 @@ def test_pp_with_stash_constraints(self): stcon1 = iris.AttributeConstraint(STASH='m01s00i004') stcon2 = iris.AttributeConstraint(STASH='m01s00i010') pp_constraints = pp._convert_constraints([stcon1, stcon2]) - pp_loader = iris.fileformats.rules.Loader(pp.load, {}, - convert, pp._load_rules) + pp_loader = iris.fileformats.rules.Loader(pp.load, {}, convert) cubes = list(load_cubes(filenames, None, pp_loader, pp_constraints)) self.assertEqual(len(cubes), 76) @@ -55,8 +53,7 @@ def test_pp_with_stash_constraints(self): def test_pp_no_constraint(self): filenames = [tests.get_data_path(('PP', 'globClim1', 'dec_subset.pp'))] pp_constraints = pp._convert_constraints(None) - pp_loader = iris.fileformats.rules.Loader(pp.load, {}, - convert, pp._load_rules) + pp_loader = iris.fileformats.rules.Loader(pp.load, {}, convert) cubes = list(load_cubes(filenames, None, pp_loader, pp_constraints)) self.assertEqual(len(cubes), 152) diff --git a/lib/iris/tests/unit/fileformats/rules/test_Loader.py b/lib/iris/tests/unit/fileformats/rules/test_Loader.py new file mode 100644 index 0000000000..63a69b31e9 --- /dev/null +++ b/lib/iris/tests/unit/fileformats/rules/test_Loader.py @@ -0,0 +1,73 @@ +# (C) British Crown Copyright 2015, Met Office +# +# This file is part of Iris. +# +# Iris is free software: you can redistribute it and/or modify it under +# the terms of the GNU Lesser General Public License as published by the +# Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Iris is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Iris. If not, see . +"""Unit tests for :class:`iris.fileformats.rules.Loader`.""" + +from __future__ import (absolute_import, division, print_function) +from six.moves import (filter, input, map, range, zip) # noqa + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests + +import mock + +from iris.fileformats.rules import Loader + + +class Test___init__(tests.IrisTest): + def test_normal(self): + with mock.patch('warnings.warn') as warn: + loader = Loader(mock.sentinel.GEN_FUNC, + mock.sentinel.GEN_FUNC_KWARGS, + mock.sentinel.CONVERTER) + self.assertEqual(warn.call_count, 0) + self.assertIs(loader.field_generator, mock.sentinel.GEN_FUNC) + self.assertIs(loader.field_generator_kwargs, + mock.sentinel.GEN_FUNC_KWARGS) + self.assertIs(loader.converter, mock.sentinel.CONVERTER) + self.assertIs(loader.legacy_custom_rules, None) + + def test_normal_with_explicit_none(self): + with mock.patch('warnings.warn') as warn: + loader = Loader(mock.sentinel.GEN_FUNC, + mock.sentinel.GEN_FUNC_KWARGS, + mock.sentinel.CONVERTER, None) + self.assertEqual(warn.call_count, 0) + self.assertIs(loader.field_generator, mock.sentinel.GEN_FUNC) + self.assertIs(loader.field_generator_kwargs, + mock.sentinel.GEN_FUNC_KWARGS) + self.assertIs(loader.converter, mock.sentinel.CONVERTER) + self.assertIs(loader.legacy_custom_rules, None) + + def test_deprecated_custom_rules(self): + with mock.patch('warnings.warn') as warn: + loader = Loader(mock.sentinel.GEN_FUNC, + mock.sentinel.GEN_FUNC_KWARGS, + mock.sentinel.CONVERTER, + mock.sentinel.CUSTOM_RULES) + self.assertEqual(warn.call_count, 1) + self.assertEqual(warn.call_args[0][0], + 'The `legacy_custom_rules` attribute is deprecated.') + self.assertIs(loader.field_generator, mock.sentinel.GEN_FUNC) + self.assertIs(loader.field_generator_kwargs, + mock.sentinel.GEN_FUNC_KWARGS) + self.assertIs(loader.converter, mock.sentinel.CONVERTER) + self.assertIs(loader.legacy_custom_rules, mock.sentinel.CUSTOM_RULES) + + +if __name__ == '__main__': + tests.main()