From 75fe49cededac225bd5ecdc99615d143905d9356 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Mon, 11 Sep 2023 12:37:29 +0100 Subject: [PATCH 01/14] Introduce IrisUserWarning. --- lib/iris/exceptions.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index 5d3da3349e..305dcee91c 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -180,3 +180,16 @@ class CannotAddError(ValueError): """Raised when an object (e.g. coord) cannot be added to a :class:`~iris.cube.Cube`.""" pass + + +############################################################################### +# WARNINGS +# Please namespace all warning objects (i.e. prefix with Iris...). + + +class IrisUserWarning(UserWarning): + """ + Base class for :class:`~python.warnings.UserWarnings` generated by Iris. + """ + + pass From 6bc3dffde5ca127018bab279a76b8219f9d44991 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Mon, 11 Sep 2023 12:43:41 +0100 Subject: [PATCH 02/14] Align existing warning subcategories. --- lib/iris/exceptions.py | 24 +++++++++++++++++++ .../fileformats/_nc_load_rules/helpers.py | 14 +++++++---- lib/iris/fileformats/netcdf/saver.py | 12 +++++++--- lib/iris/fileformats/nimrod_load_rules.py | 14 ++++++++--- .../integration/netcdf/test_delayed_save.py | 8 ++++--- .../tests/integration/netcdf/test_general.py | 6 +++-- .../netcdf/saver/test__fillvalue_report.py | 8 ++++--- .../nimrod_load_rules/test_vertical_coord.py | 9 +++---- 8 files changed, 71 insertions(+), 24 deletions(-) diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index 305dcee91c..bb69f213ab 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -193,3 +193,27 @@ class IrisUserWarning(UserWarning): """ pass + + +class IrisNimrodTranslationWarning(IrisUserWarning): + """ + For unsupported vertical coord types in :mod:`iris.file_formats.nimrod_load_rules`. + """ + + pass + + +class IrisUnknownCellMethodWarning(IrisUserWarning): + """ + If a loaded cell method is not one the method names known to Iris. + """ + + pass + + +class IrisSaverFillValueWarning(IrisUserWarning): + """ + For fill value complications during Iris file saving. + """ + + pass diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index bbf9c660c5..6a44c84fa3 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -34,6 +34,8 @@ import iris.std_names import iris.util +IrisUnknownCellMethodWarning = iris.exceptions.IrisUnknownCellMethodWarning + # TODO: should un-addable coords / cell measures / etcetera be skipped? iris#5068. # @@ -282,7 +284,11 @@ def _split_cell_methods(nc_cell_methods: str) -> List[re.Match]: return nc_cell_methods_matches -class UnknownCellMethodWarning(Warning): +class UnknownCellMethodWarning(iris.exceptions.IrisUnknownCellMethodWarning): + """ + Backwards compatible form of :class:`iris.exceptions.IrisUnknownCellMethodWarning`. + """ + pass @@ -320,7 +326,7 @@ def parse_cell_methods(nc_cell_methods): msg = "NetCDF variable contains unknown cell method {!r}" warnings.warn( msg.format("{}".format(method_words[0])), - UnknownCellMethodWarning, + IrisUnknownCellMethodWarning, ) d[_CM_METHOD] = method name = d[_CM_NAME] @@ -420,7 +426,7 @@ def build_cube_metadata(engine): warning_records = [ record for record in warning_records - if issubclass(record.category, UnknownCellMethodWarning) + if issubclass(record.category, IrisUnknownCellMethodWarning) ] if len(warning_records) > 0: # Output an enhanced warning message. @@ -428,7 +434,7 @@ def build_cube_metadata(engine): 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) + warnings.warn(message=msg, category=IrisUnknownCellMethodWarning) # Set the cube global attributes. for attr_name, attr_value in cf_var.cf_group.global_attributes.items(): diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index c0cfd3d10b..9667d5197a 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -49,6 +49,8 @@ import iris.io import iris.util +IrisSaverFillValueWarning = iris.exceptions.IrisSaverFillValueWarning + # Get the logger : shared logger for all in 'iris.fileformats.netcdf'. from . import logger @@ -308,7 +310,11 @@ def _data_fillvalue_check(arraylib, data, check_value): return is_masked, contains_value -class SaverFillValueWarning(UserWarning): +class SaverFillValueWarning(IrisSaverFillValueWarning): + """ + Backwards compatible form of :class:`iris.exceptions.IrisSaverFillValueWarning`. + """ + pass @@ -340,7 +346,7 @@ def _fillvalue_report(fill_info, is_masked, contains_fill_value, warn=False): is_byte_data = fill_info.dtype.itemsize == 1 result = None if is_byte_data and is_masked and user_value is None: - result = SaverFillValueWarning( + result = IrisSaverFillValueWarning( f"CF var '{varname}' contains byte data with masked points, but " "no fill_value keyword was given. As saved, these " "points will read back as valid values. To save as " @@ -349,7 +355,7 @@ def _fillvalue_report(fill_info, is_masked, contains_fill_value, warn=False): "keyword during saving, otherwise use ncedit/equivalent." ) elif contains_fill_value: - result = SaverFillValueWarning( + result = IrisSaverFillValueWarning( f"CF var '{varname}' contains unmasked data points equal to the " f"fill-value, {check_value}. As saved, these points will read back " "as missing data. To save these as normal values, " diff --git a/lib/iris/fileformats/nimrod_load_rules.py b/lib/iris/fileformats/nimrod_load_rules.py index fd1ccb0e95..2f58adac64 100644 --- a/lib/iris/fileformats/nimrod_load_rules.py +++ b/lib/iris/fileformats/nimrod_load_rules.py @@ -16,7 +16,11 @@ import iris import iris.coord_systems from iris.coords import DimCoord -from iris.exceptions import CoordinateNotFoundError, TranslationError +from iris.exceptions import ( + CoordinateNotFoundError, + IrisNimrodTranslationWarning, + TranslationError, +) __all__ = ["run"] @@ -28,7 +32,11 @@ ) -class TranslationWarning(Warning): +class TranslationWarning(IrisNimrodTranslationWarning): + """ + Backwards compatible form of :class:`iris.exceptions.IrisNimrodTranslationWarning`. + """ + pass @@ -586,7 +594,7 @@ def vertical_coord(cube, field): warnings.warn( "Vertical coord {!r} not yet handled" "".format(field.vertical_coord_type), - TranslationWarning, + IrisNimrodTranslationWarning, ) diff --git a/lib/iris/tests/integration/netcdf/test_delayed_save.py b/lib/iris/tests/integration/netcdf/test_delayed_save.py index 616feb3b0e..09f6235aab 100644 --- a/lib/iris/tests/integration/netcdf/test_delayed_save.py +++ b/lib/iris/tests/integration/netcdf/test_delayed_save.py @@ -17,8 +17,8 @@ import pytest import iris +from iris.exceptions import IrisSaverFillValueWarning from iris.fileformats.netcdf._thread_safe_nc import default_fillvals -from iris.fileformats.netcdf.saver import SaverFillValueWarning import iris.tests from iris.tests.stock import realistic_4d @@ -311,7 +311,7 @@ def test_fill_warnings(self, warning_type, output_path, save_is_delayed): result_warnings = [ log.message for log in logged_warnings - if isinstance(log.message, SaverFillValueWarning) + if isinstance(log.message, IrisSaverFillValueWarning) ] if save_is_delayed: @@ -320,7 +320,9 @@ def test_fill_warnings(self, warning_type, output_path, save_is_delayed): # Complete the operation now with warnings.catch_warnings(): # NOTE: warnings should *not* be issued here, instead they are returned. - warnings.simplefilter("error", category=SaverFillValueWarning) + warnings.simplefilter( + "error", category=IrisSaverFillValueWarning + ) result_warnings = result.compute() # Either way, we should now have 2 similar warnings. diff --git a/lib/iris/tests/integration/netcdf/test_general.py b/lib/iris/tests/integration/netcdf/test_general.py index dc0c29455f..6214f09e7e 100644 --- a/lib/iris/tests/integration/netcdf/test_general.py +++ b/lib/iris/tests/integration/netcdf/test_general.py @@ -25,7 +25,7 @@ from iris.coords import CellMethod from iris.cube import Cube, CubeList import iris.exceptions -from iris.fileformats.netcdf import Saver, UnknownCellMethodWarning +from iris.fileformats.netcdf import Saver # Get the netCDF4 module, but in a sneaky way that avoids triggering the "do not import # netCDF4" check in "iris.tests.test_coding_standards.test_netcdf4_import()". @@ -141,7 +141,9 @@ def test_unknown_method(self): warning_messages = [ warn for warn in warning_messages - if isinstance(warn, UnknownCellMethodWarning) + if isinstance( + warn, iris.exceptions.IrisUnknownCellMethodWarning + ) ] self.assertEqual(len(warning_messages), 1) message = warning_messages[0].args[0] diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/test__fillvalue_report.py b/lib/iris/tests/unit/fileformats/netcdf/saver/test__fillvalue_report.py index b2e4b63e3a..317f75bb8c 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/test__fillvalue_report.py +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/test__fillvalue_report.py @@ -11,9 +11,9 @@ import numpy as np import pytest +from iris.exceptions import IrisSaverFillValueWarning from iris.fileformats.netcdf._thread_safe_nc import default_fillvals from iris.fileformats.netcdf.saver import ( - SaverFillValueWarning, _fillvalue_report, _FillvalueCheckInfo, ) @@ -93,12 +93,14 @@ def test_warn(self, has_collision): expected_msg = "'' contains unmasked data points equal to the fill-value" # Enter a warnings context that checks for the error. warning_context = pytest.warns( - SaverFillValueWarning, match=expected_msg + IrisSaverFillValueWarning, match=expected_msg ) warning_context.__enter__() else: # Check that we get NO warning of the expected type. - warnings.filterwarnings("error", category=SaverFillValueWarning) + warnings.filterwarnings( + "error", category=IrisSaverFillValueWarning + ) # Do call: it should raise AND return a warning, ONLY IF there was a collision. result = _fillvalue_report( diff --git a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py index 44dcf8ac48..54340f598b 100644 --- a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py +++ b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py @@ -15,12 +15,9 @@ from unittest import mock +from iris.exceptions import IrisNimrodTranslationWarning from iris.fileformats.nimrod import NimrodField -from iris.fileformats.nimrod_load_rules import ( - NIMROD_DEFAULT, - TranslationWarning, - vertical_coord, -) +from iris.fileformats.nimrod_load_rules import NIMROD_DEFAULT, vertical_coord class Test(tests.IrisTest): @@ -63,7 +60,7 @@ def test_unhandled(self): vertical_coord_val=1.0, vertical_coord_type=-1 ) warn.assert_called_once_with( - "Vertical coord -1 not yet handled", TranslationWarning + "Vertical coord -1 not yet handled", IrisNimrodTranslationWarning ) def test_null(self): From 1c1afcb4a5ca0ae49ca4eee276dde18d7d9daea1 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Mon, 11 Sep 2023 13:23:19 +0100 Subject: [PATCH 03/14] Plant a flag for future DeprecationWarning use. --- lib/iris/__init__.py | 2 ++ lib/iris/_deprecation.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 0e6670533f..2a3bd8a753 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -175,6 +175,8 @@ def __init__(self, datum_support=False, pandas_ndim=False): # self.__dict__['example_future_flag'] = example_future_flag self.__dict__["datum_support"] = datum_support self.__dict__["pandas_ndim"] = pandas_ndim + # TODO: next major release: set IrisDeprecation to subclass + # DeprecationWarning instead of UserWarning. def __repr__(self): # msg = ('Future(example_future_flag={})') diff --git a/lib/iris/_deprecation.py b/lib/iris/_deprecation.py index 73fcedcd82..f91ad8ae9d 100644 --- a/lib/iris/_deprecation.py +++ b/lib/iris/_deprecation.py @@ -12,7 +12,13 @@ class IrisDeprecation(UserWarning): - """An Iris deprecation warning.""" + """ + An Iris deprecation warning. + + Note this subclasses UserWarning for backwards compatibility with Iris' + original deprection warnings. Should subclass DeprecationWarning at the + next major release. + """ pass From 131bf7dbea468b3476ab387a89bda59b33306faa Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Mon, 11 Sep 2023 16:37:58 +0100 Subject: [PATCH 04/14] Introduce test_categorised_warnings(). --- lib/iris/tests/test_coding_standards.py | 43 ++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index 6cea9dc001..0aac351078 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -13,8 +13,9 @@ from glob import glob import os from pathlib import Path +import re import subprocess -from typing import List, Tuple +from typing import List, NamedTuple, Tuple import iris from iris.fileformats.netcdf import _thread_safe_nc @@ -133,6 +134,46 @@ def test_python_versions(): assert search in path.read_text() +def test_categorised_warnings(): + """ + To ensure that all warnings raised by Iris are categorised, for ease of use. + """ + warn_regex = re.compile(r"(?s)warn\(.*?\)") + + class ResultTuple(NamedTuple): + file_path: Path + line_number: int + match_string: str + + warns_without_category: list[ResultTuple] = [] + warns_with_user_warning: list[ResultTuple] = [] + + for file_path in Path(IRIS_DIR).rglob("*.py"): + file_text = file_path.read_text() + + matched = warn_regex.search(file_text) + while matched: + start, end = matched.span() + result_tuple = ResultTuple( + file_path=file_path, + line_number=file_text[:start].count("\n") + 1, + match_string=file_text[start:end], + ) + if "category=" not in result_tuple.match_string: + warns_without_category.append(result_tuple) + if "UserWarning" in result_tuple.match_string: + warns_with_user_warning.append(result_tuple) + + matched = warn_regex.search(file_text, start + 1) + + # All warnings raised by Iris must be raised with a category kwarg. + # (This avoids UserWarnings being raised by unwritten default behaviour). + assert warns_without_category == [] + + # No warnings raised by Iris can be the base UserWarning class. + assert warns_with_user_warning == [] + + class TestLicenseHeaders(tests.IrisTest): @staticmethod def whatchanged_parse(whatchanged_output): From 54ebf1ffea386933c95cb8e05308aad2f90eb613 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 14 Sep 2023 13:27:14 +0100 Subject: [PATCH 05/14] Fix backwards compatibility of existing warnings classes. --- lib/iris/fileformats/_nc_load_rules/helpers.py | 9 ++++----- lib/iris/fileformats/netcdf/saver.py | 9 ++++----- lib/iris/fileformats/nimrod_load_rules.py | 3 ++- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 6a44c84fa3..e696068e67 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -34,8 +34,6 @@ import iris.std_names import iris.util -IrisUnknownCellMethodWarning = iris.exceptions.IrisUnknownCellMethodWarning - # TODO: should un-addable coords / cell measures / etcetera be skipped? iris#5068. # @@ -289,6 +287,7 @@ class UnknownCellMethodWarning(iris.exceptions.IrisUnknownCellMethodWarning): Backwards compatible form of :class:`iris.exceptions.IrisUnknownCellMethodWarning`. """ + # TODO: remove at the next major release. pass @@ -326,7 +325,7 @@ def parse_cell_methods(nc_cell_methods): msg = "NetCDF variable contains unknown cell method {!r}" warnings.warn( msg.format("{}".format(method_words[0])), - IrisUnknownCellMethodWarning, + UnknownCellMethodWarning, ) d[_CM_METHOD] = method name = d[_CM_NAME] @@ -426,7 +425,7 @@ def build_cube_metadata(engine): warning_records = [ record for record in warning_records - if issubclass(record.category, IrisUnknownCellMethodWarning) + if issubclass(record.category, UnknownCellMethodWarning) ] if len(warning_records) > 0: # Output an enhanced warning message. @@ -434,7 +433,7 @@ def build_cube_metadata(engine): 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=IrisUnknownCellMethodWarning) + warnings.warn(message=msg, category=UnknownCellMethodWarning) # Set the cube global attributes. for attr_name, attr_value in cf_var.cf_group.global_attributes.items(): diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 9667d5197a..ee3f499034 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -49,8 +49,6 @@ import iris.io import iris.util -IrisSaverFillValueWarning = iris.exceptions.IrisSaverFillValueWarning - # Get the logger : shared logger for all in 'iris.fileformats.netcdf'. from . import logger @@ -310,11 +308,12 @@ def _data_fillvalue_check(arraylib, data, check_value): return is_masked, contains_value -class SaverFillValueWarning(IrisSaverFillValueWarning): +class SaverFillValueWarning(iris.exceptions.IrisSaverFillValueWarning): """ Backwards compatible form of :class:`iris.exceptions.IrisSaverFillValueWarning`. """ + # TODO: remove at the next major release. pass @@ -346,7 +345,7 @@ def _fillvalue_report(fill_info, is_masked, contains_fill_value, warn=False): is_byte_data = fill_info.dtype.itemsize == 1 result = None if is_byte_data and is_masked and user_value is None: - result = IrisSaverFillValueWarning( + result = SaverFillValueWarning( f"CF var '{varname}' contains byte data with masked points, but " "no fill_value keyword was given. As saved, these " "points will read back as valid values. To save as " @@ -355,7 +354,7 @@ def _fillvalue_report(fill_info, is_masked, contains_fill_value, warn=False): "keyword during saving, otherwise use ncedit/equivalent." ) elif contains_fill_value: - result = IrisSaverFillValueWarning( + result = SaverFillValueWarning( f"CF var '{varname}' contains unmasked data points equal to the " f"fill-value, {check_value}. As saved, these points will read back " "as missing data. To save these as normal values, " diff --git a/lib/iris/fileformats/nimrod_load_rules.py b/lib/iris/fileformats/nimrod_load_rules.py index 2f58adac64..1bfd0858d0 100644 --- a/lib/iris/fileformats/nimrod_load_rules.py +++ b/lib/iris/fileformats/nimrod_load_rules.py @@ -37,6 +37,7 @@ class TranslationWarning(IrisNimrodTranslationWarning): Backwards compatible form of :class:`iris.exceptions.IrisNimrodTranslationWarning`. """ + # TODO: remove at the next major release. pass @@ -594,7 +595,7 @@ def vertical_coord(cube, field): warnings.warn( "Vertical coord {!r} not yet handled" "".format(field.vertical_coord_type), - IrisNimrodTranslationWarning, + TranslationWarning, ) From f4e6a35d8b37b43e412b74b8f4a6a517358b23b6 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 14 Sep 2023 15:52:27 +0100 Subject: [PATCH 06/14] Categorise all Iris warnings. --- lib/iris/_concatenate.py | 3 +- lib/iris/analysis/_regrid.py | 3 +- lib/iris/analysis/calculus.py | 6 +- lib/iris/analysis/cartography.py | 23 +- lib/iris/analysis/geometry.py | 8 +- lib/iris/analysis/maths.py | 3 +- lib/iris/aux_factory.py | 68 ++++-- lib/iris/config.py | 12 +- lib/iris/coord_systems.py | 7 +- lib/iris/coords.py | 20 +- lib/iris/cube.py | 8 +- lib/iris/exceptions.py | 196 +++++++++++++++++- lib/iris/experimental/regrid.py | 3 +- lib/iris/experimental/ugrid/cf.py | 27 ++- lib/iris/experimental/ugrid/load.py | 20 +- lib/iris/fileformats/_ff.py | 34 ++- .../fileformats/_nc_load_rules/actions.py | 18 +- .../fileformats/_nc_load_rules/helpers.py | 102 +++++++-- lib/iris/fileformats/cf.py | 50 ++++- lib/iris/fileformats/name_loaders.py | 4 +- lib/iris/fileformats/netcdf/loader.py | 17 +- lib/iris/fileformats/netcdf/saver.py | 29 ++- lib/iris/fileformats/nimrod_load_rules.py | 11 +- lib/iris/fileformats/pp.py | 33 ++- lib/iris/fileformats/pp_save_rules.py | 3 +- lib/iris/fileformats/rules.py | 10 +- lib/iris/iterate.py | 5 +- lib/iris/pandas.py | 3 +- lib/iris/plot.py | 6 +- lib/iris/tests/graphics/idiff.py | 3 +- lib/iris/tests/test_coding_standards.py | 13 +- 31 files changed, 615 insertions(+), 133 deletions(-) diff --git a/lib/iris/_concatenate.py b/lib/iris/_concatenate.py index c6d58b1622..837afd73f3 100644 --- a/lib/iris/_concatenate.py +++ b/lib/iris/_concatenate.py @@ -16,6 +16,7 @@ import iris.coords import iris.cube +import iris.exceptions from iris.util import array_equal, guess_coord_axis # @@ -998,7 +999,7 @@ def register( raise iris.exceptions.ConcatenateError([msg]) elif not match: msg = f"Found cubes with overlap on concatenate axis {candidate_axis}, skipping concatenation for these cubes" - warnings.warn(msg) + warnings.warn(msg, category=iris.exceptions.IrisUserWarning) # Check for compatible AuxCoords. if match: diff --git a/lib/iris/analysis/_regrid.py b/lib/iris/analysis/_regrid.py index 4592a0ede7..65679cd968 100644 --- a/lib/iris/analysis/_regrid.py +++ b/lib/iris/analysis/_regrid.py @@ -20,6 +20,7 @@ snapshot_grid, ) from iris.analysis._scipy_interpolate import _RegularGridInterpolator +from iris.exceptions import IrisImpossibleUpdateWarning from iris.util import _meshgrid, guess_coord_axis @@ -1136,6 +1137,6 @@ def regrid_reference_surface( "Cannot update aux_factory {!r} because of dropped" " coordinates.".format(factory.name()) ) - warnings.warn(msg) + warnings.warn(msg, category=IrisImpossibleUpdateWarning) return result diff --git a/lib/iris/analysis/calculus.py b/lib/iris/analysis/calculus.py index 75b7d86406..44b1adc580 100644 --- a/lib/iris/analysis/calculus.py +++ b/lib/iris/analysis/calculus.py @@ -24,6 +24,7 @@ import iris.analysis.maths import iris.coord_systems import iris.coords +from iris.exceptions import IrisUserWarning from iris.util import delta __all__ = ["cube_delta", "curl", "differentiate"] @@ -85,7 +86,10 @@ def _construct_midpoint_coord(coord, circular=None): "Construction coordinate midpoints for the '{}' coordinate, " "though it has the attribute 'circular'={}." ) - warnings.warn(msg.format(circular, coord.circular, coord.name())) + warnings.warn( + msg.format(circular, coord.circular, coord.name()), + category=IrisUserWarning, + ) if coord.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(coord) diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index 0d17f0b38a..0fae5bc499 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -401,16 +401,25 @@ def area_weights(cube, normalize=False): cs = cube.coord_system("CoordSystem") if isinstance(cs, iris.coord_systems.GeogCS): if cs.inverse_flattening != 0.0: - warnings.warn("Assuming spherical earth from ellipsoid.") + warnings.warn( + "Assuming spherical earth from ellipsoid.", + category=iris.exceptions.IrisDefaultingWarning, + ) radius_of_earth = cs.semi_major_axis elif isinstance(cs, iris.coord_systems.RotatedGeogCS) and ( cs.ellipsoid is not None ): if cs.ellipsoid.inverse_flattening != 0.0: - warnings.warn("Assuming spherical earth from ellipsoid.") + warnings.warn( + "Assuming spherical earth from ellipsoid.", + category=iris.exceptions.IrisDefaultingWarning, + ) radius_of_earth = cs.ellipsoid.semi_major_axis else: - warnings.warn("Using DEFAULT_SPHERICAL_EARTH_RADIUS.") + warnings.warn( + "Using DEFAULT_SPHERICAL_EARTH_RADIUS.", + category=iris.exceptions.IrisDefaultingWarning, + ) radius_of_earth = DEFAULT_SPHERICAL_EARTH_RADIUS # Get the lon and lat coords and axes @@ -551,7 +560,7 @@ def cosine_latitude_weights(cube): warnings.warn( "Out of range latitude values will be " "clipped to the valid range.", - UserWarning, + category=iris.exceptions.IrisDefaultingWarning, ) points = lat.points l_weights = np.cos(points).clip(0.0, 1.0) @@ -665,7 +674,8 @@ def project(cube, target_proj, nx=None, ny=None): # Assume WGS84 latlon if unspecified warnings.warn( "Coordinate system of latitude and longitude " - "coordinates is not specified. Assuming WGS84 Geodetic." + "coordinates is not specified. Assuming WGS84 Geodetic.", + category=iris.exceptions.IrisDefaultingWarning, ) orig_cs = iris.coord_systems.GeogCS( semi_major_axis=6378137.0, inverse_flattening=298.257223563 @@ -857,7 +867,8 @@ def project(cube, target_proj, nx=None, ny=None): lat_coord.name(), lon_coord.name(), [coord.name() for coord in discarded_coords], - ) + ), + category=iris.exceptions.IrisIgnoringWarning, ) # TODO handle derived coords/aux_factories diff --git a/lib/iris/analysis/geometry.py b/lib/iris/analysis/geometry.py index b246b518d4..9898f4e974 100644 --- a/lib/iris/analysis/geometry.py +++ b/lib/iris/analysis/geometry.py @@ -74,7 +74,7 @@ def _extract_relevant_cube_slice(cube, geometry): except ValueError: warnings.warn( "The geometry exceeds the cube's x dimension at the " "lower end.", - UserWarning, + category=iris.exceptions.IrisGeometryExceedWarning, ) x_min_ix = 0 if x_ascending else x_coord.points.size - 1 @@ -84,7 +84,7 @@ def _extract_relevant_cube_slice(cube, geometry): except ValueError: warnings.warn( "The geometry exceeds the cube's x dimension at the " "upper end.", - UserWarning, + category=iris.exceptions.IrisGeometryExceedWarning, ) x_max_ix = x_coord.points.size - 1 if x_ascending else 0 @@ -94,7 +94,7 @@ def _extract_relevant_cube_slice(cube, geometry): except ValueError: warnings.warn( "The geometry exceeds the cube's y dimension at the " "lower end.", - UserWarning, + category=iris.exceptions.IrisGeometryExceedWarning, ) y_min_ix = 0 if y_ascending else y_coord.points.size - 1 @@ -104,7 +104,7 @@ def _extract_relevant_cube_slice(cube, geometry): except ValueError: warnings.warn( "The geometry exceeds the cube's y dimension at the " "upper end.", - UserWarning, + category=iris.exceptions.IrisGeometryExceedWarning, ) y_max_ix = y_coord.points.size - 1 if y_ascending else 0 diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index b77c6cd80f..5e180c6ee2 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -988,7 +988,8 @@ def _broadcast_cube_coord_data(cube, other, operation_name, dim=None): if other.has_bounds(): warnings.warn( "Using {!r} with a bounded coordinate is not well " - "defined; ignoring bounds.".format(operation_name) + "defined; ignoring bounds.".format(operation_name), + category=iris.exceptions.IrisIgnoringBoundsWarning, ) points = other.points diff --git a/lib/iris/aux_factory.py b/lib/iris/aux_factory.py index f49de62b3f..323c89e3fb 100644 --- a/lib/iris/aux_factory.py +++ b/lib/iris/aux_factory.py @@ -21,6 +21,7 @@ metadata_manager_factory, ) import iris.coords +from iris.exceptions import IrisIgnoringBoundsWarning class AuxCoordFactory(CFVariableMixin, metaclass=ABCMeta): @@ -441,7 +442,9 @@ def _check_dependencies(pressure_at_top, sigma, surface_air_pressure): f"Coordinate '{coord.name()}' has bounds. These will " "be disregarded" ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Check units if sigma.units.is_unknown(): @@ -522,7 +525,8 @@ def make_coord(self, coord_dims_func): if pressure_at_top.shape[-1:] not in [(), (1,)]: warnings.warn( "Pressure at top coordinate has bounds. These are being " - "disregarded" + "disregarded", + category=IrisIgnoringBoundsWarning, ) pressure_at_top_pts = nd_points_by_key["pressure_at_top"] bds_shape = list(pressure_at_top_pts.shape) + [1] @@ -530,7 +534,8 @@ def make_coord(self, coord_dims_func): if surface_air_pressure.shape[-1:] not in [(), (1,)]: warnings.warn( "Surface pressure coordinate has bounds. These are being " - "disregarded" + "disregarded", + category=IrisIgnoringBoundsWarning, ) surface_air_pressure_pts = nd_points_by_key[ "surface_air_pressure" @@ -595,7 +600,9 @@ def __init__(self, delta=None, sigma=None, orography=None): "Orography coordinate {!r} has bounds." " These will be disregarded.".format(orography.name()) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) self.delta = delta self.sigma = sigma @@ -684,7 +691,7 @@ def make_coord(self, coord_dims_func): warnings.warn( "Orography coordinate has bounds. " "These are being disregarded.", - UserWarning, + category=IrisIgnoringBoundsWarning, stacklevel=2, ) orography_pts = nd_points_by_key["orography"] @@ -739,7 +746,9 @@ def update(self, old_coord, new_coord=None): "Orography coordinate {!r} has bounds." " These will be disregarded.".format(new_coord.name()) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) self.orography = new_coord @@ -806,7 +815,9 @@ def _check_dependencies(delta, sigma, surface_air_pressure): "Surface pressure coordinate {!r} has bounds. These will" " be disregarded.".format(surface_air_pressure.name()) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Check units. if sigma is not None and sigma.units.is_unknown(): @@ -898,7 +909,8 @@ def make_coord(self, coord_dims_func): if surface_air_pressure.shape[-1:] not in [(), (1,)]: warnings.warn( "Surface pressure coordinate has bounds. " - "These are being disregarded." + "These are being disregarded.", + category=IrisIgnoringBoundsWarning, ) surface_air_pressure_pts = nd_points_by_key[ "surface_air_pressure" @@ -1012,7 +1024,9 @@ def _check_dependencies(sigma, eta, depth, depth_c, nsigma, zlev): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) for coord, term in ((depth_c, "depth_c"), (nsigma, "nsigma")): if coord is not None and coord.shape != (1,): @@ -1187,7 +1201,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Swap bounds with points. bds_shape = list(nd_points_by_key[key].shape) + [1] bounds = nd_points_by_key[key].reshape(bds_shape) @@ -1268,7 +1284,9 @@ def _check_dependencies(sigma, eta, depth): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Check units. if sigma is not None and sigma.units.is_unknown(): @@ -1349,7 +1367,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Swap bounds with points. bds_shape = list(nd_points_by_key[key].shape) + [1] bounds = nd_points_by_key[key].reshape(bds_shape) @@ -1444,7 +1464,9 @@ def _check_dependencies(s, c, eta, depth, depth_c): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) if depth_c is not None and depth_c.shape != (1,): msg = ( @@ -1543,7 +1565,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Swap bounds with points. bds_shape = list(nd_points_by_key[key].shape) + [1] bounds = nd_points_by_key[key].reshape(bds_shape) @@ -1637,7 +1661,9 @@ def _check_dependencies(s, eta, depth, a, b, depth_c): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) coords = ((a, "a"), (b, "b"), (depth_c, "depth_c")) for coord, term in coords: @@ -1740,7 +1766,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Swap bounds with points. bds_shape = list(nd_points_by_key[key].shape) + [1] bounds = nd_points_by_key[key].reshape(bds_shape) @@ -1839,7 +1867,9 @@ def _check_dependencies(s, c, eta, depth, depth_c): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) if depth_c is not None and depth_c.shape != (1,): msg = ( @@ -1938,7 +1968,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Swap bounds with points. bds_shape = list(nd_points_by_key[key].shape) + [1] bounds = nd_points_by_key[key].reshape(bds_shape) diff --git a/lib/iris/config.py b/lib/iris/config.py index 79d141e53f..03d3d363a6 100644 --- a/lib/iris/config.py +++ b/lib/iris/config.py @@ -36,6 +36,8 @@ import os.path import warnings +import iris.exceptions + def get_logger( name, datefmt=None, fmt=None, level=None, propagate=None, handler=True @@ -145,7 +147,10 @@ def get_dir_option(section, option, default=None): "Ignoring config item {!r}:{!r} (section:option) as {!r}" " is not a valid directory path." ) - warnings.warn(msg.format(section, option, c_path)) + warnings.warn( + msg.format(section, option, c_path), + category=iris.exceptions.IrisIgnoringWarning, + ) return path @@ -251,7 +256,10 @@ def __setattr__(self, name, value): "Attempting to set invalid value {!r} for " "attribute {!r}. Defaulting to {!r}." ) - warnings.warn(wmsg.format(value, name, good_value)) + warnings.warn( + wmsg.format(value, name, good_value), + category=iris.exceptions.IrisDefaultingWarning, + ) value = good_value self.__dict__[name] = value diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index edf0c1871b..ed39a159b8 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -15,6 +15,8 @@ import cartopy.crs as ccrs import numpy as np +import iris.exceptions + def _arg_default(value, default, cast_as=float): """Apply a default value and type for an optional kwarg.""" @@ -449,7 +451,7 @@ def inverse_flattening(self, value): "the GeogCS object. To change other properties set them explicitly" " or create a new GeogCS instance." ) - warnings.warn(wmsg, UserWarning) + warnings.warn(wmsg, iris.exceptions.IrisUserWarning) value = float(value) self._inverse_flattening = value @@ -818,7 +820,8 @@ def as_cartopy_crs(self): warnings.warn( "Discarding false_easting and false_northing that are " - "not used by Cartopy." + "not used by Cartopy.", + category=iris.exceptions.IrisDefaultingWarning, ) return ccrs.Orthographic( diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 1a6e8d4e6a..2c0eb6f5f7 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -2057,7 +2057,8 @@ def contiguous_bounds(self): if self.ndim == 1: warnings.warn( "Coordinate {!r} is not bounded, guessing " - "contiguous bounds.".format(self.name()) + "contiguous bounds.".format(self.name()), + category=iris.exceptions.IrisGuessBoundsWarning, ) bounds = self._guess_bounds() elif self.ndim == 2: @@ -2222,9 +2223,12 @@ def serialize(x): if self.ndim > 1: msg = ( "Collapsing a multi-dimensional coordinate. " - "Metadata may not be fully descriptive for {!r}." + "Metadata may not be fully descriptive for {!r}.", + ) + warnings.warn( + msg.format(self.name()), + category=iris.exceptions.IrisVagueMetadataWarning, ) - warnings.warn(msg.format(self.name())) else: try: self._sanity_check_bounds() @@ -2234,7 +2238,10 @@ def serialize(x): "Metadata may not be fully descriptive for {!r}. " "Ignoring bounds." ) - warnings.warn(msg.format(str(exc), self.name())) + warnings.warn( + msg.format(str(exc), self.name()), + category=iris.exceptions.IrisVagueMetadataWarning, + ) self.bounds = None else: if not self.is_contiguous(): @@ -2242,7 +2249,10 @@ def serialize(x): "Collapsing a non-contiguous coordinate. " "Metadata may not be fully descriptive for {!r}." ) - warnings.warn(msg.format(self.name())) + warnings.warn( + msg.format(self.name()), + category=iris.exceptions.IrisVagueMetadataWarning, + ) if self.has_bounds(): item = self.core_bounds() diff --git a/lib/iris/cube.py b/lib/iris/cube.py index aec80dce47..60fdbc9c94 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -3857,7 +3857,10 @@ def collapsed(self, coords, aggregator, **kwargs): ] if lat_match: for coord in lat_match: - warnings.warn(msg.format(coord.name())) + warnings.warn( + msg.format(coord.name()), + category=iris.exceptions.IrisUserWarning, + ) # Determine the dimensions we need to collapse (and those we don't) if aggregator.cell_method == "peak": @@ -4444,7 +4447,8 @@ def rolling_window(self, coord, aggregator, window, **kwargs): if coord_.has_bounds(): warnings.warn( "The bounds of coordinate %r were ignored in " - "the rolling window operation." % coord_.name() + "the rolling window operation." % coord_.name(), + category=iris.exceptions.IrisIgnoringBoundsWarning, ) if coord_.ndim != 1: diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index bb69f213ab..f89239f341 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -195,25 +195,211 @@ class IrisUserWarning(UserWarning): pass -class IrisNimrodTranslationWarning(IrisUserWarning): +class IrisLoadWarning(IrisUserWarning): + """Any warning relating to loading.""" + + pass + + +class IrisSaveWarning(IrisUserWarning): + """Any warning relating to saving.""" + + pass + + +class IrisCfWarning(IrisUserWarning): + """Any warning relating to :term:`CF Conventions` .""" + + pass + + +class IrisIgnoringWarning(IrisUserWarning): + """ + Any warning that involves an Iris operation not using some information. + + E.g. :class:`~iris.aux_factory.AuxCoordFactory` generation disregarding + bounds. + """ + + pass + + +class IrisDefaultingWarning(IrisUserWarning): + """ + Any warning that involves Iris changing invalid/missing information. + + E.g. creating a :class:`~iris.coords.AuxCoord` from an invalid + :class:`~iris.coords.DimCoord` definition. + """ + + pass + + +class IrisVagueMetadataWarning(IrisUserWarning): + """Warnings where object metadata may not be fully descriptive.""" + + pass + + +class IrisUnsupportedPlottingWarning(IrisUserWarning): + """Warnings where support for a plotting module/function is not guaranteed.""" + + pass + + +class IrisImpossibleUpdateWarning(IrisUserWarning): + """ + Warnings where it is not possible to update an object. + + Mainly generated during regridding where the necessary information for + updating an :class:`~iris.aux_factory.AuxCoordFactory` is no longer + present. + """ + + pass + + +class IrisGeometryExceedWarning(IrisUserWarning): + """:mod:`iris.analysis.geometry` warnings about geometry exceeding dimensions.""" + + pass + + +class IrisMaskValueMatchWarning(IrisUserWarning): + """Warnings where the value representing masked data is actually present in data.""" + + pass + + +######## + + +class IrisCfLoadWarning(IrisCfWarning, IrisLoadWarning): + """Any warning relating to both loading and :term:`CF Conventions` .""" + + pass + + +class IrisCfSaveWarning(IrisCfWarning, IrisSaveWarning): + """Any warning relating to both saving and :term:`CF Conventions` .""" + + pass + + +class IrisCfInvalidCoordParamWarning(IrisCfLoadWarning): + """ + Warnings where incorrect information for CF coord construction is in a file. + """ + + pass + + +class IrisCfMissingVarWarning(IrisCfLoadWarning): + """ + Warnings where a CF variable references another variable that is not in the file. + """ + + pass + + +class IrisCfLabelVarWarning(IrisCfLoadWarning, IrisIgnoringWarning): + """ + Warnings where a CF string/label variable is being used inappropriately. + """ + + pass + + +class IrisCfNonSpanningVarWarning(IrisCfLoadWarning, IrisIgnoringWarning): + """ + Warnings where a CF variable is ignored because it does not span the required dimension. + """ + + pass + + +######## + + +class IrisIgnoringBoundsWarning(IrisIgnoringWarning): + """ + Warnings where bounds information has not been used by an Iris operation. + """ + + pass + + +class IrisCannotAddWarning(IrisIgnoringWarning): + """ + Warnings where a member object cannot be added to a :class:`~iris.cube.Cube` . + """ + + pass + + +class IrisGuessBoundsWarning(IrisDefaultingWarning): + """ + Warnings where Iris has filled absent bounds information with a best estimate. + """ + + pass + + +class IrisPpClimModifiedWarning(IrisSaveWarning, IrisDefaultingWarning): + """ + Warnings where a climatology has been modified while saving :term:`Post Processing (PP) Format` . + """ + + pass + + +class IrisFactoryCoordNotFoundWarning(IrisLoadWarning): + """ + Warnings where a referenced factory coord can not be found when loading a variable in :term:`NetCDF Format`. + """ + + pass + + +class IrisNimrodTranslationWarning(IrisLoadWarning): """ For unsupported vertical coord types in :mod:`iris.file_formats.nimrod_load_rules`. + + (Pre-dates the full categorisation of Iris UserWarnings). """ pass -class IrisUnknownCellMethodWarning(IrisUserWarning): +class IrisUnknownCellMethodWarning(IrisCfLoadWarning): """ - If a loaded cell method is not one the method names known to Iris. + If a loaded :class:`~iris.coords.CellMethod` is not one the method names known to Iris. + + (Pre-dates the full categorisation of Iris UserWarnings). """ pass -class IrisSaverFillValueWarning(IrisUserWarning): +class IrisSaverFillValueWarning(IrisMaskValueMatchWarning, IrisSaveWarning): """ - For fill value complications during Iris file saving. + For fill value complications during Iris file saving :term:`NetCDF Format`. + + (Pre-dates the full categorisation of Iris UserWarnings). """ pass + + +######## + + +def warning_combo(*warning_classes): + """Return a one-off combination of warning classes to help user filtering.""" + + # TODO: full docstring (with example?) + # TODO: type hinting + class _WarningCombo(*warning_classes): + pass + + return _WarningCombo diff --git a/lib/iris/experimental/regrid.py b/lib/iris/experimental/regrid.py index 76c6002d2b..d5fa7c6f72 100644 --- a/lib/iris/experimental/regrid.py +++ b/lib/iris/experimental/regrid.py @@ -43,6 +43,7 @@ import iris.analysis.cartography import iris.coord_systems import iris.cube +from iris.exceptions import IrisImpossibleUpdateWarning from iris.util import _meshgrid wmsg = ( @@ -538,7 +539,7 @@ def regrid_reference_surface( "Cannot update aux_factory {!r} because of dropped" " coordinates.".format(factory.name()) ) - warnings.warn(msg) + warnings.warn(msg, category=IrisImpossibleUpdateWarning) return result def __call__(self, src_cube): diff --git a/lib/iris/experimental/ugrid/cf.py b/lib/iris/experimental/ugrid/cf.py index 86b76c7a75..42c1cfd0a3 100644 --- a/lib/iris/experimental/ugrid/cf.py +++ b/lib/iris/experimental/ugrid/cf.py @@ -12,6 +12,7 @@ """ import warnings +from ...exceptions import IrisCfLabelVarWarning, IrisCfMissingVarWarning from ...fileformats import cf from .mesh import Connectivity @@ -65,7 +66,9 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"{nc_var_name}" ) if warn: - warnings.warn(message) + warnings.warn( + message, category=IrisCfMissingVarWarning + ) else: # Restrict to non-string type i.e. not a # CFLabelVariable. @@ -80,7 +83,9 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"CF-netCDF label variable." ) if warn: - warnings.warn(message) + warnings.warn( + message, category=IrisCfLabelVarWarning + ) return result @@ -136,7 +141,10 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"variable {nc_var_name}" ) if warn: - warnings.warn(message) + warnings.warn( + message, + category=IrisCfMissingVarWarning, + ) else: # Restrict to non-string type i.e. not a # CFLabelVariable. @@ -154,7 +162,10 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"CF-netCDF label variable." ) if warn: - warnings.warn(message) + warnings.warn( + message, + category=IrisCfLabelVarWarning, + ) return result @@ -211,7 +222,9 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"referenced by netCDF variable {nc_var_name}" ) if warn: - warnings.warn(message) + warnings.warn( + message, category=IrisCfMissingVarWarning + ) else: # Restrict to non-string type i.e. not a # CFLabelVariable. @@ -226,7 +239,9 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"variable." ) if warn: - warnings.warn(message) + warnings.warn( + message, category=IrisCfLabelVarWarning + ) return result diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index d2670ac690..f16b3d7881 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -19,6 +19,12 @@ from ...config import get_logger from ...coords import AuxCoord +from ...exceptions import ( + IrisCfWarning, + IrisDefaultingWarning, + IrisIgnoringWarning, + warning_combo, +) from ...fileformats._nc_load_rules.helpers import get_attr_units, get_names from ...fileformats.netcdf import loader as nc_loader from ...io import decode_uri, expand_filespecs @@ -351,7 +357,10 @@ def _build_mesh(cf, mesh_var, file_path): ) if cf_role_message: cf_role_message += " Correcting to 'mesh_topology'." - warnings.warn(cf_role_message) + warnings.warn( + cf_role_message, + warning_combo(IrisCfWarning, IrisDefaultingWarning), + ) if hasattr(mesh_var, "volume_node_connectivity"): topology_dimension = 3 @@ -369,7 +378,7 @@ def _build_mesh(cf, mesh_var, file_path): f" : *Assuming* topology_dimension={topology_dimension}" ", consistent with the attached connectivities." ) - warnings.warn(msg) + warnings.warn(msg, warning_combo(IrisCfWarning, IrisDefaultingWarning)) else: quoted_topology_dimension = mesh_var.topology_dimension if quoted_topology_dimension != topology_dimension: @@ -381,7 +390,12 @@ def _build_mesh(cf, mesh_var, file_path): f"{quoted_topology_dimension}" " -- ignoring this as it is inconsistent." ) - warnings.warn(msg) + warnings.warn( + msg, + warning_combo( + IrisCfWarning, IrisDefaultingWarning, IrisIgnoringWarning + ), + ) node_dimension = None edge_dimension = getattr(mesh_var, "edge_dimension", None) diff --git a/lib/iris/fileformats/_ff.py b/lib/iris/fileformats/_ff.py index 2545bc39ae..f65722c89b 100644 --- a/lib/iris/fileformats/_ff.py +++ b/lib/iris/fileformats/_ff.py @@ -13,7 +13,12 @@ import numpy as np -from iris.exceptions import NotYetImplementedError +from iris.exceptions import ( + IrisDefaultingWarning, + IrisLoadWarning, + NotYetImplementedError, + warning_combo, +) from iris.fileformats._ff_cross_references import STASH_TRANS from . import pp @@ -431,7 +436,8 @@ def grid(self): grid_class = NewDynamics warnings.warn( "Staggered grid type: {} not currently interpreted, assuming " - "standard C-grid".format(self.grid_staggering) + "standard C-grid".format(self.grid_staggering), + category=warning_combo(IrisDefaultingWarning, IrisLoadWarning), ) grid = grid_class( self.column_dependent_constants, @@ -554,7 +560,7 @@ def range_order(range1, range2, resolution): "may be incorrect, not having taken into account the " "boundary size." ) - warnings.warn(msg) + warnings.warn(msg, IrisLoadWarning) else: range2 = field_dim[0] - res_low range1 = field_dim[0] - halo_dim * res_low @@ -628,7 +634,8 @@ def _adjust_field_for_lbc(self, field): "The LBC has a bdy less than 0. No " "case has previously been seen of " "this, and the decompression may be " - "erroneous." + "erroneous.", + category=IrisLoadWarning, ) field.bzx -= field.bdx * boundary_packing.x_halo field.bzy -= field.bdy * boundary_packing.y_halo @@ -741,7 +748,10 @@ def _extract_field(self): "which has not been explicitly " "handled by the fieldsfile loader." " Assuming the data is on a P grid" - ".".format(stash, subgrid) + ".".format(stash, subgrid), + category=warning_combo( + IrisDefaultingWarning, IrisLoadWarning + ), ) field.x, field.y = grid.vectors(subgrid) @@ -757,14 +767,20 @@ def _extract_field(self): "STASH to grid type mapping. Picking the P " "position as the cell type".format(stash) ) - warnings.warn(msg) + warnings.warn( + msg, + category=warning_combo( + IrisDefaultingWarning, IrisLoadWarning + ), + ) field.bzx, field.bdx = grid.regular_x(subgrid) field.bzy, field.bdy = grid.regular_y(subgrid) field.bplat = grid.pole_lat field.bplon = grid.pole_lon elif no_x or no_y: warnings.warn( - "Partially missing X or Y coordinate values." + "Partially missing X or Y coordinate values.", + category=IrisLoadWarning, ) # Check for LBC fields. @@ -810,7 +826,9 @@ def _extract_field(self): "Input field skipped as PPField creation failed :" " error = {!r}" ) - warnings.warn(msg.format(str(valerr))) + warnings.warn( + msg.format(str(valerr)), category=IrisLoadWarning + ) def __iter__(self): return pp._interpret_fields(self._extract_field()) diff --git a/lib/iris/fileformats/_nc_load_rules/actions.py b/lib/iris/fileformats/_nc_load_rules/actions.py index 09237d3f11..54dd107e87 100644 --- a/lib/iris/fileformats/_nc_load_rules/actions.py +++ b/lib/iris/fileformats/_nc_load_rules/actions.py @@ -44,6 +44,8 @@ import warnings from iris.config import get_logger +import iris.exceptions +from iris.exceptions import warning_combo import iris.fileformats.cf import iris.fileformats.pp as pp @@ -471,7 +473,13 @@ def action_formula_type(engine, formula_root_fact): succeed = False rule_name += f"(FAILED - unrecognised formula type = {formula_type!r})" msg = f"Ignored formula of unrecognised type: {formula_type!r}." - warnings.warn(msg) + warnings.warn( + msg, + category=warning_combo( + iris.exceptions.IrisCfLoadWarning, + iris.exceptions.IrisIgnoringWarning, + ), + ) if succeed: # Check we don't already have one. existing_type = engine.requires.get("formula_type") @@ -486,7 +494,13 @@ def action_formula_type(engine, formula_root_fact): f"Formula of type ={formula_type!r} " f"overrides another of type ={existing_type!r}.)" ) - warnings.warn(msg) + warnings.warn( + msg, + category=warning_combo( + iris.exceptions.IrisLoadWarning, + iris.exceptions.IrisIgnoringWarning, + ), + ) rule_name += f"_{formula_type}" # Set 'requires' info for iris.fileformats.netcdf._load_aux_factory. engine.requires["formula_type"] = formula_type diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index e696068e67..945078784b 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -28,6 +28,7 @@ import iris.coord_systems import iris.coords import iris.exceptions +from iris.exceptions import warning_combo import iris.fileformats.cf as cf import iris.fileformats.netcdf from iris.fileformats.netcdf.loader import _get_cf_var_data @@ -256,7 +257,11 @@ def _split_cell_methods(nc_cell_methods: str) -> List[re.Match]: "Cell methods may be incorrectly parsed due to mismatched " "brackets" ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, + category=iris.exceptions.IrisCfLoadWarning, + stacklevel=2, + ) if bracket_depth > 0 and ind in name_start_inds: name_start_inds.remove(ind) @@ -275,7 +280,9 @@ def _split_cell_methods(nc_cell_methods: str) -> List[re.Match]: msg = ( f"Failed to fully parse cell method string: {nc_cell_methods}" ) - warnings.warn(msg, UserWarning, stacklevel=2) + warnings.warn( + msg, category=iris.exceptions.IrisCfLoadWarning, stacklevel=2 + ) continue nc_cell_methods_matches.append(nc_cell_method_match) @@ -394,7 +401,6 @@ def parse_cell_methods(nc_cell_methods): ################################################################################ def build_cube_metadata(engine): """Add the standard meta data to the cube.""" - cf_var = engine.cf_var cube = engine.cube @@ -441,7 +447,13 @@ def build_cube_metadata(engine): cube.attributes[str(attr_name)] = attr_value except ValueError as e: msg = "Skipping global attribute {!r}: {}" - warnings.warn(msg.format(attr_name, str(e))) + warnings.warn( + msg.format(attr_name, str(e)), + category=warning_combo( + iris.exceptions.IrisIgnoringWarning, + iris.exceptions.IrisLoadWarning, + ), + ) ################################################################################ @@ -517,7 +529,10 @@ def build_rotated_coordinate_system(engine, cf_grid_var): cf_grid_var, CF_ATTR_GRID_NORTH_POLE_LON, 0.0 ) if north_pole_latitude is None or north_pole_longitude is None: - warnings.warn("Rotated pole position is not fully specified") + warnings.warn( + "Rotated pole position is not fully specified", + category=iris.exceptions.IrisCfLoadWarning, + ) north_pole_grid_lon = getattr( cf_grid_var, CF_ATTR_GRID_NORTH_POLE_GRID_LON, 0.0 @@ -864,7 +879,13 @@ def get_attr_units(cf_var, attributes): msg = "Ignoring netCDF variable {!r} invalid units {!r}".format( cf_var.cf_name, attr_units ) - warnings.warn(msg) + warnings.warn( + msg, + warning_combo( + iris.exceptions.IrisIgnoringWarning, + iris.exceptions.IrisCfLoadWarning, + ), + ) attributes["invalid_units"] = attr_units attr_units = UNKNOWN_UNIT_STRING @@ -953,7 +974,11 @@ def get_cf_bounds_var(cf_coord_var): if attr_bounds is not None and attr_climatology is not None: warnings.warn( "Ignoring climatology in favour of bounds attribute " - "on NetCDF variable {!r}.".format(cf_coord_var.cf_name) + "on NetCDF variable {!r}.".format(cf_coord_var.cf_name), + category=warning_combo( + iris.exceptions.IrisCfLoadWarning, + iris.exceptions.IrisIgnoringWarning, + ), ) return cf_bounds_var, climatological @@ -1012,7 +1037,13 @@ def build_dimension_coordinate( if ma.is_masked(points_data): points_data = ma.filled(points_data) msg = "Gracefully filling {!r} dimension coordinate masked points" - warnings.warn(msg.format(str(cf_coord_var.cf_name))) + warnings.warn( + msg.format(str(cf_coord_var.cf_name)), + category=warning_combo( + iris.exceptions.IrisDefaultingWarning, + iris.exceptions.IrisLoadWarning, + ), + ) # Get any coordinate bounds. cf_bounds_var, climatological = get_cf_bounds_var(cf_coord_var) @@ -1022,7 +1053,13 @@ def build_dimension_coordinate( if ma.is_masked(bounds_data): bounds_data = ma.filled(bounds_data) msg = "Gracefully filling {!r} dimension coordinate masked bounds" - warnings.warn(msg.format(str(cf_coord_var.cf_name))) + warnings.warn( + msg.format(str(cf_coord_var.cf_name)), + category=warning_combo( + iris.exceptions.IrisDefaultingWarning, + iris.exceptions.IrisLoadWarning, + ), + ) # Handle transposed bounds where the vertex dimension is not # the last one. Test based on shape to support different # dimension names. @@ -1087,7 +1124,13 @@ def build_dimension_coordinate( "Failed to create {name!r} dimension coordinate: {error}\n" "Gracefully creating {name!r} auxiliary coordinate instead." ) - warnings.warn(msg.format(name=str(cf_coord_var.cf_name), error=e_msg)) + warnings.warn( + msg.format(name=str(cf_coord_var.cf_name), error=e_msg), + category=warning_combo( + iris.exceptions.IrisCfLoadWarning, + iris.exceptions.IrisDefaultingWarning, + ), + ) coord = iris.coords.AuxCoord( points_data, standard_name=standard_name, @@ -1102,7 +1145,10 @@ def build_dimension_coordinate( try: cube.add_aux_coord(coord, data_dims) except iris.exceptions.CannotAddError as e_msg: - warnings.warn(coord_skipped_msg.format(error=e_msg)) + warnings.warn( + coord_skipped_msg.format(error=e_msg), + category=iris.exceptions.IrisCannotAddWarning, + ) coord_skipped = True else: # Add the dimension coordinate to the cube. @@ -1113,7 +1159,10 @@ def build_dimension_coordinate( # Scalar coords are placed in the aux_coords container. cube.add_aux_coord(coord, data_dims) except iris.exceptions.CannotAddError as e_msg: - warnings.warn(coord_skipped_msg.format(error=e_msg)) + warnings.warn( + coord_skipped_msg.format(error=e_msg), + category=iris.exceptions.IrisCannotAddWarning, + ) coord_skipped = True if not coord_skipped: @@ -1191,7 +1240,10 @@ def build_auxiliary_coordinate( cube.add_aux_coord(coord, data_dims) except iris.exceptions.CannotAddError as e_msg: msg = "{name!r} coordinate not added to Cube: {error}" - warnings.warn(msg.format(name=str(cf_coord_var.cf_name), error=e_msg)) + warnings.warn( + msg.format(name=str(cf_coord_var.cf_name), error=e_msg), + category=iris.exceptions.IrisCannotAddWarning, + ) else: # Make a list with names, stored on the engine, so we can find them all later. engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name)) @@ -1242,7 +1294,10 @@ def build_cell_measures(engine, cf_cm_var): cube.add_cell_measure(cell_measure, data_dims) except iris.exceptions.CannotAddError as e_msg: msg = "{name!r} cell measure not added to Cube: {error}" - warnings.warn(msg.format(name=str(cf_cm_var.cf_name), error=e_msg)) + warnings.warn( + msg.format(name=str(cf_cm_var.cf_name), error=e_msg), + category=iris.exceptions.IrisCannotAddWarning, + ) else: # Make a list with names, stored on the engine, so we can find them all later. engine.cube_parts["cell_measures"].append( @@ -1291,7 +1346,10 @@ def build_ancil_var(engine, cf_av_var): cube.add_ancillary_variable(av, data_dims) except iris.exceptions.CannotAddError as e_msg: msg = "{name!r} ancillary variable not added to Cube: {error}" - warnings.warn(msg.format(name=str(cf_av_var.cf_name), error=e_msg)) + warnings.warn( + msg.format(name=str(cf_av_var.cf_name), error=e_msg), + category=iris.exceptions.IrisCannotAddWarning, + ) else: # Make a list with names, stored on the engine, so we can find them all later. engine.cube_parts["ancillary_variables"].append( @@ -1508,7 +1566,8 @@ def has_supported_mercator_parameters(engine, cf_name): ): warnings.warn( "It does not make sense to provide both " - '"scale_factor_at_projection_origin" and "standard_parallel".' + '"scale_factor_at_projection_origin" and "standard_parallel".', + category=iris.exceptions.IrisCfInvalidCoordParamWarning, ) is_valid = False @@ -1538,7 +1597,10 @@ def has_supported_polar_stereographic_parameters(engine, cf_name): latitude_of_projection_origin != 90 and latitude_of_projection_origin != -90 ): - warnings.warn('"latitude_of_projection_origin" must be +90 or -90.') + warnings.warn( + '"latitude_of_projection_origin" must be +90 or -90.', + category=iris.exceptions.IrisCfInvalidCoordParamWarning, + ) is_valid = False if ( @@ -1547,14 +1609,16 @@ def has_supported_polar_stereographic_parameters(engine, cf_name): ): warnings.warn( "It does not make sense to provide both " - '"scale_factor_at_projection_origin" and "standard_parallel".' + '"scale_factor_at_projection_origin" and "standard_parallel".', + category=iris.exceptions.IrisCfInvalidCoordParamWarning, ) is_valid = False if scale_factor_at_projection_origin is None and standard_parallel is None: warnings.warn( 'One of "scale_factor_at_projection_origin" and ' - '"standard_parallel" is required.' + '"standard_parallel" is required.', + category=iris.exceptions.IrisCfInvalidCoordParamWarning, ) is_valid = False diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index 2ed01846bd..d093469429 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -23,6 +23,7 @@ import numpy as np import numpy.ma as ma +import iris.exceptions from iris.fileformats.netcdf import _thread_safe_nc import iris.util @@ -280,7 +281,10 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if name not in variables: if warn: message = "Missing CF-netCDF ancillary data variable %r, referenced by netCDF variable %r" - warnings.warn(message % (name, nc_var_name)) + warnings.warn( + message % (name, nc_var_name), + category=iris.exceptions.IrisCfMissingVarWarning, + ) else: result[name] = CFAncillaryDataVariable( name, variables[name] @@ -323,7 +327,10 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if name not in variables: if warn: message = "Missing CF-netCDF auxiliary coordinate variable %r, referenced by netCDF variable %r" - warnings.warn(message % (name, nc_var_name)) + warnings.warn( + message % (name, nc_var_name), + category=iris.exceptions.IrisCfMissingVarWarning, + ) else: # Restrict to non-string type i.e. not a CFLabelVariable. if not _is_str_dtype(variables[name]): @@ -369,7 +376,10 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if name not in variables: if warn: message = "Missing CF-netCDF boundary variable %r, referenced by netCDF variable %r" - warnings.warn(message % (name, nc_var_name)) + warnings.warn( + message % (name, nc_var_name), + category=iris.exceptions.IrisCfMissingVarWarning, + ) else: result[name] = CFBoundaryVariable( name, variables[name] @@ -441,7 +451,10 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if name not in variables: if warn: message = "Missing CF-netCDF climatology variable %r, referenced by netCDF variable %r" - warnings.warn(message % (name, nc_var_name)) + warnings.warn( + message % (name, nc_var_name), + category=iris.exceptions.IrisCfMissingVarWarning, + ) else: result[name] = CFClimatologyVariable( name, variables[name] @@ -582,7 +595,8 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if warn: message = "Missing CF-netCDF formula term variable %r, referenced by netCDF variable %r" warnings.warn( - message % (variable_name, nc_var_name) + message % (variable_name, nc_var_name), + category=iris.exceptions.IrisCfMissingVarWarning, ) else: if variable_name not in result: @@ -646,7 +660,10 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if name not in variables: if warn: message = "Missing CF-netCDF grid mapping variable %r, referenced by netCDF variable %r" - warnings.warn(message % (name, nc_var_name)) + warnings.warn( + message % (name, nc_var_name), + category=iris.exceptions.IrisCfMissingVarWarning, + ) else: result[name] = CFGridMappingVariable( name, variables[name] @@ -685,7 +702,10 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if name not in variables: if warn: message = "Missing CF-netCDF label variable %r, referenced by netCDF variable %r" - warnings.warn(message % (name, nc_var_name)) + warnings.warn( + message % (name, nc_var_name), + category=iris.exceptions.IrisCfMissingVarWarning, + ) else: # Register variable, but only allow string type. var = variables[name] @@ -857,7 +877,8 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if warn: message = "Missing CF-netCDF measure variable %r, referenced by netCDF variable %r" warnings.warn( - message % (variable_name, nc_var_name) + message % (variable_name, nc_var_name), + category=iris.exceptions.IrisCfMissingVarWarning, ) else: result[variable_name] = CFMeasureVariable( @@ -1069,7 +1090,8 @@ def __init__(self, file_source, warn=False, monotonic=False): ]: warnings.warn( "Optimise CF-netCDF loading by converting data from NetCDF3 " - 'to NetCDF4 file format using the "nccopy" command.' + 'to NetCDF4 file format using the "nccopy" command.', + iris.exceptions.IrisLoadWarning, ) self._check_monotonic = monotonic @@ -1210,7 +1232,10 @@ def _build(cf_variable): cf_variable.dimensions, ) ) - warnings.warn(msg) + warnings.warn( + msg, + category=iris.exceptions.IrisCfNonSpanningVarWarning, + ) # Build CF data variable relationships. if isinstance(cf_variable, CFDataVariable): @@ -1261,7 +1286,10 @@ def _build(cf_variable): cf_variable.dimensions, ) ) - warnings.warn(msg) + warnings.warn( + msg, + category=iris.exceptions.IrisCfNonSpanningVarWarning, + ) # Add the CF group to the variable. cf_variable.cf_group = cf_group diff --git a/lib/iris/fileformats/name_loaders.py b/lib/iris/fileformats/name_loaders.py index 0189a8806f..123d2eb4cf 100644 --- a/lib/iris/fileformats/name_loaders.py +++ b/lib/iris/fileformats/name_loaders.py @@ -17,7 +17,7 @@ import iris.coord_systems from iris.coords import AuxCoord, CellMethod, DimCoord import iris.cube -from iris.exceptions import TranslationError +from iris.exceptions import IrisLoadWarning, TranslationError import iris.util EARTH_RADIUS = 6371229.0 @@ -273,7 +273,7 @@ def _parse_units(units): try: units = cf_units.Unit(units) except ValueError: - warnings.warn("Unknown units: {!r}".format(units)) + warnings.warn("Unknown units: {!r}".format(units), IrisLoadWarning) units = cf_units.Unit(None) return units diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py index 20d255ea44..caf300bf01 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -33,6 +33,7 @@ import iris.coord_systems import iris.coords import iris.exceptions +from iris.exceptions import warning_combo import iris.fileformats.cf from iris.fileformats.netcdf import _thread_safe_nc from iris.fileformats.netcdf.saver import _CF_ATTRS @@ -352,7 +353,8 @@ def coord_from_term(term): return coord warnings.warn( "Unable to find coordinate for variable " - "{!r}".format(name) + "{!r}".format(name), + category=iris.exceptions.IrisFactoryCoordNotFoundWarning, ) if formula_type == "atmosphere_sigma_coordinate": @@ -393,7 +395,13 @@ def coord_from_term(term): coord_p0.name() ) ) - warnings.warn(msg) + warnings.warn( + msg, + category=warning_combo( + iris.exceptions.IrisIgnoringBoundsWarning, + iris.exceptions.IrisLoadWarning, + ), + ) coord_a = coord_from_term("a") if coord_a is not None: if coord_a.units.is_unknown(): @@ -584,7 +592,10 @@ def load_cubes(file_sources, callback=None, constraints=None): try: _load_aux_factory(engine, cube) except ValueError as e: - warnings.warn("{}".format(e)) + warnings.warn( + "{}".format(e), + category=iris.exceptions.IrisLoadWarning, + ) # Perform any user registered callback function. cube = run_callback(callback, cube, cf_var, file_source) diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index ee3f499034..644dd41808 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -44,6 +44,7 @@ import iris.coords from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord import iris.exceptions +from iris.exceptions import warning_combo import iris.fileformats.cf from iris.fileformats.netcdf import _dask_locks, _thread_safe_nc import iris.io @@ -364,7 +365,13 @@ def _fillvalue_report(fill_info, is_masked, contains_fill_value, warn=False): ) if warn and result is not None: - warnings.warn(result) + warnings.warn( + result, + category=warning_combo( + iris.exceptions.IrisMaskValueMatchWarning, + iris.exceptions.IrisSaveWarning, + ), + ) return result @@ -738,7 +745,7 @@ def write( msg = "cf_profile is available but no {} defined.".format( "cf_patch" ) - warnings.warn(msg) + warnings.warn(msg, iris.exceptions.IrisCfSaveWarning) @staticmethod def check_attribute_compliance(container, data_dtype): @@ -1149,7 +1156,7 @@ def _add_aux_factories(self, cube, cf_var_cube, dimension_names): "Unable to determine formula terms " "for AuxFactory: {!r}".format(factory) ) - warnings.warn(msg) + warnings.warn(msg, iris.exceptions.IrisSaveWarning) else: # Override `standard_name`, `long_name`, and `axis` of the # primary coord that signals the presence of a dimensionless @@ -2131,7 +2138,10 @@ def add_ellipsoid(ellipsoid): # osgb (a specific tmerc) elif isinstance(cs, iris.coord_systems.OSGB): - warnings.warn("OSGB coordinate system not yet handled") + warnings.warn( + "OSGB coordinate system not yet handled", + iris.exceptions.IrisSaveWarning, + ) # lambert azimuthal equal area elif isinstance( @@ -2200,7 +2210,8 @@ def add_ellipsoid(ellipsoid): warnings.warn( "Unable to represent the horizontal " "coordinate system. The coordinate system " - "type %r is not yet implemented." % type(cs) + "type %r is not yet implemented." % type(cs), + category=iris.exceptions.IrisSaveWarning, ) self._coord_systems.append(cs) @@ -2364,7 +2375,7 @@ def set_packing_ncattrs(cfvar): "attribute, but {attr_name!r} should only be a CF " "global attribute.".format(attr_name=attr_name) ) - warnings.warn(msg) + warnings.warn(msg, category=iris.exceptions.IrisCfSaveWarning) _setncattr(cf_var, attr_name, value) @@ -2598,7 +2609,9 @@ def complete(self, issue_warnings=True) -> List[Warning]: if issue_warnings: # Issue any delayed warnings from the compute. for delayed_warning in result_warnings: - warnings.warn(delayed_warning) + warnings.warn( + delayed_warning, category=iris.exceptions.IrisSaveWarning + ) return result_warnings @@ -2916,7 +2929,7 @@ def is_valid_packspec(p): msg = "cf_profile is available but no {} defined.".format( "cf_patch_conventions" ) - warnings.warn(msg) + warnings.warn(msg, category=iris.exceptions.IrisCfSaveWarning) # Add conventions attribute. sman.update_global_attributes(Conventions=conventions) diff --git a/lib/iris/fileformats/nimrod_load_rules.py b/lib/iris/fileformats/nimrod_load_rules.py index 1bfd0858d0..54525429ca 100644 --- a/lib/iris/fileformats/nimrod_load_rules.py +++ b/lib/iris/fileformats/nimrod_load_rules.py @@ -190,7 +190,8 @@ def units(cube, field): warnings.warn( "Unhandled units '{0}' recorded in cube attributes.".format( field_units - ) + ), + category=IrisNimrodTranslationWarning, ) cube.attributes["invalid_units"] = field_units @@ -426,7 +427,8 @@ def coord_system(field, handle_metadata_errors): if any([is_missing(field, v) for v in crs_args]): warnings.warn( "Coordinate Reference System is not completely defined. " - "Plotting and reprojection may be impaired." + "Plotting and reprojection may be impaired.", + category=IrisNimrodTranslationWarning, ) coord_sys = iris.coord_systems.TransverseMercator( *crs_args, iris.coord_systems.GeogCS(**ellipsoid) @@ -548,7 +550,7 @@ def vertical_coord(cube, field): f"{field.vertical_coord_type} != {field.reference_vertical_coord_type}. " f"Assuming {field.vertical_coord_type}" ) - warnings.warn(msg) + warnings.warn(msg, category=IrisNimrodTranslationWarning) coord_point = field.vertical_coord if coord_point == 8888.0: @@ -840,7 +842,8 @@ def probability_coord(cube, field, handle_metadata_errors): ) warnings.warn( f"No default units for {coord_name} coord of {cube.name()}. " - "Meta-data may be incomplete." + "Meta-data may be incomplete.", + category=IrisNimrodTranslationWarning, ) new_coord = iris.coords.AuxCoord( np.array(coord_val, dtype=np.float32), bounds=bounds, **coord_keys diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index 65e0e16d72..9f06ee6a29 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -27,6 +27,8 @@ from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data import iris.config import iris.coord_systems +import iris.exceptions +from iris.exceptions import warning_combo # NOTE: this is for backwards-compatitibility *ONLY* # We could simply remove it for v2.0 ? @@ -1165,7 +1167,13 @@ def save(self, file_handle): "missing data. To save these as normal values, please " "set the field BMDI not equal to any valid data points." ) - warnings.warn(msg.format(mdi)) + warnings.warn( + msg.format(mdi), + category=warning_combo( + iris.exceptions.IrisLoadWarning, + iris.exceptions.IrisMaskValueMatchWarning, + ), + ) if isinstance(data, ma.MaskedArray): if ma.is_masked(data): data = data.filled(fill_value=mdi) @@ -1290,7 +1298,11 @@ def save(self, file_handle): warnings.warn( "Downcasting array precision from float64 to float32" " for save.If float64 precision is required then" - " please save in a different format" + " please save in a different format", + category=warning_combo( + iris.exceptions.IrisDefaultingWarning, + iris.exceptions.IrisLoadWarning, + ), ) data = data.astype(">f4") lb[self.HEADER_DICT["lbuser"][0]] = 1 @@ -1732,7 +1744,8 @@ def _interpret_fields(fields): warnings.warn( "Landmask compressed fields existed without a " "landmask to decompress with. The data will have " - "a shape of (0, 0) and will not read." + "a shape of (0, 0) and will not read.", + category=iris.exceptions.IrisLoadWarning, ) mask_shape = (0, 0) else: @@ -1901,7 +1914,13 @@ def _field_gen(filename, read_data_bytes, little_ended=False): "Unable to interpret field {}. {}. Skipping " "the remainder of the file.".format(field_count, str(e)) ) - warnings.warn(msg) + warnings.warn( + msg, + category=warning_combo( + iris.exceptions.IrisIgnoringWarning, + iris.exceptions.IrisLoadWarning, + ), + ) break # Skip the trailing 4-byte word containing the header length @@ -1921,7 +1940,11 @@ def _field_gen(filename, read_data_bytes, little_ended=False): warnings.warn( wmsg.format( pp_field.lblrec * PP_WORD_DEPTH, len_of_data_plus_extra - ) + ), + category=warning_combo( + iris.exceptions.IrisIgnoringWarning, + iris.exceptions.IrisLoadWarning, + ), ) break diff --git a/lib/iris/fileformats/pp_save_rules.py b/lib/iris/fileformats/pp_save_rules.py index 0369fc9fd0..998255ff2b 100644 --- a/lib/iris/fileformats/pp_save_rules.py +++ b/lib/iris/fileformats/pp_save_rules.py @@ -10,6 +10,7 @@ import iris from iris.aux_factory import HybridHeightFactory, HybridPressureFactory +from iris.exceptions import IrisPpClimModifiedWarning from iris.fileformats._ff_cross_references import STASH_TRANS from iris.fileformats._pp_lbproc_pairs import LBPROC_MAP from iris.fileformats.rules import ( @@ -890,4 +891,4 @@ def verify(cube, field): def _conditional_warning(condition, warning): if condition: - warnings.warn(warning) + warnings.warn(warning, category=IrisPpClimModifiedWarning) diff --git a/lib/iris/fileformats/rules.py b/lib/iris/fileformats/rules.py index 707fd58757..d5a4b9c823 100644 --- a/lib/iris/fileformats/rules.py +++ b/lib/iris/fileformats/rules.py @@ -47,7 +47,8 @@ def as_cube(self): src_cubes = src_cubes.merge(unique=False) if len(src_cubes) > 1: warnings.warn( - "Multiple reference cubes for {}".format(self.name) + "Multiple reference cubes for {}".format(self.name), + category=iris.exceptions.IrisUserWarning, ) src_cube = src_cubes[-1] @@ -329,7 +330,7 @@ def _make_cube(field, converter): cube.units = metadata.units except ValueError: msg = "Ignoring PP invalid units {!r}".format(metadata.units) - warnings.warn(msg) + warnings.warn(msg, category=iris.exceptions.IrisIgnoringWarning) cube.attributes["invalid_units"] = metadata.units cube.units = cf_units._UNKNOWN_UNIT_STRING @@ -350,7 +351,10 @@ def _resolve_factory_references( except _ReferenceError as e: msg = "Unable to create instance of {factory}. " + str(e) factory_name = factory.factory_class.__name__ - warnings.warn(msg.format(factory=factory_name)) + warnings.warn( + msg.format(factory=factory_name), + category=iris.exceptions.IrisUserWarning, + ) else: aux_factory = factory.factory_class(*args) cube.add_aux_factory(aux_factory) diff --git a/lib/iris/iterate.py b/lib/iris/iterate.py index cf16c9cbe6..cc82433e85 100644 --- a/lib/iris/iterate.py +++ b/lib/iris/iterate.py @@ -14,6 +14,8 @@ import numpy as np +from iris.exceptions import IrisUserWarning + __all__ = ["izip"] @@ -164,7 +166,8 @@ def izip(*cubes, **kwargs): warnings.warn( "Iterating over coordinate '%s' in step whose " "definitions match but whose values " - "differ." % coord_a.name() + "differ." % coord_a.name(), + category=IrisUserWarning, ) return _ZipSlicesIterator( diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index 4d6681e94e..459756fbfc 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -29,6 +29,7 @@ from iris._deprecation import warn_deprecated from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord from iris.cube import Cube, CubeList +from iris.exceptions import IrisIgnoringWarning def _get_dimensional_metadata(name, values, calendar=None, dm_class=None): @@ -446,7 +447,7 @@ def format_dimensional_metadata(dm_class_, values_, name_, dimensions_): if columns_ignored: ignored_args = ", ".join([t[2] for t in class_arg_mapping]) message = f"The input pandas_structure is a Series; ignoring arguments: {ignored_args} ." - warnings.warn(message) + warnings.warn(message, category=IrisIgnoringWarning) class_arg_mapping = [] non_data_names = [] diff --git a/lib/iris/plot.py b/lib/iris/plot.py index ebcb5c3bcb..d85d82c6e4 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -34,7 +34,7 @@ import iris.coord_systems import iris.coords import iris.cube -from iris.exceptions import IrisError +from iris.exceptions import IrisError, IrisUnsupportedPlottingWarning # Importing iris.palette to register the brewer palettes. import iris.palette @@ -2023,7 +2023,7 @@ def update_animation_iris(i, cubes, vmin, vmax, coords): "use: {}." ) msg = msg.format(plot_func.__module__, supported) - warnings.warn(msg, UserWarning) + warnings.warn(msg, IrisUnsupportedPlottingWarning) supported = ["contour", "contourf", "pcolor", "pcolormesh"] if plot_func.__name__ not in supported: @@ -2032,7 +2032,7 @@ def update_animation_iris(i, cubes, vmin, vmax, coords): "use: {}." ) msg = msg.format(plot_func.__name__, supported) - warnings.warn(msg, UserWarning) + warnings.warn(msg, IrisUnsupportedPlottingWarning) # Determine plot range. vmin = kwargs.pop("vmin", min([cc.data.min() for cc in cubes])) diff --git a/lib/iris/tests/graphics/idiff.py b/lib/iris/tests/graphics/idiff.py index a355f2cf82..db02f3586c 100755 --- a/lib/iris/tests/graphics/idiff.py +++ b/lib/iris/tests/graphics/idiff.py @@ -28,6 +28,7 @@ from matplotlib.testing.exceptions import ImageComparisonFailure # noqa import matplotlib.widgets as mwidget # noqa +from iris.exceptions import IrisIgnoringWarning # noqa import iris.tests # noqa import iris.tests.graphics as graphics # noqa @@ -156,7 +157,7 @@ def step_over_diffs(result_dir, display=True): distance = graphics.get_phash(reference_image_path) - phash except FileNotFoundError: wmsg = "Ignoring unregistered test result {!r}." - warnings.warn(wmsg.format(test_key)) + warnings.warn(wmsg.format(test_key), category=IrisIgnoringWarning) continue processed = True diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index 0aac351078..f383bf2a29 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -136,7 +136,14 @@ def test_python_versions(): def test_categorised_warnings(): """ - To ensure that all warnings raised by Iris are categorised, for ease of use. + To ensure that all UserWarnings raised by Iris are categorised, for ease of use. + + No obvious category? use the parent: + :class:`iris.exceptions.IrisUserWarning`. + + Need a unique combination of categories? + Use :func:`~iris.exceptions.warning_combo` . + """ warn_regex = re.compile(r"(?s)warn\(.*?\)") @@ -159,9 +166,9 @@ class ResultTuple(NamedTuple): line_number=file_text[:start].count("\n") + 1, match_string=file_text[start:end], ) - if "category=" not in result_tuple.match_string: + if not re.search(r"category=", result_tuple.match_string): warns_without_category.append(result_tuple) - if "UserWarning" in result_tuple.match_string: + if re.search(r"\bUserWarning\b", result_tuple.match_string): warns_with_user_warning.append(result_tuple) matched = warn_regex.search(file_text, start + 1) From fb63268c6ec03de60178ccd5eb3cd9500a128978 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Tue, 19 Sep 2023 16:17:58 +0100 Subject: [PATCH 07/14] Fix test_categorised_warnings() by using ast module. --- lib/iris/tests/test_coding_standards.py | 67 +++++++++++++------------ 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index f383bf2a29..3967eefce3 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -8,14 +8,14 @@ # importing anything else import iris.tests as tests # isort:skip +import ast from datetime import datetime from fnmatch import fnmatch from glob import glob import os from pathlib import Path -import re import subprocess -from typing import List, NamedTuple, Tuple +from typing import List, Tuple import iris from iris.fileformats.netcdf import _thread_safe_nc @@ -145,40 +145,45 @@ def test_categorised_warnings(): Use :func:`~iris.exceptions.warning_combo` . """ - warn_regex = re.compile(r"(?s)warn\(.*?\)") - - class ResultTuple(NamedTuple): - file_path: Path - line_number: int - match_string: str - - warns_without_category: list[ResultTuple] = [] - warns_with_user_warning: list[ResultTuple] = [] + warns_without_category = [] + warns_with_user_warning = [] + tmp_list = [] for file_path in Path(IRIS_DIR).rglob("*.py"): file_text = file_path.read_text() + parsed = ast.parse(source=file_text) + calls = filter(lambda node: hasattr(node, "func"), ast.walk(parsed)) + warn_calls = filter( + lambda c: getattr(c.func, "attr", None) == "warn", calls + ) - matched = warn_regex.search(file_text) - while matched: - start, end = matched.span() - result_tuple = ResultTuple( - file_path=file_path, - line_number=file_text[:start].count("\n") + 1, - match_string=file_text[start:end], - ) - if not re.search(r"category=", result_tuple.match_string): - warns_without_category.append(result_tuple) - if re.search(r"\bUserWarning\b", result_tuple.match_string): - warns_with_user_warning.append(result_tuple) - - matched = warn_regex.search(file_text, start + 1) - - # All warnings raised by Iris must be raised with a category kwarg. - # (This avoids UserWarnings being raised by unwritten default behaviour). - assert warns_without_category == [] + warn_call: ast.Call + for warn_call in warn_calls: + warn_ref = f"{file_path}:{warn_call.lineno}" + tmp_list.append(warn_ref) - # No warnings raised by Iris can be the base UserWarning class. - assert warns_with_user_warning == [] + category_kwargs = filter( + lambda k: k.arg == "category", warn_call.keywords + ) + category_kwarg: ast.keyword = next(category_kwargs, None) + + if category_kwarg is None: + warns_without_category.append(warn_ref) + # Work with Attribute or Name instances. + elif ( + getattr(category_kwarg.value, "attr", None) + or getattr(category_kwarg.value, "id", None) + ) == "UserWarning": + warns_with_user_warning.append(warn_ref) + + # This avoids UserWarnings being raised by unwritten default behaviour. + assert ( + warns_without_category == [] + ), "All warnings raised by Iris must be raised with the category kwarg." + + assert ( + warns_with_user_warning == [] + ), "No warnings raised by Iris can be the base UserWarning class." class TestLicenseHeaders(tests.IrisTest): From d5a67e0b9b35ecdcf1a0139b83c70ed891a877b5 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Tue, 19 Sep 2023 16:31:02 +0100 Subject: [PATCH 08/14] Add missing warning category kwargs. --- lib/iris/_deprecation.py | 2 +- lib/iris/coord_systems.py | 2 +- lib/iris/experimental/ugrid/load.py | 8 +++++--- lib/iris/fileformats/_ff.py | 2 +- lib/iris/fileformats/_nc_load_rules/helpers.py | 6 +++--- lib/iris/fileformats/cf.py | 2 +- lib/iris/fileformats/name_loaders.py | 8 ++++++-- lib/iris/fileformats/netcdf/saver.py | 6 +++--- lib/iris/fileformats/nimrod_load_rules.py | 2 +- lib/iris/pandas.py | 2 +- lib/iris/plot.py | 4 ++-- .../ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py | 6 +++++- .../ugrid/cf/test_CFUGridConnectivityVariable.py | 6 +++++- .../experimental/ugrid/cf/test_CFUGridMeshVariable.py | 6 +++++- 14 files changed, 40 insertions(+), 22 deletions(-) diff --git a/lib/iris/_deprecation.py b/lib/iris/_deprecation.py index f91ad8ae9d..8ad762a558 100644 --- a/lib/iris/_deprecation.py +++ b/lib/iris/_deprecation.py @@ -50,7 +50,7 @@ def warn_deprecated(msg, stacklevel=2): >>> """ - warnings.warn(msg, IrisDeprecation, stacklevel=stacklevel) + warnings.warn(msg, category=IrisDeprecation, stacklevel=stacklevel) # A Mixin for a wrapper class that copies the docstring of the wrapped class diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index ed39a159b8..e2003d1286 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -451,7 +451,7 @@ def inverse_flattening(self, value): "the GeogCS object. To change other properties set them explicitly" " or create a new GeogCS instance." ) - warnings.warn(wmsg, iris.exceptions.IrisUserWarning) + warnings.warn(wmsg, category=iris.exceptions.IrisUserWarning) value = float(value) self._inverse_flattening = value diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index f16b3d7881..e490202682 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -359,7 +359,7 @@ def _build_mesh(cf, mesh_var, file_path): cf_role_message += " Correcting to 'mesh_topology'." warnings.warn( cf_role_message, - warning_combo(IrisCfWarning, IrisDefaultingWarning), + category=warning_combo(IrisCfWarning, IrisDefaultingWarning), ) if hasattr(mesh_var, "volume_node_connectivity"): @@ -378,7 +378,9 @@ def _build_mesh(cf, mesh_var, file_path): f" : *Assuming* topology_dimension={topology_dimension}" ", consistent with the attached connectivities." ) - warnings.warn(msg, warning_combo(IrisCfWarning, IrisDefaultingWarning)) + warnings.warn( + msg, category=warning_combo(IrisCfWarning, IrisDefaultingWarning) + ) else: quoted_topology_dimension = mesh_var.topology_dimension if quoted_topology_dimension != topology_dimension: @@ -392,7 +394,7 @@ def _build_mesh(cf, mesh_var, file_path): ) warnings.warn( msg, - warning_combo( + category=warning_combo( IrisCfWarning, IrisDefaultingWarning, IrisIgnoringWarning ), ) diff --git a/lib/iris/fileformats/_ff.py b/lib/iris/fileformats/_ff.py index f65722c89b..75d147f651 100644 --- a/lib/iris/fileformats/_ff.py +++ b/lib/iris/fileformats/_ff.py @@ -560,7 +560,7 @@ def range_order(range1, range2, resolution): "may be incorrect, not having taken into account the " "boundary size." ) - warnings.warn(msg, IrisLoadWarning) + warnings.warn(msg, category=IrisLoadWarning) else: range2 = field_dim[0] - res_low range1 = field_dim[0] - halo_dim * res_low diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 945078784b..db31c748a4 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -332,7 +332,7 @@ def parse_cell_methods(nc_cell_methods): msg = "NetCDF variable contains unknown cell method {!r}" warnings.warn( msg.format("{}".format(method_words[0])), - UnknownCellMethodWarning, + category=UnknownCellMethodWarning, ) d[_CM_METHOD] = method name = d[_CM_NAME] @@ -496,7 +496,7 @@ def _get_ellipsoid(cf_grid_var): "applied. To apply the datum when loading, use the " "iris.FUTURE.datum_support flag." ) - warnings.warn(wmsg, FutureWarning, stacklevel=14) + warnings.warn(wmsg, category=FutureWarning, stacklevel=14) datum = None if datum is not None: @@ -881,7 +881,7 @@ def get_attr_units(cf_var, attributes): ) warnings.warn( msg, - warning_combo( + category=warning_combo( iris.exceptions.IrisIgnoringWarning, iris.exceptions.IrisCfLoadWarning, ), diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index d093469429..f412955adb 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -1091,7 +1091,7 @@ def __init__(self, file_source, warn=False, monotonic=False): warnings.warn( "Optimise CF-netCDF loading by converting data from NetCDF3 " 'to NetCDF4 file format using the "nccopy" command.', - iris.exceptions.IrisLoadWarning, + category=iris.exceptions.IrisLoadWarning, ) self._check_monotonic = monotonic diff --git a/lib/iris/fileformats/name_loaders.py b/lib/iris/fileformats/name_loaders.py index 123d2eb4cf..cb8867b6ea 100644 --- a/lib/iris/fileformats/name_loaders.py +++ b/lib/iris/fileformats/name_loaders.py @@ -273,7 +273,9 @@ def _parse_units(units): try: units = cf_units.Unit(units) except ValueError: - warnings.warn("Unknown units: {!r}".format(units), IrisLoadWarning) + warnings.warn( + "Unknown units: {!r}".format(units), category=IrisLoadWarning + ) units = cf_units.Unit(None) return units @@ -611,7 +613,9 @@ def _build_cell_methods(av_or_ints, coord): else: cell_method = None msg = "Unknown {} statistic: {!r}. Unable to create cell method." - warnings.warn(msg.format(coord, av_or_int)) + warnings.warn( + msg.format(coord, av_or_int), category=IrisLoadWarning + ) cell_methods.append(cell_method) # NOTE: this can be a None return cell_methods diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 644dd41808..2780da3135 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -745,7 +745,7 @@ def write( msg = "cf_profile is available but no {} defined.".format( "cf_patch" ) - warnings.warn(msg, iris.exceptions.IrisCfSaveWarning) + warnings.warn(msg, category=iris.exceptions.IrisCfSaveWarning) @staticmethod def check_attribute_compliance(container, data_dtype): @@ -1156,7 +1156,7 @@ def _add_aux_factories(self, cube, cf_var_cube, dimension_names): "Unable to determine formula terms " "for AuxFactory: {!r}".format(factory) ) - warnings.warn(msg, iris.exceptions.IrisSaveWarning) + warnings.warn(msg, category=iris.exceptions.IrisSaveWarning) else: # Override `standard_name`, `long_name`, and `axis` of the # primary coord that signals the presence of a dimensionless @@ -2140,7 +2140,7 @@ def add_ellipsoid(ellipsoid): elif isinstance(cs, iris.coord_systems.OSGB): warnings.warn( "OSGB coordinate system not yet handled", - iris.exceptions.IrisSaveWarning, + category=iris.exceptions.IrisSaveWarning, ) # lambert azimuthal equal area diff --git a/lib/iris/fileformats/nimrod_load_rules.py b/lib/iris/fileformats/nimrod_load_rules.py index 54525429ca..17db0644ee 100644 --- a/lib/iris/fileformats/nimrod_load_rules.py +++ b/lib/iris/fileformats/nimrod_load_rules.py @@ -597,7 +597,7 @@ def vertical_coord(cube, field): warnings.warn( "Vertical coord {!r} not yet handled" "".format(field.vertical_coord_type), - TranslationWarning, + category=TranslationWarning, ) diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index 459756fbfc..9c54a0514b 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -897,7 +897,7 @@ def merge_metadata(meta_var_list): "'iris.FUTURE.pandas_ndim = True'. More info is in the " "documentation." ) - warnings.warn(message, FutureWarning) + warnings.warn(message, category=FutureWarning) # The legacy behaviour. data = cube.data diff --git a/lib/iris/plot.py b/lib/iris/plot.py index d85d82c6e4..28b458f715 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -2023,7 +2023,7 @@ def update_animation_iris(i, cubes, vmin, vmax, coords): "use: {}." ) msg = msg.format(plot_func.__module__, supported) - warnings.warn(msg, IrisUnsupportedPlottingWarning) + warnings.warn(msg, category=IrisUnsupportedPlottingWarning) supported = ["contour", "contourf", "pcolor", "pcolormesh"] if plot_func.__name__ not in supported: @@ -2032,7 +2032,7 @@ def update_animation_iris(i, cubes, vmin, vmax, coords): "use: {}." ) msg = msg.format(plot_func.__name__, supported) - warnings.warn(msg, IrisUnsupportedPlottingWarning) + warnings.warn(msg, category=IrisUnsupportedPlottingWarning) # Determine plot range. vmin = kwargs.pop("vmin", min([cc.data.min() for cc in cubes])) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py index a4e0e05a08..88e3ad1edb 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py +++ b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py @@ -20,6 +20,7 @@ import numpy as np import pytest +import iris.exceptions from iris.experimental.ugrid.cf import CFUGridAuxiliaryCoordinateVariable from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( netcdf_ugrid_variable, @@ -215,7 +216,10 @@ def test_warn(self): } def operation(warn: bool): - warnings.warn("emit at least 1 warning") + warnings.warn( + "emit at least 1 warning", + category=iris.exceptions.IrisUserWarning, + ) result = CFUGridAuxiliaryCoordinateVariable.identify( vars_all, warn=warn ) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py index 27d5c1db90..e9ef40a1e5 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py +++ b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py @@ -20,6 +20,7 @@ import numpy as np import pytest +import iris.exceptions from iris.experimental.ugrid.cf import CFUGridConnectivityVariable from iris.experimental.ugrid.mesh import Connectivity from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( @@ -204,7 +205,10 @@ def test_warn(self): } def operation(warn: bool): - warnings.warn("emit at least 1 warning") + warnings.warn( + "emit at least 1 warning", + category=iris.exceptions.IrisUserWarning, + ) result = CFUGridConnectivityVariable.identify(vars_all, warn=warn) self.assertDictEqual({}, result) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py index 6b278cf1b1..b63376719c 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py +++ b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py @@ -20,6 +20,7 @@ import numpy as np import pytest +import iris.exceptions from iris.experimental.ugrid.cf import CFUGridMeshVariable from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( netcdf_ugrid_variable, @@ -247,7 +248,10 @@ def test_warn(self): } def operation(warn: bool): - warnings.warn("emit at least 1 warning") + warnings.warn( + "emit at least 1 warning", + category=iris.exceptions.IrisUserWarning, + ) result = CFUGridMeshVariable.identify(vars_all, warn=warn) self.assertDictEqual({}, result) From 8f0756c24e2b0dc9f92224698f464076b956d939 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 20 Sep 2023 09:58:16 +0100 Subject: [PATCH 09/14] Warnings combo experiment. --- lib/iris/exceptions.py | 14 ----- lib/iris/experimental/ugrid/load.py | 19 +++--- lib/iris/fileformats/_ff.py | 15 +++-- .../fileformats/_nc_load_rules/actions.py | 25 +++++--- .../fileformats/_nc_load_rules/helpers.py | 59 +++++++++++-------- lib/iris/fileformats/netcdf/loader.py | 13 ++-- lib/iris/fileformats/netcdf/saver.py | 13 ++-- lib/iris/fileformats/pp.py | 42 +++++++------ lib/iris/tests/test_coding_standards.py | 4 +- 9 files changed, 111 insertions(+), 93 deletions(-) diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index f89239f341..75f97462a3 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -389,17 +389,3 @@ class IrisSaverFillValueWarning(IrisMaskValueMatchWarning, IrisSaveWarning): """ pass - - -######## - - -def warning_combo(*warning_classes): - """Return a one-off combination of warning classes to help user filtering.""" - - # TODO: full docstring (with example?) - # TODO: type hinting - class _WarningCombo(*warning_classes): - pass - - return _WarningCombo diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index e490202682..1d794388ca 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -23,7 +23,6 @@ IrisCfWarning, IrisDefaultingWarning, IrisIgnoringWarning, - warning_combo, ) from ...fileformats._nc_load_rules.helpers import get_attr_units, get_names from ...fileformats.netcdf import loader as nc_loader @@ -41,6 +40,14 @@ logger = get_logger(__name__, propagate=True, handler=False) +class _CfDefaultingCombo(IrisCfWarning, IrisDefaultingWarning): + pass + + +class _CfDefaultingIgnoringCombo(_CfDefaultingCombo, IrisIgnoringWarning): + pass + + class ParseUGridOnLoad(threading.local): def __init__(self): """ @@ -359,7 +366,7 @@ def _build_mesh(cf, mesh_var, file_path): cf_role_message += " Correcting to 'mesh_topology'." warnings.warn( cf_role_message, - category=warning_combo(IrisCfWarning, IrisDefaultingWarning), + category=_CfDefaultingCombo, ) if hasattr(mesh_var, "volume_node_connectivity"): @@ -378,9 +385,7 @@ def _build_mesh(cf, mesh_var, file_path): f" : *Assuming* topology_dimension={topology_dimension}" ", consistent with the attached connectivities." ) - warnings.warn( - msg, category=warning_combo(IrisCfWarning, IrisDefaultingWarning) - ) + warnings.warn(msg, category=_CfDefaultingCombo) else: quoted_topology_dimension = mesh_var.topology_dimension if quoted_topology_dimension != topology_dimension: @@ -394,9 +399,7 @@ def _build_mesh(cf, mesh_var, file_path): ) warnings.warn( msg, - category=warning_combo( - IrisCfWarning, IrisDefaultingWarning, IrisIgnoringWarning - ), + category=_CfDefaultingIgnoringCombo, ) node_dimension = None diff --git a/lib/iris/fileformats/_ff.py b/lib/iris/fileformats/_ff.py index 75d147f651..97f7fe7758 100644 --- a/lib/iris/fileformats/_ff.py +++ b/lib/iris/fileformats/_ff.py @@ -17,7 +17,6 @@ IrisDefaultingWarning, IrisLoadWarning, NotYetImplementedError, - warning_combo, ) from iris.fileformats._ff_cross_references import STASH_TRANS @@ -123,6 +122,10 @@ REAL_POLE_LON = 5 +class _LoadingDefaultingCombo(IrisDefaultingWarning, IrisLoadWarning): + pass + + class Grid: """ An abstract class representing the default/file-level grid @@ -437,7 +440,7 @@ def grid(self): warnings.warn( "Staggered grid type: {} not currently interpreted, assuming " "standard C-grid".format(self.grid_staggering), - category=warning_combo(IrisDefaultingWarning, IrisLoadWarning), + category=_LoadingDefaultingCombo, ) grid = grid_class( self.column_dependent_constants, @@ -749,9 +752,7 @@ def _extract_field(self): "handled by the fieldsfile loader." " Assuming the data is on a P grid" ".".format(stash, subgrid), - category=warning_combo( - IrisDefaultingWarning, IrisLoadWarning - ), + category=_LoadingDefaultingCombo, ) field.x, field.y = grid.vectors(subgrid) @@ -769,9 +770,7 @@ def _extract_field(self): ) warnings.warn( msg, - category=warning_combo( - IrisDefaultingWarning, IrisLoadWarning - ), + category=_LoadingDefaultingCombo, ) field.bzx, field.bdx = grid.regular_x(subgrid) field.bzy, field.bdy = grid.regular_y(subgrid) diff --git a/lib/iris/fileformats/_nc_load_rules/actions.py b/lib/iris/fileformats/_nc_load_rules/actions.py index 54dd107e87..a60ed48221 100644 --- a/lib/iris/fileformats/_nc_load_rules/actions.py +++ b/lib/iris/fileformats/_nc_load_rules/actions.py @@ -45,7 +45,6 @@ from iris.config import get_logger import iris.exceptions -from iris.exceptions import warning_combo import iris.fileformats.cf import iris.fileformats.pp as pp @@ -55,6 +54,20 @@ logger = get_logger(__name__, fmt="[%(funcName)s]") +class _CfLoadIgnoringCombo( + iris.exceptions.IrisCfLoadWarning, + iris.exceptions.IrisIgnoringWarning, +): + pass + + +class _LoadIgnoringCombo( + iris.exceptions.IrisLoadWarning, + iris.exceptions.IrisIgnoringWarning, +): + pass + + def _default_rulenamesfunc(func_name): # A simple default function to deduce the rules-name from an action-name. funcname_prefix = "action_" @@ -475,10 +488,7 @@ def action_formula_type(engine, formula_root_fact): msg = f"Ignored formula of unrecognised type: {formula_type!r}." warnings.warn( msg, - category=warning_combo( - iris.exceptions.IrisCfLoadWarning, - iris.exceptions.IrisIgnoringWarning, - ), + category=_CfLoadIgnoringCombo, ) if succeed: # Check we don't already have one. @@ -496,10 +506,7 @@ def action_formula_type(engine, formula_root_fact): ) warnings.warn( msg, - category=warning_combo( - iris.exceptions.IrisLoadWarning, - iris.exceptions.IrisIgnoringWarning, - ), + category=_LoadIgnoringCombo, ) rule_name += f"_{formula_type}" # Set 'requires' info for iris.fileformats.netcdf._load_aux_factory. diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index db31c748a4..3bc4b91395 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -28,7 +28,6 @@ import iris.coord_systems import iris.coords import iris.exceptions -from iris.exceptions import warning_combo import iris.fileformats.cf as cf import iris.fileformats.netcdf from iris.fileformats.netcdf.loader import _get_cf_var_data @@ -220,6 +219,34 @@ ] +class _IgnoringLoadCombo( + iris.exceptions.IrisIgnoringWarning, + iris.exceptions.IrisLoadWarning, +): + pass + + +class _DefaultingLoadCombo( + iris.exceptions.IrisDefaultingWarning, + iris.exceptions.IrisLoadWarning, +): + pass + + +class _DefaultingCfLoadCombo( + iris.exceptions.IrisCfLoadWarning, + iris.exceptions.IrisDefaultingWarning, +): + pass + + +class _IgnoringCfLoadCombo( + iris.exceptions.IrisIgnoringWarning, + iris.exceptions.IrisCfLoadWarning, +): + pass + + def _split_cell_methods(nc_cell_methods: str) -> List[re.Match]: """ Split a CF cell_methods attribute string into a list of zero or more cell @@ -449,10 +476,7 @@ def build_cube_metadata(engine): msg = "Skipping global attribute {!r}: {}" warnings.warn( msg.format(attr_name, str(e)), - category=warning_combo( - iris.exceptions.IrisIgnoringWarning, - iris.exceptions.IrisLoadWarning, - ), + category=_IgnoringLoadCombo, ) @@ -881,10 +905,7 @@ def get_attr_units(cf_var, attributes): ) warnings.warn( msg, - category=warning_combo( - iris.exceptions.IrisIgnoringWarning, - iris.exceptions.IrisCfLoadWarning, - ), + category=_IgnoringCfLoadCombo, ) attributes["invalid_units"] = attr_units attr_units = UNKNOWN_UNIT_STRING @@ -975,10 +996,7 @@ def get_cf_bounds_var(cf_coord_var): warnings.warn( "Ignoring climatology in favour of bounds attribute " "on NetCDF variable {!r}.".format(cf_coord_var.cf_name), - category=warning_combo( - iris.exceptions.IrisCfLoadWarning, - iris.exceptions.IrisIgnoringWarning, - ), + category=_IgnoringCfLoadCombo, ) return cf_bounds_var, climatological @@ -1039,10 +1057,7 @@ def build_dimension_coordinate( msg = "Gracefully filling {!r} dimension coordinate masked points" warnings.warn( msg.format(str(cf_coord_var.cf_name)), - category=warning_combo( - iris.exceptions.IrisDefaultingWarning, - iris.exceptions.IrisLoadWarning, - ), + category=_DefaultingLoadCombo, ) # Get any coordinate bounds. @@ -1055,10 +1070,7 @@ def build_dimension_coordinate( msg = "Gracefully filling {!r} dimension coordinate masked bounds" warnings.warn( msg.format(str(cf_coord_var.cf_name)), - category=warning_combo( - iris.exceptions.IrisDefaultingWarning, - iris.exceptions.IrisLoadWarning, - ), + category=_DefaultingLoadCombo, ) # Handle transposed bounds where the vertex dimension is not # the last one. Test based on shape to support different @@ -1126,10 +1138,7 @@ def build_dimension_coordinate( ) warnings.warn( msg.format(name=str(cf_coord_var.cf_name), error=e_msg), - category=warning_combo( - iris.exceptions.IrisCfLoadWarning, - iris.exceptions.IrisDefaultingWarning, - ), + category=_DefaultingCfLoadCombo, ) coord = iris.coords.AuxCoord( points_data, diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py index caf300bf01..6500275ddd 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -33,7 +33,6 @@ import iris.coord_systems import iris.coords import iris.exceptions -from iris.exceptions import warning_combo import iris.fileformats.cf from iris.fileformats.netcdf import _thread_safe_nc from iris.fileformats.netcdf.saver import _CF_ATTRS @@ -51,6 +50,13 @@ NetCDFDataProxy = _thread_safe_nc.NetCDFDataProxy +class _IgnoringBoundsLoadCombo( + iris.exceptions.IrisIgnoringBoundsWarning, + iris.exceptions.IrisLoadWarning, +): + pass + + def _actions_engine(): # Return an 'actions engine', which provides a pyke-rules-like interface to # the core cf translation code. @@ -397,10 +403,7 @@ def coord_from_term(term): ) warnings.warn( msg, - category=warning_combo( - iris.exceptions.IrisIgnoringBoundsWarning, - iris.exceptions.IrisLoadWarning, - ), + category=_IgnoringBoundsLoadCombo, ) coord_a = coord_from_term("a") if coord_a is not None: diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 2780da3135..39df3e2980 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -44,7 +44,6 @@ import iris.coords from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord import iris.exceptions -from iris.exceptions import warning_combo import iris.fileformats.cf from iris.fileformats.netcdf import _dask_locks, _thread_safe_nc import iris.io @@ -158,6 +157,13 @@ } +class _MaskSaveCombo( + iris.exceptions.IrisMaskValueMatchWarning, + iris.exceptions.IrisSaveWarning, +): + pass + + class CFNameCoordMap: """Provide a simple CF name to CF coordinate mapping.""" @@ -367,10 +373,7 @@ def _fillvalue_report(fill_info, is_masked, contains_fill_value, warn=False): if warn and result is not None: warnings.warn( result, - category=warning_combo( - iris.exceptions.IrisMaskValueMatchWarning, - iris.exceptions.IrisSaveWarning, - ), + category=_MaskSaveCombo, ) return result diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index 9f06ee6a29..14151c5d07 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -28,7 +28,6 @@ import iris.config import iris.coord_systems import iris.exceptions -from iris.exceptions import warning_combo # NOTE: this is for backwards-compatitibility *ONLY* # We could simply remove it for v2.0 ? @@ -222,6 +221,27 @@ } +class _LoadingMaskCombo( + iris.exceptions.IrisLoadWarning, + iris.exceptions.IrisMaskValueMatchWarning, +): + pass + + +class _LoadingDefaultingCombo( + iris.exceptions.IrisDefaultingWarning, + iris.exceptions.IrisLoadWarning, +): + pass + + +class _IgnoringLoadCombo( + iris.exceptions.IrisIgnoringWarning, + iris.exceptions.IrisLoadWarning, +): + pass + + class STASH(collections.namedtuple("STASH", "model section item")): """ A class to hold a single STASH code. @@ -1169,10 +1189,7 @@ def save(self, file_handle): ) warnings.warn( msg.format(mdi), - category=warning_combo( - iris.exceptions.IrisLoadWarning, - iris.exceptions.IrisMaskValueMatchWarning, - ), + category=_LoadingMaskCombo, ) if isinstance(data, ma.MaskedArray): if ma.is_masked(data): @@ -1299,10 +1316,7 @@ def save(self, file_handle): "Downcasting array precision from float64 to float32" " for save.If float64 precision is required then" " please save in a different format", - category=warning_combo( - iris.exceptions.IrisDefaultingWarning, - iris.exceptions.IrisLoadWarning, - ), + category=_LoadingDefaultingCombo, ) data = data.astype(">f4") lb[self.HEADER_DICT["lbuser"][0]] = 1 @@ -1916,10 +1930,7 @@ def _field_gen(filename, read_data_bytes, little_ended=False): ) warnings.warn( msg, - category=warning_combo( - iris.exceptions.IrisIgnoringWarning, - iris.exceptions.IrisLoadWarning, - ), + category=_IgnoringLoadCombo, ) break @@ -1941,10 +1952,7 @@ def _field_gen(filename, read_data_bytes, little_ended=False): wmsg.format( pp_field.lblrec * PP_WORD_DEPTH, len_of_data_plus_extra ), - category=warning_combo( - iris.exceptions.IrisIgnoringWarning, - iris.exceptions.IrisLoadWarning, - ), + category=_IgnoringLoadCombo, ) break diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index 3967eefce3..8fdd49064e 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -141,8 +141,8 @@ def test_categorised_warnings(): No obvious category? use the parent: :class:`iris.exceptions.IrisUserWarning`. - Need a unique combination of categories? - Use :func:`~iris.exceptions.warning_combo` . + .. todo: + Advice on combining warning classes """ warns_without_category = [] From 8a844f1ba990f9b5f4ae6c0b84fd024164cd7bf9 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 20 Sep 2023 11:52:11 +0100 Subject: [PATCH 10/14] Warnings combo finalise. --- lib/iris/experimental/ugrid/load.py | 16 +++++++---- lib/iris/fileformats/_ff.py | 10 ++++--- .../fileformats/_nc_load_rules/actions.py | 12 +++++--- .../fileformats/_nc_load_rules/helpers.py | 28 ++++++++++++------- lib/iris/fileformats/netcdf/loader.py | 6 ++-- lib/iris/fileformats/netcdf/saver.py | 6 ++-- lib/iris/fileformats/pp.py | 20 ++++++++----- lib/iris/tests/test_coding_standards.py | 14 ++++++++-- 8 files changed, 75 insertions(+), 37 deletions(-) diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index 1d794388ca..67d1491930 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -40,11 +40,17 @@ logger = get_logger(__name__, propagate=True, handler=False) -class _CfDefaultingCombo(IrisCfWarning, IrisDefaultingWarning): +class _WarnComboCfDefaulting(IrisCfWarning, IrisDefaultingWarning): + """One-off combination of warning classes - enhances user filtering.""" + pass -class _CfDefaultingIgnoringCombo(_CfDefaultingCombo, IrisIgnoringWarning): +class _WarnComboCfDefaultingIgnoring( + _WarnComboCfDefaulting, IrisIgnoringWarning +): + """One-off combination of warning classes - enhances user filtering.""" + pass @@ -366,7 +372,7 @@ def _build_mesh(cf, mesh_var, file_path): cf_role_message += " Correcting to 'mesh_topology'." warnings.warn( cf_role_message, - category=_CfDefaultingCombo, + category=_WarnComboCfDefaulting, ) if hasattr(mesh_var, "volume_node_connectivity"): @@ -385,7 +391,7 @@ def _build_mesh(cf, mesh_var, file_path): f" : *Assuming* topology_dimension={topology_dimension}" ", consistent with the attached connectivities." ) - warnings.warn(msg, category=_CfDefaultingCombo) + warnings.warn(msg, category=_WarnComboCfDefaulting) else: quoted_topology_dimension = mesh_var.topology_dimension if quoted_topology_dimension != topology_dimension: @@ -399,7 +405,7 @@ def _build_mesh(cf, mesh_var, file_path): ) warnings.warn( msg, - category=_CfDefaultingIgnoringCombo, + category=_WarnComboCfDefaultingIgnoring, ) node_dimension = None diff --git a/lib/iris/fileformats/_ff.py b/lib/iris/fileformats/_ff.py index 97f7fe7758..5121b47976 100644 --- a/lib/iris/fileformats/_ff.py +++ b/lib/iris/fileformats/_ff.py @@ -122,7 +122,9 @@ REAL_POLE_LON = 5 -class _LoadingDefaultingCombo(IrisDefaultingWarning, IrisLoadWarning): +class _WarnComboLoadingDefaulting(IrisDefaultingWarning, IrisLoadWarning): + """One-off combination of warning classes - enhances user filtering.""" + pass @@ -440,7 +442,7 @@ def grid(self): warnings.warn( "Staggered grid type: {} not currently interpreted, assuming " "standard C-grid".format(self.grid_staggering), - category=_LoadingDefaultingCombo, + category=_WarnComboLoadingDefaulting, ) grid = grid_class( self.column_dependent_constants, @@ -752,7 +754,7 @@ def _extract_field(self): "handled by the fieldsfile loader." " Assuming the data is on a P grid" ".".format(stash, subgrid), - category=_LoadingDefaultingCombo, + category=_WarnComboLoadingDefaulting, ) field.x, field.y = grid.vectors(subgrid) @@ -770,7 +772,7 @@ def _extract_field(self): ) warnings.warn( msg, - category=_LoadingDefaultingCombo, + category=_WarnComboLoadingDefaulting, ) field.bzx, field.bdx = grid.regular_x(subgrid) field.bzy, field.bdy = grid.regular_y(subgrid) diff --git a/lib/iris/fileformats/_nc_load_rules/actions.py b/lib/iris/fileformats/_nc_load_rules/actions.py index a60ed48221..be84b65132 100644 --- a/lib/iris/fileformats/_nc_load_rules/actions.py +++ b/lib/iris/fileformats/_nc_load_rules/actions.py @@ -54,17 +54,21 @@ logger = get_logger(__name__, fmt="[%(funcName)s]") -class _CfLoadIgnoringCombo( +class _WarnComboCfLoadIgnoring( iris.exceptions.IrisCfLoadWarning, iris.exceptions.IrisIgnoringWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass -class _LoadIgnoringCombo( +class _WarnComboLoadIgnoring( iris.exceptions.IrisLoadWarning, iris.exceptions.IrisIgnoringWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass @@ -488,7 +492,7 @@ def action_formula_type(engine, formula_root_fact): msg = f"Ignored formula of unrecognised type: {formula_type!r}." warnings.warn( msg, - category=_CfLoadIgnoringCombo, + category=_WarnComboCfLoadIgnoring, ) if succeed: # Check we don't already have one. @@ -506,7 +510,7 @@ def action_formula_type(engine, formula_root_fact): ) warnings.warn( msg, - category=_LoadIgnoringCombo, + category=_WarnComboLoadIgnoring, ) rule_name += f"_{formula_type}" # Set 'requires' info for iris.fileformats.netcdf._load_aux_factory. diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 3bc4b91395..19a9cd18ca 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -219,31 +219,39 @@ ] -class _IgnoringLoadCombo( +class _WarnComboIgnoringLoad( iris.exceptions.IrisIgnoringWarning, iris.exceptions.IrisLoadWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass -class _DefaultingLoadCombo( +class _WarnComboDefaultingLoad( iris.exceptions.IrisDefaultingWarning, iris.exceptions.IrisLoadWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass -class _DefaultingCfLoadCombo( +class _WarnComboDefaultingCfLoad( iris.exceptions.IrisCfLoadWarning, iris.exceptions.IrisDefaultingWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass -class _IgnoringCfLoadCombo( +class _WarnComboIgnoringCfLoad( iris.exceptions.IrisIgnoringWarning, iris.exceptions.IrisCfLoadWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass @@ -476,7 +484,7 @@ def build_cube_metadata(engine): msg = "Skipping global attribute {!r}: {}" warnings.warn( msg.format(attr_name, str(e)), - category=_IgnoringLoadCombo, + category=_WarnComboIgnoringLoad, ) @@ -905,7 +913,7 @@ def get_attr_units(cf_var, attributes): ) warnings.warn( msg, - category=_IgnoringCfLoadCombo, + category=_WarnComboIgnoringCfLoad, ) attributes["invalid_units"] = attr_units attr_units = UNKNOWN_UNIT_STRING @@ -996,7 +1004,7 @@ def get_cf_bounds_var(cf_coord_var): warnings.warn( "Ignoring climatology in favour of bounds attribute " "on NetCDF variable {!r}.".format(cf_coord_var.cf_name), - category=_IgnoringCfLoadCombo, + category=_WarnComboIgnoringCfLoad, ) return cf_bounds_var, climatological @@ -1057,7 +1065,7 @@ def build_dimension_coordinate( msg = "Gracefully filling {!r} dimension coordinate masked points" warnings.warn( msg.format(str(cf_coord_var.cf_name)), - category=_DefaultingLoadCombo, + category=_WarnComboDefaultingLoad, ) # Get any coordinate bounds. @@ -1070,7 +1078,7 @@ def build_dimension_coordinate( msg = "Gracefully filling {!r} dimension coordinate masked bounds" warnings.warn( msg.format(str(cf_coord_var.cf_name)), - category=_DefaultingLoadCombo, + category=_WarnComboDefaultingLoad, ) # Handle transposed bounds where the vertex dimension is not # the last one. Test based on shape to support different @@ -1138,7 +1146,7 @@ def build_dimension_coordinate( ) warnings.warn( msg.format(name=str(cf_coord_var.cf_name), error=e_msg), - category=_DefaultingCfLoadCombo, + category=_WarnComboDefaultingCfLoad, ) coord = iris.coords.AuxCoord( points_data, diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py index 6500275ddd..29202af89e 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -50,10 +50,12 @@ NetCDFDataProxy = _thread_safe_nc.NetCDFDataProxy -class _IgnoringBoundsLoadCombo( +class _WarnComboIgnoringBoundsLoad( iris.exceptions.IrisIgnoringBoundsWarning, iris.exceptions.IrisLoadWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass @@ -403,7 +405,7 @@ def coord_from_term(term): ) warnings.warn( msg, - category=_IgnoringBoundsLoadCombo, + category=_WarnComboIgnoringBoundsLoad, ) coord_a = coord_from_term("a") if coord_a is not None: diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 39df3e2980..1ff69df1f7 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -157,10 +157,12 @@ } -class _MaskSaveCombo( +class _WarnComboMaskSave( iris.exceptions.IrisMaskValueMatchWarning, iris.exceptions.IrisSaveWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass @@ -373,7 +375,7 @@ def _fillvalue_report(fill_info, is_masked, contains_fill_value, warn=False): if warn and result is not None: warnings.warn( result, - category=_MaskSaveCombo, + category=_WarnComboMaskSave, ) return result diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index 14151c5d07..e19ba3adff 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -221,24 +221,30 @@ } -class _LoadingMaskCombo( +class _WarnComboLoadingMask( iris.exceptions.IrisLoadWarning, iris.exceptions.IrisMaskValueMatchWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass -class _LoadingDefaultingCombo( +class _WarnComboLoadingDefaulting( iris.exceptions.IrisDefaultingWarning, iris.exceptions.IrisLoadWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass -class _IgnoringLoadCombo( +class _WarnComboIgnoringLoad( iris.exceptions.IrisIgnoringWarning, iris.exceptions.IrisLoadWarning, ): + """One-off combination of warning classes - enhances user filtering.""" + pass @@ -1189,7 +1195,7 @@ def save(self, file_handle): ) warnings.warn( msg.format(mdi), - category=_LoadingMaskCombo, + category=_WarnComboLoadingMask, ) if isinstance(data, ma.MaskedArray): if ma.is_masked(data): @@ -1316,7 +1322,7 @@ def save(self, file_handle): "Downcasting array precision from float64 to float32" " for save.If float64 precision is required then" " please save in a different format", - category=_LoadingDefaultingCombo, + category=_WarnComboLoadingDefaulting, ) data = data.astype(">f4") lb[self.HEADER_DICT["lbuser"][0]] = 1 @@ -1930,7 +1936,7 @@ def _field_gen(filename, read_data_bytes, little_ended=False): ) warnings.warn( msg, - category=_IgnoringLoadCombo, + category=_WarnComboIgnoringLoad, ) break @@ -1952,7 +1958,7 @@ def _field_gen(filename, read_data_bytes, little_ended=False): wmsg.format( pp_field.lblrec * PP_WORD_DEPTH, len_of_data_plus_extra ), - category=_IgnoringLoadCombo, + category=_WarnComboIgnoringLoad, ) break diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index 8fdd49064e..54309e3906 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -138,11 +138,19 @@ def test_categorised_warnings(): """ To ensure that all UserWarnings raised by Iris are categorised, for ease of use. - No obvious category? use the parent: + No obvious category? Use the parent: :class:`iris.exceptions.IrisUserWarning`. - .. todo: - Advice on combining warning classes + Warning matches multiple categories? Create a one-off combo class. For + example: + + .. code-block:: python + + class _WarnComboCfDefaulting(IrisCfWarning, IrisDefaultingWarning): + \""" + One-off combination of warning classes - enhances user filtering. + \""" + pass """ warns_without_category = [] From 740881413fca03ca3c823dba643aec846702b849 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 20 Sep 2023 12:01:27 +0100 Subject: [PATCH 11/14] Fix stray comma in coords.py. --- lib/iris/coords.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 2c0eb6f5f7..3ff9bc8e5e 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -2223,7 +2223,7 @@ def serialize(x): if self.ndim > 1: msg = ( "Collapsing a multi-dimensional coordinate. " - "Metadata may not be fully descriptive for {!r}.", + "Metadata may not be fully descriptive for {!r}." ) warnings.warn( msg.format(self.name()), From 37df50be68bcc7f36daa3bbeaf0e12b112e0add0 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 20 Sep 2023 12:53:16 +0100 Subject: [PATCH 12/14] Fix failing tests. --- .../integration/netcdf/test_self_referencing.py | 9 +++++++-- lib/iris/tests/integration/test_pp.py | 6 +++--- lib/iris/tests/test_netcdf.py | 5 ++++- .../tests/unit/analysis/cartography/test_project.py | 4 +++- lib/iris/tests/unit/cube/test_Cube.py | 12 ++++++++++-- lib/iris/tests/unit/fileformats/ff/test_FF2PP.py | 4 ++-- lib/iris/tests/unit/fileformats/ff/test_FFHeader.py | 5 +++-- .../name_loaders/test__build_cell_methods.py | 5 +++-- .../netcdf/loader/test__load_aux_factory.py | 4 +++- .../nimrod_load_rules/test_vertical_coord.py | 9 ++++++--- 10 files changed, 44 insertions(+), 19 deletions(-) diff --git a/lib/iris/tests/integration/netcdf/test_self_referencing.py b/lib/iris/tests/integration/netcdf/test_self_referencing.py index 3395296e11..554fabb4fc 100644 --- a/lib/iris/tests/integration/netcdf/test_self_referencing.py +++ b/lib/iris/tests/integration/netcdf/test_self_referencing.py @@ -16,6 +16,7 @@ import numpy as np import iris +from iris.exceptions import IrisCfMissingVarWarning from iris.fileformats.netcdf import _thread_safe_nc @@ -46,7 +47,9 @@ def test_cmip6_volcello_load_issue_3367(self): with mock.patch("warnings.warn") as warn: # ensure file loads without failure cube = iris.load_cube(self.fname) - warn.assert_has_calls([mock.call(expected_msg)]) + warn.assert_has_calls( + [mock.call(expected_msg, category=IrisCfMissingVarWarning)] + ) # extra check to ensure correct variable was found assert cube.standard_name == "ocean_volume" @@ -113,7 +116,9 @@ def test_self_referencing_load_issue_3367(self): with mock.patch("warnings.warn") as warn: # ensure file loads without failure cube = iris.load_cube(self.temp_dir_path) - warn.assert_called_with(expected_msg) + warn.assert_called_with( + expected_msg, category=IrisCfMissingVarWarning + ) # extra check to ensure correct variable was found assert cube.standard_name == "ocean_volume" diff --git a/lib/iris/tests/integration/test_pp.py b/lib/iris/tests/integration/test_pp.py index e654694aa7..026bdae58a 100644 --- a/lib/iris/tests/integration/test_pp.py +++ b/lib/iris/tests/integration/test_pp.py @@ -18,7 +18,7 @@ from iris.aux_factory import HybridHeightFactory, HybridPressureFactory from iris.coords import AuxCoord, CellMethod, DimCoord from iris.cube import Cube -from iris.exceptions import IgnoreCubeException +from iris.exceptions import IgnoreCubeException, IrisUserWarning import iris.fileformats.pp from iris.fileformats.pp import load_pairs_from_fields import iris.fileformats.pp_load_rules @@ -290,7 +290,7 @@ def test_hybrid_pressure_with_duplicate_references(self): "iris.fileformats.pp.load", new=load ) as load, mock.patch("warnings.warn") as warn: _, _, _ = iris.fileformats.pp.load_cubes("DUMMY") - warn.assert_called_with(msg) + warn.assert_called_with(msg, category=IrisUserWarning) def test_hybrid_height_with_non_standard_coords(self): # Check the save rules are using the AuxFactory to find the @@ -415,7 +415,7 @@ def test_hybrid_height_round_trip_no_reference(self): "Unable to create instance of HybridHeightFactory. " "The source data contains no field(s) for 'orography'." ) - warn.assert_called_with(msg) + warn.assert_called_with(msg, category=IrisUserWarning) # Check the data cube is set up to use hybrid height. self._test_coord( diff --git a/lib/iris/tests/test_netcdf.py b/lib/iris/tests/test_netcdf.py index 6438140ed9..2e389942bf 100644 --- a/lib/iris/tests/test_netcdf.py +++ b/lib/iris/tests/test_netcdf.py @@ -26,6 +26,7 @@ from iris._lazy_data import is_lazy_data import iris.analysis.trajectory import iris.coord_systems as icoord_systems +from iris.exceptions import IrisCfSaveWarning from iris.fileformats._nc_load_rules import helpers as ncload_helpers import iris.fileformats.netcdf from iris.fileformats.netcdf import _thread_safe_nc @@ -1099,7 +1100,9 @@ def test_conflicting_global_attributes(self): with self.temp_filename(suffix=".nc") as filename: with mock.patch("warnings.warn") as warn: iris.save([self.cube, self.cube2], filename) - warn.assert_called_with(expected_msg) + warn.assert_called_with( + expected_msg, category=IrisCfSaveWarning + ) self.assertCDL( filename, ("netcdf", "netcdf_save_confl_global_attr.cdl") ) diff --git a/lib/iris/tests/unit/analysis/cartography/test_project.py b/lib/iris/tests/unit/analysis/cartography/test_project.py index 8649cc55ea..c00830aacc 100644 --- a/lib/iris/tests/unit/analysis/cartography/test_project.py +++ b/lib/iris/tests/unit/analysis/cartography/test_project.py @@ -16,6 +16,7 @@ import iris.coord_systems import iris.coords import iris.cube +from iris.exceptions import IrisDefaultingWarning import iris.tests import iris.tests.stock @@ -161,7 +162,8 @@ def test_no_coord_system(self): warn.assert_called_once_with( "Coordinate system of latitude and " "longitude coordinates is not specified. " - "Assuming WGS84 Geodetic." + "Assuming WGS84 Geodetic.", + category=IrisDefaultingWarning, ) diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index a733665df8..443c9db546 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -40,6 +40,8 @@ AncillaryVariableNotFoundError, CellMeasureNotFoundError, CoordinateNotFoundError, + IrisUserWarning, + IrisVagueMetadataWarning, UnitConversionError, ) import iris.tests.stock as stock @@ -676,7 +678,10 @@ def _assert_warn_collapse_without_weight(self, coords, warn): # Ensure that warning is raised. msg = "Collapsing spatial coordinate {!r} without weighting" for coord in coords: - self.assertIn(mock.call(msg.format(coord)), warn.call_args_list) + self.assertIn( + mock.call(msg.format(coord), category=IrisUserWarning), + warn.call_args_list, + ) def _assert_nowarn_collapse_without_weight(self, coords, warn): # Ensure that warning is not raised. @@ -765,7 +770,10 @@ def _assert_warn_cannot_check_contiguity(self, warn): f"bounds. Metadata may not be fully descriptive for " f"'{coord}'. Ignoring bounds." ) - self.assertIn(mock.call(msg), warn.call_args_list) + self.assertIn( + mock.call(msg, category=IrisVagueMetadataWarning), + warn.call_args_list, + ) def _assert_cube_as_expected(self, cube): """Ensure that cube data and coordinates are as expected.""" diff --git a/lib/iris/tests/unit/fileformats/ff/test_FF2PP.py b/lib/iris/tests/unit/fileformats/ff/test_FF2PP.py index cec4f53bc3..16943c0c15 100644 --- a/lib/iris/tests/unit/fileformats/ff/test_FF2PP.py +++ b/lib/iris/tests/unit/fileformats/ff/test_FF2PP.py @@ -15,7 +15,7 @@ import numpy as np -from iris.exceptions import NotYetImplementedError +from iris.exceptions import IrisLoadWarning, NotYetImplementedError import iris.fileformats._ff as ff from iris.fileformats._ff import FF2PP import iris.fileformats.pp as pp @@ -467,7 +467,7 @@ def test_unequal_spacing_eitherside(self): with mock.patch("warnings.warn") as warn: result = ff2pp._det_border(field_x, None) - warn.assert_called_with(msg) + warn.assert_called_with(msg, category=IrisLoadWarning) self.assertIs(result, field_x) def test_increasing_field_values(self): diff --git a/lib/iris/tests/unit/fileformats/ff/test_FFHeader.py b/lib/iris/tests/unit/fileformats/ff/test_FFHeader.py index 6a65397086..72d522ec85 100644 --- a/lib/iris/tests/unit/fileformats/ff/test_FFHeader.py +++ b/lib/iris/tests/unit/fileformats/ff/test_FFHeader.py @@ -14,7 +14,7 @@ import numpy as np -from iris.fileformats._ff import FFHeader +from iris.fileformats._ff import FFHeader, _WarnComboLoadingDefaulting MyGrid = collections.namedtuple("MyGrid", "column row real horiz_grid_type") @@ -60,7 +60,8 @@ def test_unknown(self): grid = header.grid() warn.assert_called_with( "Staggered grid type: 0 not currently" - " interpreted, assuming standard C-grid" + " interpreted, assuming standard C-grid", + category=_WarnComboLoadingDefaulting, ) self.assertIs(grid, mock.sentinel.grid) diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py b/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py index ded635984c..624837c19d 100644 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py +++ b/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py @@ -15,6 +15,7 @@ from unittest import mock import iris.coords +from iris.exceptions import IrisLoadWarning from iris.fileformats.name_loaders import _build_cell_methods @@ -104,7 +105,7 @@ def test_unrecognised(self): "Unknown {} statistic: {!r}. Unable to " "create cell method.".format(coord_name, unrecognised_heading) ) - warn.assert_called_with(expected_msg) + warn.assert_called_with(expected_msg, category=IrisLoadWarning) def test_unrecognised_similar_to_no_averaging(self): unrecognised_headings = [ @@ -129,7 +130,7 @@ def test_unrecognised_similar_to_no_averaging(self): "Unknown {} statistic: {!r}. Unable to " "create cell method.".format(coord_name, unrecognised_heading) ) - warn.assert_called_with(expected_msg) + warn.assert_called_with(expected_msg, category=IrisLoadWarning) if __name__ == "__main__": diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py index 841935cc81..c15c8737fd 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py @@ -16,6 +16,7 @@ from iris.coords import DimCoord from iris.cube import Cube +from iris.exceptions import IrisFactoryCoordNotFoundWarning from iris.fileformats.netcdf.loader import _load_aux_factory @@ -165,7 +166,8 @@ def test_formula_terms_ap_missing_coords(self): with mock.patch("warnings.warn") as warn: _load_aux_factory(self.engine, self.cube) warn.assert_called_once_with( - "Unable to find coordinate for variable " "'ap'" + "Unable to find coordinate for variable " "'ap'", + category=IrisFactoryCoordNotFoundWarning, ) self._check_no_delta() diff --git a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py index 54340f598b..2279bcffc3 100644 --- a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py +++ b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py @@ -15,9 +15,12 @@ from unittest import mock -from iris.exceptions import IrisNimrodTranslationWarning from iris.fileformats.nimrod import NimrodField -from iris.fileformats.nimrod_load_rules import NIMROD_DEFAULT, vertical_coord +from iris.fileformats.nimrod_load_rules import ( + NIMROD_DEFAULT, + TranslationWarning, + vertical_coord, +) class Test(tests.IrisTest): @@ -60,7 +63,7 @@ def test_unhandled(self): vertical_coord_val=1.0, vertical_coord_type=-1 ) warn.assert_called_once_with( - "Vertical coord -1 not yet handled", IrisNimrodTranslationWarning + "Vertical coord -1 not yet handled", category=TranslationWarning ) def test_null(self): From 257be1583e07309fc321454458f7f1530c3a2ad1 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 20 Sep 2023 15:33:11 +0100 Subject: [PATCH 13/14] Categorise warning tests. --- .../integration/experimental/test_ugrid_load.py | 9 +++++---- lib/iris/tests/test_concatenate.py | 13 +++++++++---- lib/iris/tests/test_coordsystem.py | 3 ++- lib/iris/tests/test_hybrid.py | 9 +++++---- lib/iris/tests/test_iterate.py | 5 +++-- .../geometry/test_geometry_area_weights.py | 5 ++++- lib/iris/tests/unit/coords/test_Coord.py | 14 +++++++------- .../cf/test_CFUGridAuxiliaryCoordinateVariable.py | 8 ++++++-- .../ugrid/cf/test_CFUGridConnectivityVariable.py | 8 ++++++-- .../ugrid/cf/test_CFUGridMeshVariable.py | 8 ++++++-- .../fileformats/nc_load_rules/actions/__init__.py | 3 ++- .../helpers/test_parse_cell_methods.py | 5 +++-- .../unit/fileformats/netcdf/saver/test_Saver.py | 7 ++++--- .../netcdf/saver/test_Saver__lazy_stream_data.py | 3 ++- lib/iris/tests/unit/fileformats/pp/test_PPField.py | 7 ++++--- 15 files changed, 68 insertions(+), 39 deletions(-) diff --git a/lib/iris/tests/integration/experimental/test_ugrid_load.py b/lib/iris/tests/integration/experimental/test_ugrid_load.py index b0b60ee506..d94e85d2f5 100644 --- a/lib/iris/tests/integration/experimental/test_ugrid_load.py +++ b/lib/iris/tests/integration/experimental/test_ugrid_load.py @@ -19,6 +19,7 @@ import pytest from iris import Constraint, load +from iris.exceptions import IrisCfWarning from iris.experimental.ugrid.load import ( PARSE_UGRID_ON_LOAD, load_mesh, @@ -170,7 +171,7 @@ def create_synthetic_file(self, **create_kwargs): def test_mesh_bad_topology_dimension(self): # Check that the load generates a suitable warning. warn_regex = r"topology_dimension.* ignoring" - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns(IrisCfWarning, match=warn_regex): template = "minimal_bad_topology_dim" dim_line = "mesh_var:topology_dimension = 1 ;" # which is wrong ! cube = self.create_synthetic_test_cube( @@ -183,7 +184,7 @@ def test_mesh_bad_topology_dimension(self): def test_mesh_no_topology_dimension(self): # Check that the load generates a suitable warning. warn_regex = r"Mesh variable.* has no 'topology_dimension'" - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns(IrisCfWarning, match=warn_regex): template = "minimal_bad_topology_dim" dim_line = "" # don't create ANY topology_dimension property cube = self.create_synthetic_test_cube( @@ -196,7 +197,7 @@ def test_mesh_no_topology_dimension(self): def test_mesh_bad_cf_role(self): # Check that the load generates a suitable warning. warn_regex = r"inappropriate cf_role" - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns(IrisCfWarning, match=warn_regex): template = "minimal_bad_mesh_cf_role" dim_line = 'mesh_var:cf_role = "foo" ;' _ = self.create_synthetic_test_cube( @@ -206,7 +207,7 @@ def test_mesh_bad_cf_role(self): def test_mesh_no_cf_role(self): # Check that the load generates a suitable warning. warn_regex = r"no cf_role attribute" - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns(IrisCfWarning, match=warn_regex): template = "minimal_bad_mesh_cf_role" dim_line = "" _ = self.create_synthetic_test_cube( diff --git a/lib/iris/tests/test_concatenate.py b/lib/iris/tests/test_concatenate.py index 9287a79fda..ec92838466 100644 --- a/lib/iris/tests/test_concatenate.py +++ b/lib/iris/tests/test_concatenate.py @@ -20,6 +20,7 @@ from iris.aux_factory import HybridHeightFactory from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord import iris.cube +from iris.exceptions import IrisUserWarning import iris.tests.stock as stock @@ -340,7 +341,8 @@ def test_points_overlap_increasing(self): cubes.append(_make_cube((0, 2), y, 1)) cubes.append(_make_cube((1, 3), y, 2)) with pytest.warns( - UserWarning, match="Found cubes with overlap on concatenate axis" + IrisUserWarning, + match="Found cubes with overlap on concatenate axis", ): result = concatenate(cubes) self.assertEqual(len(result), 2) @@ -351,7 +353,8 @@ def test_points_overlap_decreasing(self): cubes.append(_make_cube(x, (3, 0, -1), 1)) cubes.append(_make_cube(x, (1, -1, -1), 2)) with pytest.warns( - UserWarning, match="Found cubes with overlap on concatenate axis" + IrisUserWarning, + match="Found cubes with overlap on concatenate axis", ): result = concatenate(cubes) self.assertEqual(len(result), 2) @@ -366,7 +369,8 @@ def test_bounds_overlap_increasing(self): ) cubes.append(cube) with pytest.warns( - UserWarning, match="Found cubes with overlap on concatenate axis" + IrisUserWarning, + match="Found cubes with overlap on concatenate axis", ): result = concatenate(cubes) self.assertEqual(len(result), 2) @@ -381,7 +385,8 @@ def test_bounds_overlap_decreasing(self): ) cubes.append(cube) with pytest.warns( - UserWarning, match="Found cubes with overlap on concatenate axis" + IrisUserWarning, + match="Found cubes with overlap on concatenate axis", ): result = concatenate(cubes) self.assertEqual(len(result), 2) diff --git a/lib/iris/tests/test_coordsystem.py b/lib/iris/tests/test_coordsystem.py index 7cd15297cc..2e5aef249c 100644 --- a/lib/iris/tests/test_coordsystem.py +++ b/lib/iris/tests/test_coordsystem.py @@ -18,6 +18,7 @@ ) import iris.coords import iris.cube +from iris.exceptions import IrisUserWarning import iris.tests.stock @@ -341,7 +342,7 @@ def test_inverse_flattening_change(self): cs = GeogCS(6543210, 6500000) initial_crs = cs.as_cartopy_crs() with self.assertWarnsRegex( - UserWarning, + IrisUserWarning, "Setting inverse_flattening does not affect other properties of the GeogCS object.", ): cs.inverse_flattening = cs.inverse_flattening + 1 diff --git a/lib/iris/tests/test_hybrid.py b/lib/iris/tests/test_hybrid.py index 76fc971a08..b070f36a7a 100644 --- a/lib/iris/tests/test_hybrid.py +++ b/lib/iris/tests/test_hybrid.py @@ -18,6 +18,7 @@ import iris from iris.aux_factory import HybridHeightFactory, HybridPressureFactory +from iris.exceptions import IrisIgnoringBoundsWarning import iris.tests.stock @@ -136,7 +137,7 @@ def test_invalid_dependencies(self): with warnings.catch_warnings(): # Cause all warnings to raise Exceptions warnings.simplefilter("error") - with self.assertRaises(UserWarning): + with self.assertRaises(IrisIgnoringBoundsWarning): _ = HybridHeightFactory(orography=sigma) def test_bounded_orography(self): @@ -154,7 +155,7 @@ def test_bounded_orography(self): with warnings.catch_warnings(): # Cause all warnings to raise Exceptions warnings.simplefilter("error") - with self.assertRaisesRegex(UserWarning, msg): + with self.assertRaisesRegex(IrisIgnoringBoundsWarning, msg): self.cube.coord("altitude") @@ -215,7 +216,7 @@ def test_invalid_dependencies(self): with warnings.catch_warnings(): # Cause all warnings to raise Exceptions warnings.simplefilter("error") - with self.assertRaises(UserWarning): + with self.assertRaises(IrisIgnoringBoundsWarning): _ = HybridPressureFactory( sigma=sigma, surface_air_pressure=sigma ) @@ -235,7 +236,7 @@ def test_bounded_surface_pressure(self): with warnings.catch_warnings(): # Cause all warnings to raise Exceptions warnings.simplefilter("error") - with self.assertRaisesRegex(UserWarning, msg): + with self.assertRaisesRegex(IrisIgnoringBoundsWarning, msg): self.cube.coord("air_pressure") diff --git a/lib/iris/tests/test_iterate.py b/lib/iris/tests/test_iterate.py index ec86d2f69d..6317ef32b5 100644 --- a/lib/iris/tests/test_iterate.py +++ b/lib/iris/tests/test_iterate.py @@ -22,6 +22,7 @@ import iris import iris.analysis +from iris.exceptions import IrisUserWarning import iris.iterate import iris.tests.stock @@ -365,12 +366,12 @@ def test_izip_different_valued_coords(self): warnings.simplefilter( "error" ) # Cause all warnings to raise Exceptions - with self.assertRaises(UserWarning): + with self.assertRaises(IrisUserWarning): iris.iterate.izip( self.cube_a, self.cube_b, coords=self.coord_names ) # Call with coordinates, rather than names - with self.assertRaises(UserWarning): + with self.assertRaises(IrisUserWarning): iris.iterate.izip( self.cube_a, self.cube_b, coords=[latitude, longitude] ) diff --git a/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py b/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py index 49e03a1174..62ab1ae283 100644 --- a/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py +++ b/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py @@ -21,6 +21,7 @@ from iris.analysis.geometry import geometry_area_weights from iris.coords import DimCoord from iris.cube import Cube +from iris.exceptions import IrisGeometryExceedWarning import iris.tests.stock as stock @@ -148,7 +149,9 @@ def test_distinct_xy_bounds_pole(self): "The geometry exceeds the " "cube's y dimension at the upper end.", ) - self.assertTrue(issubclass(w[-1].category, UserWarning)) + self.assertTrue( + issubclass(w[-1].category, IrisGeometryExceedWarning) + ) target = np.array( [ [0, top_cell_half, top_cell_half, 0], diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index 69b6b70c96..c548d017f2 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -19,7 +19,7 @@ import iris from iris.coords import AuxCoord, Coord, DimCoord from iris.cube import Cube -from iris.exceptions import UnitConversionError +from iris.exceptions import IrisVagueMetadataWarning, UnitConversionError from iris.tests.unit.coords import CoordTestMixin Pair = collections.namedtuple("Pair", "points bounds") @@ -482,7 +482,7 @@ def test_numeric_nd_multidim_bounds_warning(self): "Collapsing a multi-dimensional coordinate. " "Metadata may not be fully descriptive for 'y'." ) - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisVagueMetadataWarning, msg): coord.collapsed() def test_lazy_nd_multidim_bounds_warning(self): @@ -493,7 +493,7 @@ def test_lazy_nd_multidim_bounds_warning(self): "Collapsing a multi-dimensional coordinate. " "Metadata may not be fully descriptive for 'y'." ) - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisVagueMetadataWarning, msg): coord.collapsed() def test_numeric_nd_noncontiguous_bounds_warning(self): @@ -504,7 +504,7 @@ def test_numeric_nd_noncontiguous_bounds_warning(self): "Collapsing a non-contiguous coordinate. " "Metadata may not be fully descriptive for 'y'." ) - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisVagueMetadataWarning, msg): coord.collapsed() def test_lazy_nd_noncontiguous_bounds_warning(self): @@ -515,7 +515,7 @@ def test_lazy_nd_noncontiguous_bounds_warning(self): "Collapsing a non-contiguous coordinate. " "Metadata may not be fully descriptive for 'y'." ) - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisVagueMetadataWarning, msg): coord.collapsed() def test_numeric_3_bounds(self): @@ -530,7 +530,7 @@ def test_numeric_3_bounds(self): r"1D coordinates with 2 bounds. Metadata may not be fully " r"descriptive for 'x'. Ignoring bounds." ) - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisVagueMetadataWarning, msg): collapsed_coord = coord.collapsed() self.assertFalse(collapsed_coord.has_lazy_points()) @@ -553,7 +553,7 @@ def test_lazy_3_bounds(self): r"1D coordinates with 2 bounds. Metadata may not be fully " r"descriptive for 'x'. Ignoring bounds." ) - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisVagueMetadataWarning, msg): collapsed_coord = coord.collapsed() self.assertTrue(collapsed_coord.has_lazy_points()) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py index 88e3ad1edb..641b6b7b44 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py +++ b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py @@ -227,7 +227,9 @@ def operation(warn: bool): # Missing warning. warn_regex = rf"Missing CF-netCDF auxiliary coordinate variable {subject_name}.*" - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns( + iris.exceptions.IrisCfMissingVarWarning, match=warn_regex + ): operation(warn=True) with pytest.warns() as record: operation(warn=False) @@ -239,7 +241,9 @@ def operation(warn: bool): vars_all[subject_name] = netcdf_ugrid_variable( subject_name, "", np.bytes_ ) - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns( + iris.exceptions.IrisCfLabelVarWarning, match=warn_regex + ): operation(warn=True) with pytest.warns() as record: operation(warn=False) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py index e9ef40a1e5..5a68a8c03f 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py +++ b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py @@ -216,7 +216,9 @@ def operation(warn: bool): warn_regex = ( rf"Missing CF-UGRID connectivity variable {subject_name}.*" ) - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns( + iris.exceptions.IrisCfMissingVarWarning, match=warn_regex + ): operation(warn=True) with pytest.warns() as record: operation(warn=False) @@ -228,7 +230,9 @@ def operation(warn: bool): vars_all[subject_name] = netcdf_ugrid_variable( subject_name, "", np.bytes_ ) - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns( + iris.exceptions.IrisCfLabelVarWarning, match=warn_regex + ): operation(warn=True) with pytest.warns() as record: operation(warn=False) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py index b63376719c..8302c30177 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py +++ b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py @@ -257,7 +257,9 @@ def operation(warn: bool): # Missing warning. warn_regex = rf"Missing CF-UGRID mesh variable {subject_name}.*" - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns( + iris.exceptions.IrisCfMissingVarWarning, match=warn_regex + ): operation(warn=True) with pytest.warns() as record: operation(warn=False) @@ -269,7 +271,9 @@ def operation(warn: bool): vars_all[subject_name] = netcdf_ugrid_variable( subject_name, "", np.bytes_ ) - with pytest.warns(UserWarning, match=warn_regex): + with pytest.warns( + iris.exceptions.IrisCfLabelVarWarning, match=warn_regex + ): operation(warn=True) with pytest.warns() as record: operation(warn=False) diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py index 399a987f11..38882810d2 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py @@ -12,6 +12,7 @@ import tempfile import warnings +from iris.exceptions import IrisLoadWarning import iris.fileformats._nc_load_rules.engine from iris.fileformats.cf import CFReader import iris.fileformats.netcdf @@ -138,7 +139,7 @@ def run_testcase(self, warning_regex=None, **testcase_kwargs): if warning_regex is None: context = self.assertNoWarningsRegexp() else: - context = self.assertWarnsRegex(UserWarning, warning_regex) + context = self.assertWarnsRegex(IrisLoadWarning, warning_regex) with context: cube = self.load_cube_from_cdl(cdl_string, cdl_path, nc_path) diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_parse_cell_methods.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_parse_cell_methods.py index 729a2d8b14..9935a6e5ae 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_parse_cell_methods.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_parse_cell_methods.py @@ -15,6 +15,7 @@ from unittest import mock from iris.coords import CellMethod +from iris.exceptions import IrisCfLoadWarning from iris.fileformats._nc_load_rules.helpers import parse_cell_methods @@ -123,7 +124,7 @@ def test_comment_bracket_mismatch_warning(self): ] for cell_method_str in cell_method_strings: with self.assertWarns( - UserWarning, + IrisCfLoadWarning, msg="Cell methods may be incorrectly parsed due to mismatched brackets", ): _ = parse_cell_methods(cell_method_str) @@ -139,7 +140,7 @@ def test_badly_formatted_warning(self): ] for cell_method_str in cell_method_strings: with self.assertWarns( - UserWarning, + IrisCfLoadWarning, msg=f"Failed to fully parse cell method string: {cell_method_str}", ): _ = parse_cell_methods(cell_method_str) 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 12af318c01..af0d7bcd30 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver.py +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver.py @@ -31,6 +31,7 @@ ) from iris.coords import AuxCoord, DimCoord from iris.cube import Cube +from iris.exceptions import IrisMaskValueMatchWarning from iris.fileformats.netcdf import Saver, _thread_safe_nc import iris.tests.stock as stock @@ -555,7 +556,7 @@ def test_contains_fill_value_passed(self): cube = self._make_cube(">f4") fill_value = 1 with self.assertWarnsRegex( - UserWarning, + IrisMaskValueMatchWarning, "contains unmasked data points equal to the fill-value", ): with self._netCDF_var(cube, fill_value=fill_value): @@ -567,7 +568,7 @@ def test_contains_fill_value_byte(self): cube = self._make_cube(">i1") fill_value = 1 with self.assertWarnsRegex( - UserWarning, + IrisMaskValueMatchWarning, "contains unmasked data points equal to the fill-value", ): with self._netCDF_var(cube, fill_value=fill_value): @@ -579,7 +580,7 @@ def test_contains_default_fill_value(self): cube = self._make_cube(">f4") cube.data[0, 0] = _thread_safe_nc.default_fillvals["f4"] with self.assertWarnsRegex( - UserWarning, + IrisMaskValueMatchWarning, "contains unmasked data points equal to the fill-value", ): with self._netCDF_var(cube): diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__lazy_stream_data.py b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__lazy_stream_data.py index 9686c88abf..da5f2d88fa 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__lazy_stream_data.py +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/test_Saver__lazy_stream_data.py @@ -18,6 +18,7 @@ import numpy as np import pytest +from iris.exceptions import IrisMaskValueMatchWarning import iris.fileformats.netcdf._thread_safe_nc as threadsafe_nc from iris.fileformats.netcdf.saver import Saver, _FillvalueCheckInfo @@ -183,5 +184,5 @@ def test_warnings(self, compute, data_form): if n_expected_warnings > 0: warning = issued_warnings[0] msg = "contains unmasked data points equal to the fill-value, 2.0" - assert isinstance(warning, UserWarning) + assert isinstance(warning, IrisMaskValueMatchWarning) assert msg in warning.args[0] diff --git a/lib/iris/tests/unit/fileformats/pp/test_PPField.py b/lib/iris/tests/unit/fileformats/pp/test_PPField.py index 316894ded1..f2bbf97a80 100644 --- a/lib/iris/tests/unit/fileformats/pp/test_PPField.py +++ b/lib/iris/tests/unit/fileformats/pp/test_PPField.py @@ -13,6 +13,7 @@ import numpy as np +from iris.exceptions import IrisDefaultingWarning, IrisMaskValueMatchWarning import iris.fileformats.pp as pp from iris.fileformats.pp import PPField, SplittableInt @@ -91,7 +92,7 @@ def field_checksum(data): data_64 = np.linspace(0, 1, num=10, endpoint=False).reshape(2, 5) checksum_32 = field_checksum(data_64.astype(">f4")) msg = "Downcasting array precision from float64 to float32 for save." - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisDefaultingWarning, msg): checksum_64 = field_checksum(data_64.astype(">f8")) self.assertEqual(checksum_32, checksum_64) @@ -104,7 +105,7 @@ def test_masked_mdi_value_warning(self): [1.0, field.bmdi, 3.0], dtype=np.float32 ) msg = "PPField data contains unmasked points" - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisMaskValueMatchWarning, msg): with self.temp_filename(".pp") as temp_filename: with open(temp_filename, "wb") as pp_file: field.save(pp_file) @@ -116,7 +117,7 @@ def test_unmasked_mdi_value_warning(self): # Make float32 data, as float64 default produces an extra warning. field.data = np.array([1.0, field.bmdi, 3.0], dtype=np.float32) msg = "PPField data contains unmasked points" - with self.assertWarnsRegex(UserWarning, msg): + with self.assertWarnsRegex(IrisMaskValueMatchWarning, msg): with self.temp_filename(".pp") as temp_filename: with open(temp_filename, "wb") as pp_file: field.save(pp_file) From 01db6a990cfb97cfb4aeeecb974af27ad4070a90 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Wed, 20 Sep 2023 15:53:47 +0100 Subject: [PATCH 14/14] What's New entry. --- docs/src/whatsnew/latest.rst | 8 +++++--- lib/iris/exceptions.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 35b6121c41..a3abf6c5ad 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -30,7 +30,9 @@ This document explains the changes made to Iris for this release ✨ Features =========== -#. N/A +#. `@trexfeathers`_ and `@HGWright`_ (reviewer) sub-categorised all Iris' + :class:`UserWarning`\s for richer filtering. The full index of + sub-categories can be seen here: :mod:`iris.exceptions` . (:pull:`5498`) 🐛 Bugs Fixed @@ -38,7 +40,7 @@ This document explains the changes made to Iris for this release #. `@scottrobinson02`_ fixed the output units when dividing a coordinate by a cube. (:issue:`5305`, :pull:`5331`) - + #. `@ESadek-MO`_ has updated :mod:`iris.tests.graphics.idiff` to stop duplicated file names preventing acceptance. (:issue:`5098`, :pull:`5482`) @@ -84,7 +86,7 @@ This document explains the changes made to Iris for this release #. `@trexfeathers`_ replaced all uses of the ``logging.WARNING`` level, in favour of using Python warnings, following team agreement. (:pull:`5488`) - + #. `@trexfeathers`_ adapted benchmarking to work with ASV ``>=v0.6`` by no longer using the ``--strict`` argument. (:pull:`5496`) diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index 75f97462a3..919917a01d 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -189,7 +189,7 @@ class CannotAddError(ValueError): class IrisUserWarning(UserWarning): """ - Base class for :class:`~python.warnings.UserWarnings` generated by Iris. + Base class for :class:`UserWarning`\\ s generated by Iris. """ pass