From a74fb835564d6c5b0b8f7f74e244e06da6c40a98 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Wed, 20 Sep 2023 14:42:04 +0100 Subject: [PATCH 1/8] prevent warning repeats --- lib/iris/exceptions.py | 19 +++++++++++++++++++ .../fileformats/_nc_load_rules/helpers.py | 2 +- .../tests/integration/netcdf/test_general.py | 9 ++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index 0fb06f3b26..dbf400cf20 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -3,6 +3,9 @@ # This file is part of Iris and is released under the BSD license. # See LICENSE in the root of the repository for full licensing details. """Exceptions specific to the Iris package.""" +from functools import lru_cache +import traceback +import warnings class IrisError(Exception): @@ -344,3 +347,19 @@ class IrisSaverFillValueWarning(IrisMaskValueMatchWarning, IrisSaveWarning): """ pass + + +@lru_cache(None) +def warn_once(msg, type, stacklevel, frame): + """Raise a warning only if a similar one has not been raised before.""" + warnings.warn(msg, type, stacklevel=stacklevel) + + +def warn_once_at_level(msg, type, stacklevel): + """ + Raise a warning only if a similar one hasn't been raised from the same line + for a given stack level. + """ + stacklevel += 1 + frame = traceback.format_stack()[-stacklevel] + warn_once(msg, type, stacklevel + 1, frame) diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 98357c1f26..cee6045f4a 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -520,7 +520,7 @@ def _get_ellipsoid(cf_grid_var): "applied. To apply the datum when loading, use the " "iris.FUTURE.datum_support flag." ) - warnings.warn(wmsg, category=FutureWarning, stacklevel=14) + iris.exceptions.warn_once_at_level(wmsg, FutureWarning, category=FutureWarning, stacklevel=14) datum = None if datum is not None: diff --git a/lib/iris/tests/integration/netcdf/test_general.py b/lib/iris/tests/integration/netcdf/test_general.py index 751c160805..945c8be4b9 100644 --- a/lib/iris/tests/integration/netcdf/test_general.py +++ b/lib/iris/tests/integration/netcdf/test_general.py @@ -505,7 +505,7 @@ def test_datum_once(self): fnames = [ "false_east_north_merc.nc", "non_unit_scale_factor_merc.nc", - # toa_brightness_temperature.nc, + "toa_brightness_temperature.nc", ] fpaths = [ tests.get_data_path(("NetCDF", "mercator", fname)) for fname in fnames @@ -518,6 +518,13 @@ def test_datum_once(self): warnings.warn("Dummy warning", category=iris.exceptions.IrisUserWarning) assert len(record) == 2 + with warnings.catch_warnings(record=True) as record: + warnings.simplefilter("default") + iris.load_cube(fpaths[0]) + iris.load_cube(fpaths[1]) + iris.load_cube(fpaths[2]) + assert len(record) == 3 + if __name__ == "__main__": tests.main() From 8e45ceb42f00dbc8ebd04fb76f850f2871949e37 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Sat, 10 Feb 2024 23:45:24 +0000 Subject: [PATCH 2/8] replace all warnings --- lib/iris/_concatenate.py | 4 +- lib/iris/_deprecation.py | 4 +- lib/iris/analysis/_regrid.py | 4 +- lib/iris/analysis/calculus.py | 4 +- lib/iris/analysis/cartography.py | 14 +++---- lib/iris/analysis/geometry.py | 10 ++--- lib/iris/analysis/maths.py | 4 +- lib/iris/aux_factory.py | 38 +++++++++--------- lib/iris/config.py | 6 +-- lib/iris/coord_systems.py | 6 +-- lib/iris/coords.py | 10 ++--- lib/iris/cube.py | 6 +-- lib/iris/exceptions.py | 2 +- lib/iris/experimental/regrid.py | 4 +- lib/iris/experimental/ugrid/cf.py | 14 +++---- lib/iris/experimental/ugrid/load.py | 8 ++-- lib/iris/fileformats/_ff.py | 16 ++++---- .../fileformats/_nc_load_rules/actions.py | 6 +-- .../fileformats/_nc_load_rules/helpers.py | 39 ++++++++++--------- lib/iris/fileformats/cf.py | 24 ++++++------ lib/iris/fileformats/name_loaders.py | 6 +-- lib/iris/fileformats/netcdf/loader.py | 6 +-- lib/iris/fileformats/netcdf/saver.py | 18 ++++----- lib/iris/fileformats/nimrod_load_rules.py | 12 +++--- lib/iris/fileformats/pp.py | 12 +++--- lib/iris/fileformats/pp_save_rules.py | 4 +- lib/iris/fileformats/rules.py | 8 ++-- lib/iris/iterate.py | 4 +- lib/iris/pandas.py | 6 +-- lib/iris/plot.py | 6 +-- 30 files changed, 153 insertions(+), 152 deletions(-) diff --git a/lib/iris/_concatenate.py b/lib/iris/_concatenate.py index 2a73fcacea..7330928fe3 100644 --- a/lib/iris/_concatenate.py +++ b/lib/iris/_concatenate.py @@ -5,7 +5,7 @@ """Automatic concatenation of multiple cubes over one or more existing dimensions.""" from collections import defaultdict, namedtuple -import warnings +from iris.exceptions import warn_once_at_level import dask.array as da import numpy as np @@ -911,7 +911,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, category=iris.exceptions.IrisUserWarning) + warn_once_at_level(msg, category=iris.exceptions.IrisUserWarning) # Check for compatible AuxCoords. if match: diff --git a/lib/iris/_deprecation.py b/lib/iris/_deprecation.py index b771883a71..0870061d8b 100644 --- a/lib/iris/_deprecation.py +++ b/lib/iris/_deprecation.py @@ -4,7 +4,7 @@ # See LICENSE in the root of the repository for full licensing details. """Utilities for producing runtime deprecation messages.""" -import warnings +from iris.exceptions import warn_once_at_level class IrisDeprecation(UserWarning): @@ -44,7 +44,7 @@ def warn_deprecated(msg, stacklevel=2): >>> """ - warnings.warn(msg, category=IrisDeprecation, stacklevel=stacklevel) + warn_once_at_level(msg, category=IrisDeprecation, stacklevel=stacklevel) # A Mixin for a wrapper class that copies the docstring of the wrapped class diff --git a/lib/iris/analysis/_regrid.py b/lib/iris/analysis/_regrid.py index 4a8ba0bb67..637e380500 100644 --- a/lib/iris/analysis/_regrid.py +++ b/lib/iris/analysis/_regrid.py @@ -5,7 +5,7 @@ import copy import functools -import warnings +from iris.exceptions import warn_once_at_level import numpy as np import numpy.ma as ma @@ -1106,6 +1106,6 @@ def regrid_reference_surface( "Cannot update aux_factory {!r} because of dropped" " coordinates.".format(factory.name()) ) - warnings.warn(msg, category=IrisImpossibleUpdateWarning) + warn_once_at_level(msg, category=IrisImpossibleUpdateWarning) return result diff --git a/lib/iris/analysis/calculus.py b/lib/iris/analysis/calculus.py index 560b9b1d5c..4e04737c3a 100644 --- a/lib/iris/analysis/calculus.py +++ b/lib/iris/analysis/calculus.py @@ -9,7 +9,7 @@ """ import re -import warnings +from iris.exceptions import warn_once_at_level import cf_units import numpy as np @@ -85,7 +85,7 @@ def _construct_midpoint_coord(coord, circular=None): "Construction coordinate midpoints for the '{}' coordinate, " "though it has the attribute 'circular'={}." ) - warnings.warn( + warn_once_at_level( msg.format(circular, coord.circular, coord.name()), category=IrisUserWarning, ) diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index d57f8ce8a3..3f20c0b891 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -6,7 +6,7 @@ from collections import namedtuple import copy -import warnings +from iris.exceptions import warn_once_at_level import cartopy.crs as ccrs import cartopy.img_transform @@ -410,7 +410,7 @@ 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( + warn_once_at_level( "Assuming spherical earth from ellipsoid.", category=iris.exceptions.IrisDefaultingWarning, ) @@ -419,13 +419,13 @@ def area_weights(cube, normalize=False): cs.ellipsoid is not None ): if cs.ellipsoid.inverse_flattening != 0.0: - warnings.warn( + warn_once_at_level( "Assuming spherical earth from ellipsoid.", category=iris.exceptions.IrisDefaultingWarning, ) radius_of_earth = cs.ellipsoid.semi_major_axis else: - warnings.warn( + warn_once_at_level( "Using DEFAULT_SPHERICAL_EARTH_RADIUS.", category=iris.exceptions.IrisDefaultingWarning, ) @@ -567,7 +567,7 @@ def cosine_latitude_weights(cube): if np.any(lat.points < -np.pi / 2.0 - threshold) or np.any( lat.points > np.pi / 2.0 + threshold ): - warnings.warn( + warn_once_at_level( "Out of range latitude values will be clipped to the valid range.", category=iris.exceptions.IrisDefaultingWarning, ) @@ -683,7 +683,7 @@ def project(cube, target_proj, nx=None, ny=None): # Determine source coordinate system if lat_coord.coord_system is None: # Assume WGS84 latlon if unspecified - warnings.warn( + warn_once_at_level( "Coordinate system of latitude and longitude " "coordinates is not specified. Assuming WGS84 Geodetic.", category=iris.exceptions.IrisDefaultingWarning, @@ -871,7 +871,7 @@ def project(cube, target_proj, nx=None, ny=None): new_cube.add_aux_coord(coord.copy(), cube.coord_dims(coord)) discarded_coords = coords_to_ignore.difference([lat_coord, lon_coord]) if discarded_coords: - warnings.warn( + warn_once_at_level( "Discarding coordinates that share dimensions with {} and {}: {}".format( lat_coord.name(), lon_coord.name(), diff --git a/lib/iris/analysis/geometry.py b/lib/iris/analysis/geometry.py index 271858716c..be446c3b3d 100644 --- a/lib/iris/analysis/geometry.py +++ b/lib/iris/analysis/geometry.py @@ -9,7 +9,7 @@ """ -import warnings +from iris.exceptions import warn_once_at_level import numpy as np from shapely.geometry import Polygon @@ -69,7 +69,7 @@ def _extract_relevant_cube_slice(cube, geometry): x_min_ix = np.where(x_bounds_lower <= x_min_geom)[0] x_min_ix = x_min_ix[np.argmax(x_bounds_lower[x_min_ix])] except ValueError: - warnings.warn( + warn_once_at_level( "The geometry exceeds the cube's x dimension at the lower end.", category=iris.exceptions.IrisGeometryExceedWarning, ) @@ -79,7 +79,7 @@ def _extract_relevant_cube_slice(cube, geometry): x_max_ix = np.where(x_bounds_upper >= x_max_geom)[0] x_max_ix = x_max_ix[np.argmin(x_bounds_upper[x_max_ix])] except ValueError: - warnings.warn( + warn_once_at_level( "The geometry exceeds the cube's x dimension at the upper end.", category=iris.exceptions.IrisGeometryExceedWarning, ) @@ -89,7 +89,7 @@ def _extract_relevant_cube_slice(cube, geometry): y_min_ix = np.where(y_bounds_lower <= y_min_geom)[0] y_min_ix = y_min_ix[np.argmax(y_bounds_lower[y_min_ix])] except ValueError: - warnings.warn( + warn_once_at_level( "The geometry exceeds the cube's y dimension at the lower end.", category=iris.exceptions.IrisGeometryExceedWarning, ) @@ -99,7 +99,7 @@ def _extract_relevant_cube_slice(cube, geometry): y_max_ix = np.where(y_bounds_upper >= y_max_geom)[0] y_max_ix = y_max_ix[np.argmin(y_bounds_upper[y_max_ix])] except ValueError: - warnings.warn( + warn_once_at_level( "The geometry exceeds the cube's y dimension at the upper end.", category=iris.exceptions.IrisGeometryExceedWarning, ) diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index f20972d1e5..a6999c5c3d 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -8,7 +8,7 @@ import inspect import math import operator -import warnings +from iris.exceptions import warn_once_at_level import cf_units import dask.array as da @@ -938,7 +938,7 @@ def _broadcast_cube_coord_data(cube, other, operation_name, dim=None): raise iris.exceptions.CoordinateMultiDimError(other) if other.has_bounds(): - warnings.warn( + warn_once_at_level( "Using {!r} with a bounded coordinate is not well " "defined; ignoring bounds.".format(operation_name), category=iris.exceptions.IrisIgnoringBoundsWarning, diff --git a/lib/iris/aux_factory.py b/lib/iris/aux_factory.py index 61b7c7ef46..f81fe9a52d 100644 --- a/lib/iris/aux_factory.py +++ b/lib/iris/aux_factory.py @@ -5,7 +5,7 @@ """Definitions of derived coordinates.""" from abc import ABCMeta, abstractmethod -import warnings +from iris.exceptions import warn_once_at_level import cf_units import dask.array as da @@ -429,7 +429,7 @@ def _check_dependencies(pressure_at_top, sigma, surface_air_pressure): f"Coordinate '{coord.name()}' has bounds. These will " "be disregarded" ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) # Check units if sigma.units.is_unknown(): @@ -502,7 +502,7 @@ def make_coord(self, coord_dims_func): if sigma.shape[-1:] not in ok_bound_shapes: raise ValueError("Invalid sigma coordinate bounds") if pressure_at_top.shape[-1:] not in [(), (1,)]: - warnings.warn( + warn_once_at_level( "Pressure at top coordinate has bounds. These are being " "disregarded", category=IrisIgnoringBoundsWarning, @@ -511,7 +511,7 @@ def make_coord(self, coord_dims_func): bds_shape = list(pressure_at_top_pts.shape) + [1] pressure_at_top = pressure_at_top_pts.reshape(bds_shape) if surface_air_pressure.shape[-1:] not in [(), (1,)]: - warnings.warn( + warn_once_at_level( "Surface pressure coordinate has bounds. These are being " "disregarded", category=IrisIgnoringBoundsWarning, @@ -572,7 +572,7 @@ def __init__(self, delta=None, sigma=None, orography=None): "Orography coordinate {!r} has bounds." " These will be disregarded.".format(orography.name()) ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) self.delta = delta self.sigma = sigma @@ -648,7 +648,7 @@ def make_coord(self, coord_dims_func): if sigma.shape[-1:] not in ok_bound_shapes: raise ValueError("Invalid sigma coordinate bounds.") if orography.shape[-1:] not in [(), (1,)]: - warnings.warn( + warn_once_at_level( "Orography coordinate has bounds. These are being disregarded.", category=IrisIgnoringBoundsWarning, stacklevel=2, @@ -704,7 +704,7 @@ def update(self, old_coord, new_coord=None): "Orography coordinate {!r} has bounds." " These will be disregarded.".format(new_coord.name()) ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) self.orography = new_coord @@ -768,7 +768,7 @@ 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, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) # Check units. if sigma is not None and sigma.units.is_unknown(): @@ -854,7 +854,7 @@ def make_coord(self, coord_dims_func): if sigma.shape[-1:] not in ok_bound_shapes: raise ValueError("Invalid sigma coordinate bounds.") if surface_air_pressure.shape[-1:] not in [(), (1,)]: - warnings.warn( + warn_once_at_level( "Surface pressure coordinate has bounds. " "These are being disregarded.", category=IrisIgnoringBoundsWarning, @@ -968,7 +968,7 @@ 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, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) for coord, term in ((depth_c, "depth_c"), (nsigma, "nsigma")): if coord is not None and coord.shape != (1,): @@ -1125,7 +1125,7 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(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) @@ -1205,7 +1205,7 @@ def _check_dependencies(sigma, eta, depth): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) # Check units. if sigma is not None and sigma.units.is_unknown(): @@ -1281,7 +1281,7 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(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) @@ -1372,7 +1372,7 @@ 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, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) if depth_c is not None and depth_c.shape != (1,): msg = "Expected scalar {} coordinate {!r}: got shape {!r}.".format( @@ -1466,7 +1466,7 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(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) @@ -1561,7 +1561,7 @@ 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, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) coords = ((a, "a"), (b, "b"), (depth_c, "depth_c")) for coord, term in coords: @@ -1658,7 +1658,7 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(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) @@ -1753,7 +1753,7 @@ 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, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) if depth_c is not None and depth_c.shape != (1,): msg = "Expected scalar depth_c coordinate {!r}: got shape {!r}.".format( @@ -1847,7 +1847,7 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warnings.warn(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level(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 c617783dec..e5822f3002 100644 --- a/lib/iris/config.py +++ b/lib/iris/config.py @@ -29,7 +29,7 @@ import contextlib import logging import os.path -import warnings +from iris.exceptions import warn_once_at_level import iris.exceptions @@ -137,7 +137,7 @@ 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( + warn_once_at_level( msg.format(section, option, c_path), category=iris.exceptions.IrisIgnoringWarning, ) @@ -244,7 +244,7 @@ def __setattr__(self, name, value): "Attempting to set invalid value {!r} for " "attribute {!r}. Defaulting to {!r}." ) - warnings.warn( + warn_once_at_level( wmsg.format(value, name, good_value), category=iris.exceptions.IrisDefaultingWarning, ) diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 5b4e593d83..91610d3377 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -7,7 +7,7 @@ from abc import ABCMeta, abstractmethod from functools import cached_property import re -import warnings +from iris.exceptions import warn_once_at_level import cartopy.crs as ccrs import numpy as np @@ -439,7 +439,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, category=iris.exceptions.IrisUserWarning) + warn_once_at_level(wmsg, category=iris.exceptions.IrisUserWarning) value = float(value) self._inverse_flattening = value @@ -765,7 +765,7 @@ def __repr__(self): def as_cartopy_crs(self): globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe()) - warnings.warn( + warn_once_at_level( "Discarding false_easting and false_northing that are " "not used by Cartopy.", category=iris.exceptions.IrisDefaultingWarning, diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 8cbe0984cd..3f61e67e93 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -11,7 +11,7 @@ from functools import lru_cache from itertools import zip_longest import operator -import warnings +from iris.exceptions import warn_once_at_level import zlib import dask.array as da @@ -1974,7 +1974,7 @@ def contiguous_bounds(self): """ if not self.has_bounds(): if self.ndim == 1: - warnings.warn( + warn_once_at_level( "Coordinate {!r} is not bounded, guessing " "contiguous bounds.".format(self.name()), category=iris.exceptions.IrisGuessBoundsWarning, @@ -2136,7 +2136,7 @@ def serialize(x): "Collapsing a multi-dimensional coordinate. " "Metadata may not be fully descriptive for {!r}." ) - warnings.warn( + warn_once_at_level( msg.format(self.name()), category=iris.exceptions.IrisVagueMetadataWarning, ) @@ -2149,7 +2149,7 @@ def serialize(x): "Metadata may not be fully descriptive for {!r}. " "Ignoring bounds." ) - warnings.warn( + warn_once_at_level( msg.format(str(exc), self.name()), category=iris.exceptions.IrisVagueMetadataWarning, ) @@ -2160,7 +2160,7 @@ def serialize(x): "Collapsing a non-contiguous coordinate. " "Metadata may not be fully descriptive for {!r}." ) - warnings.warn( + warn_once_at_level( msg.format(self.name()), category=iris.exceptions.IrisVagueMetadataWarning, ) diff --git a/lib/iris/cube.py b/lib/iris/cube.py index a6f283100a..8ac0bff2f0 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -20,7 +20,7 @@ Optional, Union, ) -import warnings +from iris.exceptions import warn_once_at_level from xml.dom.minidom import Document import zlib @@ -4040,7 +4040,7 @@ def collapsed(self, coords, aggregator, **kwargs): lat_match = [coord for coord in coords if "latitude" in coord.name()] if lat_match: for coord in lat_match: - warnings.warn( + warn_once_at_level( msg.format(coord.name()), category=iris.exceptions.IrisUserWarning, ) @@ -4593,7 +4593,7 @@ def rolling_window(self, coord, aggregator, window, **kwargs): # now update all of the coordinates to reflect the aggregation for coord_ in self.coords(dimensions=dimension): if coord_.has_bounds(): - warnings.warn( + warn_once_at_level( "The bounds of coordinate %r were ignored in " "the rolling window operation." % coord_.name(), category=iris.exceptions.IrisIgnoringBoundsWarning, diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index dbf400cf20..9b0e84bc45 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -355,7 +355,7 @@ def warn_once(msg, type, stacklevel, frame): warnings.warn(msg, type, stacklevel=stacklevel) -def warn_once_at_level(msg, type, stacklevel): +def warn_once_at_level(msg, type=None, stacklevel=0): """ Raise a warning only if a similar one hasn't been raised from the same line for a given stack level. diff --git a/lib/iris/experimental/regrid.py b/lib/iris/experimental/regrid.py index 1bea933fbf..4f56a1b6e8 100644 --- a/lib/iris/experimental/regrid.py +++ b/lib/iris/experimental/regrid.py @@ -18,7 +18,7 @@ """ import copy import functools -import warnings +from iris.exceptions import warn_once_at_level import cartopy.crs as ccrs import numpy as np @@ -517,7 +517,7 @@ def regrid_reference_surface( "Cannot update aux_factory {!r} because of dropped" " coordinates.".format(factory.name()) ) - warnings.warn(msg, category=IrisImpossibleUpdateWarning) + warn_once_at_level(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 d00fd6ef24..b958b9afcd 100644 --- a/lib/iris/experimental/ugrid/cf.py +++ b/lib/iris/experimental/ugrid/cf.py @@ -8,7 +8,7 @@ Eventual destination: :mod:`iris.fileformats.cf`. """ -import warnings +from iris.exceptions import warn_once_at_level from ...exceptions import IrisCfLabelVarWarning, IrisCfMissingVarWarning from ...fileformats import cf @@ -65,7 +65,7 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"{nc_var_name}" ) if warn: - warnings.warn(message, category=IrisCfMissingVarWarning) + warn_once_at_level(message, category=IrisCfMissingVarWarning) else: # Restrict to non-string type i.e. not a # CFLabelVariable. @@ -80,7 +80,7 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"CF-netCDF label variable." ) if warn: - warnings.warn( + warn_once_at_level( message, category=IrisCfLabelVarWarning ) @@ -139,7 +139,7 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"variable {nc_var_name}" ) if warn: - warnings.warn( + warn_once_at_level( message, category=IrisCfMissingVarWarning, ) @@ -158,7 +158,7 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"CF-netCDF label variable." ) if warn: - warnings.warn( + warn_once_at_level( message, category=IrisCfLabelVarWarning, ) @@ -217,7 +217,7 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"referenced by netCDF variable {nc_var_name}" ) if warn: - warnings.warn(message, category=IrisCfMissingVarWarning) + warn_once_at_level(message, category=IrisCfMissingVarWarning) else: # Restrict to non-string type i.e. not a # CFLabelVariable. @@ -230,7 +230,7 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"variable." ) if warn: - warnings.warn(message, category=IrisCfLabelVarWarning) + warn_once_at_level(message, category=IrisCfLabelVarWarning) return result diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index 8ba7448e8c..7df8018a67 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -15,7 +15,7 @@ from itertools import groupby from pathlib import Path import threading -import warnings +from iris.exceptions import warn_once_at_level from ...config import get_logger from ...coords import AuxCoord @@ -356,7 +356,7 @@ def _build_mesh(cf, mesh_var, file_path): cf_role_message = f"{mesh_var.cf_name} has an inappropriate cf_role: {cf_role}." if cf_role_message: cf_role_message += " Correcting to 'mesh_topology'." - warnings.warn( + warn_once_at_level( cf_role_message, category=_WarnComboCfDefaulting, ) @@ -377,7 +377,7 @@ def _build_mesh(cf, mesh_var, file_path): f" : *Assuming* topology_dimension={topology_dimension}" ", consistent with the attached connectivities." ) - warnings.warn(msg, category=_WarnComboCfDefaulting) + warn_once_at_level(msg, category=_WarnComboCfDefaulting) else: quoted_topology_dimension = mesh_var.topology_dimension if quoted_topology_dimension != topology_dimension: @@ -389,7 +389,7 @@ def _build_mesh(cf, mesh_var, file_path): f"{quoted_topology_dimension}" " -- ignoring this as it is inconsistent." ) - warnings.warn( + warn_once_at_level( msg, category=_WarnComboCfDefaultingIgnoring, ) diff --git a/lib/iris/fileformats/_ff.py b/lib/iris/fileformats/_ff.py index d2ce1bcefb..4e9721a585 100644 --- a/lib/iris/fileformats/_ff.py +++ b/lib/iris/fileformats/_ff.py @@ -5,7 +5,7 @@ """Provides UK Met Office Fields File (FF) format specific capabilities.""" import os -import warnings +from iris.exceptions import warn_once_at_level import numpy as np @@ -419,7 +419,7 @@ def grid(self): grid_class = self.GRID_STAGGERING_CLASS.get(self.grid_staggering) if grid_class is None: grid_class = NewDynamics - warnings.warn( + warn_once_at_level( "Staggered grid type: {} not currently interpreted, assuming " "standard C-grid".format(self.grid_staggering), category=_WarnComboLoadingDefaulting, @@ -539,7 +539,7 @@ def range_order(range1, range2, resolution): "may be incorrect, not having taken into account the " "boundary size." ) - warnings.warn(msg, category=IrisLoadWarning) + warn_once_at_level(msg, category=IrisLoadWarning) else: range2 = field_dim[0] - res_low range1 = field_dim[0] - halo_dim * res_low @@ -608,7 +608,7 @@ def _adjust_field_for_lbc(self, field): field.y = self._det_border(field.y, boundary_packing.y_halo) else: if field.bdy < 0: - warnings.warn( + warn_once_at_level( "The LBC has a bdy less than 0. No " "case has previously been seen of " "this, and the decompression may be " @@ -720,7 +720,7 @@ def _extract_field(self): else: subgrid = stash_entry.grid_code if subgrid not in HANDLED_GRIDS: - warnings.warn( + warn_once_at_level( "The stash code {} is on a grid {} " "which has not been explicitly " "handled by the fieldsfile loader." @@ -742,7 +742,7 @@ def _extract_field(self): "STASH to grid type mapping. Picking the P " "position as the cell type".format(stash) ) - warnings.warn( + warn_once_at_level( msg, category=_WarnComboLoadingDefaulting, ) @@ -751,7 +751,7 @@ def _extract_field(self): field.bplat = grid.pole_lat field.bplon = grid.pole_lon elif no_x or no_y: - warnings.warn( + warn_once_at_level( "Partially missing X or Y coordinate values.", category=IrisLoadWarning, ) @@ -799,7 +799,7 @@ def _extract_field(self): "Input field skipped as PPField creation failed :" " error = {!r}" ) - warnings.warn(msg.format(str(valerr)), category=IrisLoadWarning) + warn_once_at_level(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 fefa58ad10..a9b5cdd2f0 100644 --- a/lib/iris/fileformats/_nc_load_rules/actions.py +++ b/lib/iris/fileformats/_nc_load_rules/actions.py @@ -40,7 +40,7 @@ """ from functools import wraps -import warnings +from iris.exceptions import warn_once_at_level from iris.config import get_logger import iris.exceptions @@ -495,7 +495,7 @@ 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( + warn_once_at_level( msg, category=_WarnComboCfLoadIgnoring, ) @@ -513,7 +513,7 @@ 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( + warn_once_at_level( msg, category=_WarnComboLoadIgnoring, ) diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index cee6045f4a..01ee90593c 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -29,6 +29,7 @@ import iris.coord_systems import iris.coords import iris.exceptions +from iris.exceptions import warn_once_at_level import iris.fileformats.cf as cf import iris.fileformats.netcdf from iris.fileformats.netcdf.loader import _get_cf_var_data @@ -299,7 +300,7 @@ def _split_cell_methods(nc_cell_methods: str) -> List[re.Match]: "Cell methods may be incorrectly parsed due to mismatched " "brackets" ) - warnings.warn( + warn_once_at_level( msg, category=iris.exceptions.IrisCfLoadWarning, stacklevel=2, @@ -320,7 +321,7 @@ def _split_cell_methods(nc_cell_methods: str) -> List[re.Match]: nc_cell_method_match = _CM_PARSE.match(nc_cell_method_str.strip()) if not nc_cell_method_match: msg = f"Failed to fully parse cell method string: {nc_cell_methods}" - warnings.warn(msg, category=iris.exceptions.IrisCfLoadWarning, stacklevel=2) + warn_once_at_level(msg, category=iris.exceptions.IrisCfLoadWarning, stacklevel=2) continue nc_cell_methods_matches.append(nc_cell_method_match) @@ -371,7 +372,7 @@ def parse_cell_methods(nc_cell_methods, cf_name=None): name = "{}".format(cf_name) msg = msg.replace("variable", "variable {!r}".format(name)) else: - warnings.warn( + warn_once_at_level( msg, category=UnknownCellMethodWarning, ) @@ -474,7 +475,7 @@ def build_cube_metadata(engine): cube.attributes.globals[str(attr_name)] = attr_value except ValueError as e: msg = "Skipping disallowed global attribute {!r}: {}" - warnings.warn( + warn_once_at_level( msg.format(attr_name, str(e)), category=_WarnComboIgnoringLoad, ) @@ -549,7 +550,7 @@ def build_rotated_coordinate_system(engine, cf_grid_var): north_pole_latitude = getattr(cf_grid_var, CF_ATTR_GRID_NORTH_POLE_LAT, 90.0) north_pole_longitude = getattr(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( + warn_once_at_level( "Rotated pole position is not fully specified", category=iris.exceptions.IrisCfLoadWarning, ) @@ -899,7 +900,7 @@ def get_attr_units(cf_var, attributes): msg = "Ignoring netCDF variable {!r} invalid units {!r}".format( cf_var.cf_name, attr_units ) - warnings.warn( + warn_once_at_level( msg, category=_WarnComboIgnoringCfLoad, ) @@ -984,7 +985,7 @@ def get_cf_bounds_var(cf_coord_var): climatological = True if attr_bounds is not None and attr_climatology is not None: - warnings.warn( + warn_once_at_level( "Ignoring climatology in favour of bounds attribute " "on NetCDF variable {!r}.".format(cf_coord_var.cf_name), category=_WarnComboIgnoringCfLoad, @@ -1039,7 +1040,7 @@ 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( + warn_once_at_level( msg.format(str(cf_coord_var.cf_name)), category=_WarnComboDefaultingLoad, ) @@ -1052,7 +1053,7 @@ 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( + warn_once_at_level( msg.format(str(cf_coord_var.cf_name)), category=_WarnComboDefaultingLoad, ) @@ -1112,7 +1113,7 @@ def build_dimension_coordinate( "Failed to create {name!r} dimension coordinate: {error}\n" "Gracefully creating {name!r} auxiliary coordinate instead." ) - warnings.warn( + warn_once_at_level( msg.format(name=str(cf_coord_var.cf_name), error=e_msg), category=_WarnComboDefaultingCfLoad, ) @@ -1130,7 +1131,7 @@ def build_dimension_coordinate( try: cube.add_aux_coord(coord, data_dims) except iris.exceptions.CannotAddError as e_msg: - warnings.warn( + warn_once_at_level( coord_skipped_msg.format(error=e_msg), category=iris.exceptions.IrisCannotAddWarning, ) @@ -1144,7 +1145,7 @@ 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( + warn_once_at_level( coord_skipped_msg.format(error=e_msg), category=iris.exceptions.IrisCannotAddWarning, ) @@ -1218,7 +1219,7 @@ 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( + warn_once_at_level( msg.format(name=str(cf_coord_var.cf_name), error=e_msg), category=iris.exceptions.IrisCannotAddWarning, ) @@ -1270,7 +1271,7 @@ 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( + warn_once_at_level( msg.format(name=str(cf_cm_var.cf_name), error=e_msg), category=iris.exceptions.IrisCannotAddWarning, ) @@ -1318,7 +1319,7 @@ 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( + warn_once_at_level( msg.format(name=str(cf_av_var.cf_name), error=e_msg), category=iris.exceptions.IrisCannotAddWarning, ) @@ -1519,7 +1520,7 @@ def has_supported_mercator_parameters(engine, cf_name): ) if scale_factor_at_projection_origin is not None and standard_parallel is not None: - warnings.warn( + warn_once_at_level( "It does not make sense to provide both " '"scale_factor_at_projection_origin" and "standard_parallel".', category=iris.exceptions.IrisCfInvalidCoordParamWarning, @@ -1550,14 +1551,14 @@ def has_supported_polar_stereographic_parameters(engine, cf_name): ) if latitude_of_projection_origin != 90 and latitude_of_projection_origin != -90: - warnings.warn( + warn_once_at_level( '"latitude_of_projection_origin" must be +90 or -90.', category=iris.exceptions.IrisCfInvalidCoordParamWarning, ) is_valid = False if scale_factor_at_projection_origin is not None and standard_parallel is not None: - warnings.warn( + warn_once_at_level( "It does not make sense to provide both " '"scale_factor_at_projection_origin" and "standard_parallel".', category=iris.exceptions.IrisCfInvalidCoordParamWarning, @@ -1565,7 +1566,7 @@ def has_supported_polar_stereographic_parameters(engine, cf_name): is_valid = False if scale_factor_at_projection_origin is None and standard_parallel is None: - warnings.warn( + warn_once_at_level( 'One of "scale_factor_at_projection_origin" and ' '"standard_parallel" is required.', category=iris.exceptions.IrisCfInvalidCoordParamWarning, diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index 5a0230d5eb..1c99181333 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -18,7 +18,7 @@ from collections.abc import Iterable, MutableMapping import os import re -import warnings +from iris.exceptions import warn_once_at_level import numpy as np import numpy.ma as ma @@ -276,7 +276,7 @@ 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( + warn_once_at_level( message % (name, nc_var_name), category=iris.exceptions.IrisCfMissingVarWarning, ) @@ -325,7 +325,7 @@ 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( + warn_once_at_level( message % (name, nc_var_name), category=iris.exceptions.IrisCfMissingVarWarning, ) @@ -375,7 +375,7 @@ 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( + warn_once_at_level( message % (name, nc_var_name), category=iris.exceptions.IrisCfMissingVarWarning, ) @@ -451,7 +451,7 @@ 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( + warn_once_at_level( message % (name, nc_var_name), category=iris.exceptions.IrisCfMissingVarWarning, ) @@ -591,7 +591,7 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if variable_name not in variables: if warn: message = "Missing CF-netCDF formula term variable %r, referenced by netCDF variable %r" - warnings.warn( + warn_once_at_level( message % (variable_name, nc_var_name), category=iris.exceptions.IrisCfMissingVarWarning, ) @@ -658,7 +658,7 @@ 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( + warn_once_at_level( message % (name, nc_var_name), category=iris.exceptions.IrisCfMissingVarWarning, ) @@ -699,7 +699,7 @@ 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( + warn_once_at_level( message % (name, nc_var_name), category=iris.exceptions.IrisCfMissingVarWarning, ) @@ -874,7 +874,7 @@ def identify(cls, variables, ignore=None, target=None, warn=True): if variable_name not in variables: if warn: message = "Missing CF-netCDF measure variable %r, referenced by netCDF variable %r" - warnings.warn( + warn_once_at_level( message % (variable_name, nc_var_name), category=iris.exceptions.IrisCfMissingVarWarning, ) @@ -1080,7 +1080,7 @@ def __init__(self, file_source, warn=False, monotonic=False): "NETCDF3_CLASSIC", "NETCDF3_64BIT", ]: - warnings.warn( + warn_once_at_level( "Optimise CF-netCDF loading by converting data from NetCDF3 " 'to NetCDF4 file format using the "nccopy" command.', category=iris.exceptions.IrisLoadWarning, @@ -1219,7 +1219,7 @@ def _build(cf_variable): cf_variable.dimensions, ) ) - warnings.warn( + warn_once_at_level( msg, category=iris.exceptions.IrisCfNonSpanningVarWarning, ) @@ -1268,7 +1268,7 @@ def _build(cf_variable): cf_variable.dimensions, ) ) - warnings.warn( + warn_once_at_level( msg, category=iris.exceptions.IrisCfNonSpanningVarWarning, ) diff --git a/lib/iris/fileformats/name_loaders.py b/lib/iris/fileformats/name_loaders.py index 3e337383cb..2c11d01138 100644 --- a/lib/iris/fileformats/name_loaders.py +++ b/lib/iris/fileformats/name_loaders.py @@ -8,7 +8,7 @@ import datetime from operator import itemgetter import re -import warnings +from iris.exceptions import warn_once_at_level import cf_units import numpy as np @@ -265,7 +265,7 @@ def _parse_units(units): try: units = cf_units.Unit(units) except ValueError: - warnings.warn("Unknown units: {!r}".format(units), category=IrisLoadWarning) + warn_once_at_level("Unknown units: {!r}".format(units), category=IrisLoadWarning) units = cf_units.Unit(None) return units @@ -595,7 +595,7 @@ 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), category=IrisLoadWarning) + warn_once_at_level(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/loader.py b/lib/iris/fileformats/netcdf/loader.py index 42b6ee509c..b9cc4870d1 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -16,7 +16,7 @@ from enum import Enum, auto import threading from typing import Union -import warnings +from iris.exceptions import warn_once_at_level import numpy as np @@ -413,7 +413,7 @@ def coord_from_term(term): for coord, cf_var_name in engine.cube_parts["coordinates"]: if cf_var_name == name: return coord - warnings.warn( + warn_once_at_level( "Unable to find coordinate for variable {!r}".format(name), category=iris.exceptions.IrisFactoryCoordNotFoundWarning, ) @@ -644,7 +644,7 @@ def load_cubes(file_sources, callback=None, constraints=None): try: _load_aux_factory(engine, cube) except ValueError as e: - warnings.warn( + warn_once_at_level( "{}".format(e), category=iris.exceptions.IrisLoadWarning, ) diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 086732e597..c2f043ebac 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -20,7 +20,7 @@ import re import string from typing import List -import warnings +from iris.exceptions import warn_once_at_level import cf_units import dask @@ -373,7 +373,7 @@ def _fillvalue_report(fill_info, is_masked, contains_fill_value, warn=False): ) if warn and result is not None: - warnings.warn( + warn_once_at_level( result, category=_WarnComboMaskSave, ) @@ -737,7 +737,7 @@ def write( cf_patch(profile, self._dataset, cf_var_cube) else: msg = "cf_profile is available but no {} defined.".format("cf_patch") - warnings.warn(msg, category=iris.exceptions.IrisCfSaveWarning) + warn_once_at_level(msg, category=iris.exceptions.IrisCfSaveWarning) @staticmethod def check_attribute_compliance(container, data_dtype): @@ -1133,7 +1133,7 @@ def _add_aux_factories(self, cube, cf_var_cube, dimension_names): msg = "Unable to determine formula terms for AuxFactory: {!r}".format( factory ) - warnings.warn(msg, category=iris.exceptions.IrisSaveWarning) + warn_once_at_level(msg, category=iris.exceptions.IrisSaveWarning) else: # Override `standard_name`, `long_name`, and `axis` of the # primary coord that signals the presence of a dimensionless @@ -2082,7 +2082,7 @@ def add_ellipsoid(ellipsoid): # osgb (a specific tmerc) elif isinstance(cs, iris.coord_systems.OSGB): - warnings.warn( + warn_once_at_level( "OSGB coordinate system not yet handled", category=iris.exceptions.IrisSaveWarning, ) @@ -2168,7 +2168,7 @@ def add_ellipsoid(ellipsoid): # other else: - warnings.warn( + warn_once_at_level( "Unable to represent the horizontal " "coordinate system. The coordinate system " "type %r is not yet implemented." % type(cs), @@ -2342,7 +2342,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, category=iris.exceptions.IrisCfSaveWarning) + warn_once_at_level(msg, category=iris.exceptions.IrisCfSaveWarning) _setncattr(cf_var, attr_name, value) @@ -2565,7 +2565,7 @@ 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, category=iris.exceptions.IrisSaveWarning) + warn_once_at_level(delayed_warning, category=iris.exceptions.IrisSaveWarning) return result_warnings @@ -2973,7 +2973,7 @@ def is_valid_packspec(p): msg = "cf_profile is available but no {} defined.".format( "cf_patch_conventions" ) - warnings.warn(msg, category=iris.exceptions.IrisCfSaveWarning) + warn_once_at_level(msg, category=iris.exceptions.IrisCfSaveWarning) # Add conventions attribute. if iris.FUTURE.save_split_attrs: diff --git a/lib/iris/fileformats/nimrod_load_rules.py b/lib/iris/fileformats/nimrod_load_rules.py index 5ca9ef4be3..0df1404d8c 100644 --- a/lib/iris/fileformats/nimrod_load_rules.py +++ b/lib/iris/fileformats/nimrod_load_rules.py @@ -6,7 +6,7 @@ import re import string -import warnings +from iris.exceptions import warn_once_at_level import cf_units import cftime @@ -179,7 +179,7 @@ def units(cube, field): cube.units = field_units except ValueError: # Just add it as an attribute. - warnings.warn( + warn_once_at_level( "Unhandled units '{0}' recorded in cube attributes.".format(field_units), category=IrisNimrodTranslationWarning, ) @@ -398,7 +398,7 @@ def coord_system(field, handle_metadata_errors): field.tm_meridian_scaling, ) if any([is_missing(field, v) for v in crs_args]): - warnings.warn( + warn_once_at_level( "Coordinate Reference System is not completely defined. " "Plotting and reprojection may be impaired.", category=IrisNimrodTranslationWarning, @@ -524,7 +524,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, category=IrisNimrodTranslationWarning) + warn_once_at_level(msg, category=IrisNimrodTranslationWarning) coord_point = field.vertical_coord if coord_point == 8888.0: @@ -564,7 +564,7 @@ def vertical_coord(cube, field): cube.add_aux_coord(new_coord) return - warnings.warn( + warn_once_at_level( "Vertical coord {!r} not yet handled".format(field.vertical_coord_type), category=TranslationWarning, ) @@ -803,7 +803,7 @@ def probability_coord(cube, field, handle_metadata_errors): "standard_name", coord_keys.get("long_name", coord_keys.get("var_name", None)), ) - warnings.warn( + warn_once_at_level( f"No default units for {coord_name} coord of {cube.name()}. " "Meta-data may be incomplete.", category=IrisNimrodTranslationWarning, diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index 2780c52625..33140dc382 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -11,7 +11,7 @@ import os import re import struct -import warnings +from iris.exceptions import warn_once_at_level import cf_units import cftime @@ -1154,7 +1154,7 @@ 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( + warn_once_at_level( msg.format(mdi), category=_WarnComboLoadingMask, ) @@ -1271,7 +1271,7 @@ def save(self, file_handle): if data.dtype == np.dtype(">f4"): lb[self.HEADER_DICT["lbuser"][0]] = 1 elif data.dtype == np.dtype(">f8"): - warnings.warn( + warn_once_at_level( "Downcasting array precision from float64 to float32" " for save.If float64 precision is required then" " please save in a different format", @@ -1694,7 +1694,7 @@ def _interpret_fields(fields): # they were encountered before the landmask reference field. if landmask_compressed_fields: if land_mask_field is None: - warnings.warn( + warn_once_at_level( "Landmask compressed fields existed without a " "landmask to decompress with. The data will have " "a shape of (0, 0) and will not read.", @@ -1862,7 +1862,7 @@ 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( + warn_once_at_level( msg, category=_WarnComboIgnoringLoad, ) @@ -1882,7 +1882,7 @@ def _field_gen(filename, read_data_bytes, little_ended=False): "after the header in the file ({} and {}). " "Skipping the remainder of the file." ) - warnings.warn( + warn_once_at_level( wmsg.format( pp_field.lblrec * PP_WORD_DEPTH, len_of_data_plus_extra ), diff --git a/lib/iris/fileformats/pp_save_rules.py b/lib/iris/fileformats/pp_save_rules.py index 20ed0bd618..501be5e5b4 100644 --- a/lib/iris/fileformats/pp_save_rules.py +++ b/lib/iris/fileformats/pp_save_rules.py @@ -5,7 +5,7 @@ """PP Save Rules.""" -import warnings +from iris.exceptions import warn_once_at_level import cftime @@ -904,4 +904,4 @@ def verify(cube, field): def _conditional_warning(condition, warning): if condition: - warnings.warn(warning, category=IrisPpClimModifiedWarning) + warn_once_at_level(warning, category=IrisPpClimModifiedWarning) diff --git a/lib/iris/fileformats/rules.py b/lib/iris/fileformats/rules.py index aa3d4696bc..6b835baaa0 100644 --- a/lib/iris/fileformats/rules.py +++ b/lib/iris/fileformats/rules.py @@ -5,7 +5,7 @@ """Generalised mechanisms for metadata translation and cube construction.""" import collections -import warnings +from iris.exceptions import warn_once_at_level import cf_units @@ -40,7 +40,7 @@ def as_cube(self): # time-varying surface pressure in hybrid-presure. src_cubes = src_cubes.merge(unique=False) if len(src_cubes) > 1: - warnings.warn( + warn_once_at_level( "Multiple reference cubes for {}".format(self.name), category=iris.exceptions.IrisUserWarning, ) @@ -313,7 +313,7 @@ def _make_cube(field, converter): cube.units = metadata.units except ValueError: msg = "Ignoring PP invalid units {!r}".format(metadata.units) - warnings.warn(msg, category=iris.exceptions.IrisIgnoringWarning) + warn_once_at_level(msg, category=iris.exceptions.IrisIgnoringWarning) cube.attributes["invalid_units"] = metadata.units cube.units = cf_units._UNKNOWN_UNIT_STRING @@ -334,7 +334,7 @@ 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( + warn_once_at_level( msg.format(factory=factory_name), category=iris.exceptions.IrisUserWarning, ) diff --git a/lib/iris/iterate.py b/lib/iris/iterate.py index be2a436a5e..8982abcac3 100644 --- a/lib/iris/iterate.py +++ b/lib/iris/iterate.py @@ -6,7 +6,7 @@ from collections.abc import Iterator import itertools -import warnings +from iris.exceptions import warn_once_at_level import numpy as np @@ -155,7 +155,7 @@ def izip(*cubes, **kwargs): "step." % coord_a.name() ) if coord_a != coord_b: - warnings.warn( + warn_once_at_level( "Iterating over coordinate '%s' in step whose " "definitions match but whose values " "differ." % coord_a.name(), diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index d60b46011e..8f0d631b13 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -9,7 +9,7 @@ """ import datetime from itertools import chain, combinations -import warnings +from iris.exceptions import warn_once_at_level import cf_units from cf_units import Unit @@ -430,7 +430,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, category=IrisIgnoringWarning) + warn_once_at_level(message, category=IrisIgnoringWarning) class_arg_mapping = [] non_data_names = [] @@ -870,7 +870,7 @@ def merge_metadata(meta_var_list): "'iris.FUTURE.pandas_ndim = True'. More info is in the " "documentation." ) - warnings.warn(message, category=FutureWarning) + warn_once_at_level(message, category=FutureWarning) # The legacy behaviour. data = cube.data diff --git a/lib/iris/plot.py b/lib/iris/plot.py index c727607449..6a6c1bd2dc 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -10,7 +10,7 @@ import collections import datetime -import warnings +from iris.exceptions import warn_once_at_level import cartopy.crs as ccrs from cartopy.geodesic import Geodesic @@ -1913,13 +1913,13 @@ def update_animation_iris(i, cubes, vmin, vmax, coords): if plot_func.__module__ not in supported: msg = 'Given plotting module "{}" may not be supported, intended ' "use: {}." msg = msg.format(plot_func.__module__, supported) - warnings.warn(msg, category=IrisUnsupportedPlottingWarning) + warn_once_at_level(msg, category=IrisUnsupportedPlottingWarning) supported = ["contour", "contourf", "pcolor", "pcolormesh"] if plot_func.__name__ not in supported: msg = 'Given plotting function "{}" may not be supported, intended ' "use: {}." msg = msg.format(plot_func.__name__, supported) - warnings.warn(msg, category=IrisUnsupportedPlottingWarning) + warn_once_at_level(msg, category=IrisUnsupportedPlottingWarning) # Determine plot range. vmin = kwargs.pop("vmin", min([cc.data.min() for cc in cubes])) From 730eb38579029007c87193b97858ac41886f61ef Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Sun, 11 Feb 2024 22:22:13 +0000 Subject: [PATCH 3/8] fix warnings --- lib/iris/exceptions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index 9b0e84bc45..ad7e6b06cc 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -350,16 +350,16 @@ class IrisSaverFillValueWarning(IrisMaskValueMatchWarning, IrisSaveWarning): @lru_cache(None) -def warn_once(msg, type, stacklevel, frame): +def warn_once(msg, type, stacklevel, frame, **kwargs): """Raise a warning only if a similar one has not been raised before.""" - warnings.warn(msg, type, stacklevel=stacklevel) + warnings.warn(msg, type, stacklevel=stacklevel, **kwargs) -def warn_once_at_level(msg, type=None, stacklevel=0): +def warn_once_at_level(msg, type=None, stacklevel=0, **kwargs): """ Raise a warning only if a similar one hasn't been raised from the same line for a given stack level. """ stacklevel += 1 frame = traceback.format_stack()[-stacklevel] - warn_once(msg, type, stacklevel + 1, frame) + warn_once(msg, type, stacklevel + 1, frame, **kwargs) From a503041070b79af991d7cc83cf10928dc03929e7 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Sun, 11 Feb 2024 22:56:14 +0000 Subject: [PATCH 4/8] fix warnings --- lib/iris/exceptions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index ad7e6b06cc..3686f0f814 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -350,16 +350,16 @@ class IrisSaverFillValueWarning(IrisMaskValueMatchWarning, IrisSaveWarning): @lru_cache(None) -def warn_once(msg, type, stacklevel, frame, **kwargs): +def warn_once(msg, stacklevel, frame, **kwargs): """Raise a warning only if a similar one has not been raised before.""" - warnings.warn(msg, type, stacklevel=stacklevel, **kwargs) + warnings.warn(msg, stacklevel=stacklevel, **kwargs) -def warn_once_at_level(msg, type=None, stacklevel=0, **kwargs): +def warn_once_at_level(msg, stacklevel=0, **kwargs): """ Raise a warning only if a similar one hasn't been raised from the same line for a given stack level. """ stacklevel += 1 frame = traceback.format_stack()[-stacklevel] - warn_once(msg, type, stacklevel + 1, frame, **kwargs) + warn_once(msg, stacklevel + 1, frame, **kwargs) From f501454281b6a4c9f0fd01af4bde2b44d6c4ddd4 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Mon, 12 Feb 2024 08:46:15 +0000 Subject: [PATCH 5/8] limit cache size, fix tests --- lib/iris/exceptions.py | 2 +- lib/iris/fileformats/_nc_load_rules/helpers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index 3686f0f814..de1a5cdabb 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -349,7 +349,7 @@ class IrisSaverFillValueWarning(IrisMaskValueMatchWarning, IrisSaveWarning): pass -@lru_cache(None) +@lru_cache(128) def warn_once(msg, stacklevel, frame, **kwargs): """Raise a warning only if a similar one has not been raised before.""" warnings.warn(msg, stacklevel=stacklevel, **kwargs) diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 01ee90593c..51c2f41f78 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -521,7 +521,7 @@ def _get_ellipsoid(cf_grid_var): "applied. To apply the datum when loading, use the " "iris.FUTURE.datum_support flag." ) - iris.exceptions.warn_once_at_level(wmsg, FutureWarning, category=FutureWarning, stacklevel=14) + iris.exceptions.warn_once_at_level(wmsg, category=FutureWarning, stacklevel=14) datum = None if datum is not None: From baca57fe37d7493fc9eb7783820a43d7a8e2f4de Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 08:48:37 +0000 Subject: [PATCH 6/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- lib/iris/_concatenate.py | 2 +- lib/iris/analysis/_regrid.py | 3 +- lib/iris/analysis/calculus.py | 3 +- lib/iris/analysis/cartography.py | 2 +- lib/iris/analysis/geometry.py | 3 +- lib/iris/analysis/maths.py | 2 +- lib/iris/aux_factory.py | 51 ++++++++++++++----- lib/iris/config.py | 2 +- lib/iris/coord_systems.py | 2 +- lib/iris/coords.py | 2 +- lib/iris/cube.py | 2 +- lib/iris/exceptions.py | 3 +- lib/iris/experimental/regrid.py | 3 +- lib/iris/experimental/ugrid/cf.py | 12 +++-- lib/iris/experimental/ugrid/load.py | 1 + lib/iris/fileformats/_ff.py | 6 ++- .../fileformats/_nc_load_rules/actions.py | 2 +- .../fileformats/_nc_load_rules/helpers.py | 4 +- lib/iris/fileformats/cf.py | 2 +- lib/iris/fileformats/name_loaders.py | 7 +-- lib/iris/fileformats/netcdf/loader.py | 2 +- lib/iris/fileformats/netcdf/saver.py | 6 ++- lib/iris/fileformats/nimrod_load_rules.py | 2 +- lib/iris/fileformats/pp.py | 2 +- lib/iris/fileformats/pp_save_rules.py | 4 +- lib/iris/fileformats/rules.py | 2 +- lib/iris/iterate.py | 3 +- lib/iris/pandas.py | 3 +- lib/iris/plot.py | 7 ++- 29 files changed, 89 insertions(+), 56 deletions(-) diff --git a/lib/iris/_concatenate.py b/lib/iris/_concatenate.py index 7330928fe3..0053a555f7 100644 --- a/lib/iris/_concatenate.py +++ b/lib/iris/_concatenate.py @@ -5,7 +5,6 @@ """Automatic concatenation of multiple cubes over one or more existing dimensions.""" from collections import defaultdict, namedtuple -from iris.exceptions import warn_once_at_level import dask.array as da import numpy as np @@ -13,6 +12,7 @@ import iris.coords import iris.cube import iris.exceptions +from iris.exceptions import warn_once_at_level from iris.util import array_equal, guess_coord_axis # diff --git a/lib/iris/analysis/_regrid.py b/lib/iris/analysis/_regrid.py index 637e380500..982a2de0ff 100644 --- a/lib/iris/analysis/_regrid.py +++ b/lib/iris/analysis/_regrid.py @@ -5,7 +5,6 @@ import copy import functools -from iris.exceptions import warn_once_at_level import numpy as np import numpy.ma as ma @@ -19,7 +18,7 @@ snapshot_grid, ) from iris.analysis._scipy_interpolate import _RegularGridInterpolator -from iris.exceptions import IrisImpossibleUpdateWarning +from iris.exceptions import IrisImpossibleUpdateWarning, warn_once_at_level from iris.util import _meshgrid, guess_coord_axis diff --git a/lib/iris/analysis/calculus.py b/lib/iris/analysis/calculus.py index 4e04737c3a..992b776d56 100644 --- a/lib/iris/analysis/calculus.py +++ b/lib/iris/analysis/calculus.py @@ -9,7 +9,6 @@ """ import re -from iris.exceptions import warn_once_at_level import cf_units import numpy as np @@ -22,7 +21,7 @@ import iris.analysis.maths import iris.coord_systems import iris.coords -from iris.exceptions import IrisUserWarning +from iris.exceptions import IrisUserWarning, warn_once_at_level from iris.util import delta __all__ = ["DIRECTIONAL_NAMES", "cube_delta", "curl", "differentiate"] diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index 3f20c0b891..b69d1e57d1 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -6,7 +6,6 @@ from collections import namedtuple import copy -from iris.exceptions import warn_once_at_level import cartopy.crs as ccrs import cartopy.img_transform @@ -18,6 +17,7 @@ import iris.coord_systems import iris.coords import iris.exceptions +from iris.exceptions import warn_once_at_level from iris.util import _meshgrid from ._grid_angles import gridcell_angles, rotate_grid_vectors diff --git a/lib/iris/analysis/geometry.py b/lib/iris/analysis/geometry.py index be446c3b3d..2be351ea50 100644 --- a/lib/iris/analysis/geometry.py +++ b/lib/iris/analysis/geometry.py @@ -9,12 +9,11 @@ """ -from iris.exceptions import warn_once_at_level - import numpy as np from shapely.geometry import Polygon import iris.exceptions +from iris.exceptions import warn_once_at_level def _extract_relevant_cube_slice(cube, geometry): diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index a6999c5c3d..2cb6ac8980 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -8,7 +8,6 @@ import inspect import math import operator -from iris.exceptions import warn_once_at_level import cf_units import dask.array as da @@ -22,6 +21,7 @@ from iris.config import get_logger import iris.coords import iris.exceptions +from iris.exceptions import warn_once_at_level import iris.util # Configure the logger. diff --git a/lib/iris/aux_factory.py b/lib/iris/aux_factory.py index f81fe9a52d..e0a5e51a1d 100644 --- a/lib/iris/aux_factory.py +++ b/lib/iris/aux_factory.py @@ -5,7 +5,6 @@ """Definitions of derived coordinates.""" from abc import ABCMeta, abstractmethod -from iris.exceptions import warn_once_at_level import cf_units import dask.array as da @@ -13,7 +12,7 @@ from iris.common import CFVariableMixin, CoordMetadata, metadata_manager_factory import iris.coords -from iris.exceptions import IrisIgnoringBoundsWarning +from iris.exceptions import IrisIgnoringBoundsWarning, warn_once_at_level class AuxCoordFactory(CFVariableMixin, metaclass=ABCMeta): @@ -429,7 +428,9 @@ def _check_dependencies(pressure_at_top, sigma, surface_air_pressure): f"Coordinate '{coord.name()}' has bounds. These will " "be disregarded" ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Check units if sigma.units.is_unknown(): @@ -704,7 +705,9 @@ def update(self, old_coord, new_coord=None): "Orography coordinate {!r} has bounds." " These will be disregarded.".format(new_coord.name()) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) self.orography = new_coord @@ -968,7 +971,9 @@ def _check_dependencies(sigma, eta, depth, depth_c, nsigma, zlev): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) for coord, term in ((depth_c, "depth_c"), (nsigma, "nsigma")): if coord is not None and coord.shape != (1,): @@ -1125,7 +1130,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + 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) @@ -1205,7 +1212,9 @@ def _check_dependencies(sigma, eta, depth): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) # Check units. if sigma is not None and sigma.units.is_unknown(): @@ -1281,7 +1290,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + 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) @@ -1372,7 +1383,9 @@ def _check_dependencies(s, c, eta, depth, depth_c): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) if depth_c is not None and depth_c.shape != (1,): msg = "Expected scalar {} coordinate {!r}: got shape {!r}.".format( @@ -1466,7 +1479,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + 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) @@ -1561,7 +1576,9 @@ def _check_dependencies(s, eta, depth, a, b, depth_c): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) coords = ((a, "a"), (b, "b"), (depth_c, "depth_c")) for coord, term in coords: @@ -1658,7 +1675,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + 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) @@ -1753,7 +1772,9 @@ def _check_dependencies(s, c, eta, depth, depth_c): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(term, coord.name()) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + msg, category=IrisIgnoringBoundsWarning, stacklevel=2 + ) if depth_c is not None and depth_c.shape != (1,): msg = "Expected scalar depth_c coordinate {!r}: got shape {!r}.".format( @@ -1847,7 +1868,9 @@ def make_coord(self, coord_dims_func): "The {} coordinate {!r} has bounds. " "These are being disregarded.".format(key, name) ) - warn_once_at_level(msg, category=IrisIgnoringBoundsWarning, stacklevel=2) + warn_once_at_level( + 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 e5822f3002..fd85d70ebb 100644 --- a/lib/iris/config.py +++ b/lib/iris/config.py @@ -29,9 +29,9 @@ import contextlib import logging import os.path -from iris.exceptions import warn_once_at_level import iris.exceptions +from iris.exceptions import warn_once_at_level def get_logger(name, datefmt=None, fmt=None, level=None, propagate=None, handler=True): diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 91610d3377..6014e12b53 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -7,13 +7,13 @@ from abc import ABCMeta, abstractmethod from functools import cached_property import re -from iris.exceptions import warn_once_at_level import cartopy.crs as ccrs import numpy as np from iris._deprecation import warn_deprecated import iris.exceptions +from iris.exceptions import warn_once_at_level def _arg_default(value, default, cast_as=float): diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 3f61e67e93..fe426932e2 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -11,7 +11,6 @@ from functools import lru_cache from itertools import zip_longest import operator -from iris.exceptions import warn_once_at_level import zlib import dask.array as da @@ -30,6 +29,7 @@ metadata_manager_factory, ) import iris.exceptions +from iris.exceptions import warn_once_at_level import iris.time import iris.util diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 8ac0bff2f0..04173b8375 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -20,7 +20,6 @@ Optional, Union, ) -from iris.exceptions import warn_once_at_level from xml.dom.minidom import Document import zlib @@ -44,6 +43,7 @@ import iris.coord_systems import iris.coords import iris.exceptions +from iris.exceptions import warn_once_at_level import iris.util __all__ = ["Cube", "CubeAttrsDict", "CubeList"] diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index de1a5cdabb..6a96d04384 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -356,8 +356,7 @@ def warn_once(msg, stacklevel, frame, **kwargs): def warn_once_at_level(msg, stacklevel=0, **kwargs): - """ - Raise a warning only if a similar one hasn't been raised from the same line + """Raise a warning only if a similar one hasn't been raised from the same line for a given stack level. """ stacklevel += 1 diff --git a/lib/iris/experimental/regrid.py b/lib/iris/experimental/regrid.py index 4f56a1b6e8..5a523f5349 100644 --- a/lib/iris/experimental/regrid.py +++ b/lib/iris/experimental/regrid.py @@ -18,7 +18,6 @@ """ import copy import functools -from iris.exceptions import warn_once_at_level import cartopy.crs as ccrs import numpy as np @@ -37,7 +36,7 @@ import iris.analysis.cartography import iris.coord_systems import iris.cube -from iris.exceptions import IrisImpossibleUpdateWarning +from iris.exceptions import IrisImpossibleUpdateWarning, warn_once_at_level from iris.util import _meshgrid wmsg = ( diff --git a/lib/iris/experimental/ugrid/cf.py b/lib/iris/experimental/ugrid/cf.py index b958b9afcd..688791abab 100644 --- a/lib/iris/experimental/ugrid/cf.py +++ b/lib/iris/experimental/ugrid/cf.py @@ -65,7 +65,9 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"{nc_var_name}" ) if warn: - warn_once_at_level(message, category=IrisCfMissingVarWarning) + warn_once_at_level( + message, category=IrisCfMissingVarWarning + ) else: # Restrict to non-string type i.e. not a # CFLabelVariable. @@ -217,7 +219,9 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"referenced by netCDF variable {nc_var_name}" ) if warn: - warn_once_at_level(message, category=IrisCfMissingVarWarning) + warn_once_at_level( + message, category=IrisCfMissingVarWarning + ) else: # Restrict to non-string type i.e. not a # CFLabelVariable. @@ -230,7 +234,9 @@ def identify(cls, variables, ignore=None, target=None, warn=True): f"variable." ) if warn: - warn_once_at_level(message, category=IrisCfLabelVarWarning) + warn_once_at_level( + message, category=IrisCfLabelVarWarning + ) return result diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index 7df8018a67..acd4d24a58 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -15,6 +15,7 @@ from itertools import groupby from pathlib import Path import threading + from iris.exceptions import warn_once_at_level from ...config import get_logger diff --git a/lib/iris/fileformats/_ff.py b/lib/iris/fileformats/_ff.py index 4e9721a585..0e4c3e3d33 100644 --- a/lib/iris/fileformats/_ff.py +++ b/lib/iris/fileformats/_ff.py @@ -5,7 +5,6 @@ """Provides UK Met Office Fields File (FF) format specific capabilities.""" import os -from iris.exceptions import warn_once_at_level import numpy as np @@ -13,6 +12,7 @@ IrisDefaultingWarning, IrisLoadWarning, NotYetImplementedError, + warn_once_at_level, ) from iris.fileformats._ff_cross_references import STASH_TRANS @@ -799,7 +799,9 @@ def _extract_field(self): "Input field skipped as PPField creation failed :" " error = {!r}" ) - warn_once_at_level(msg.format(str(valerr)), category=IrisLoadWarning) + warn_once_at_level( + 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 a9b5cdd2f0..ccfa0c6668 100644 --- a/lib/iris/fileformats/_nc_load_rules/actions.py +++ b/lib/iris/fileformats/_nc_load_rules/actions.py @@ -40,10 +40,10 @@ """ from functools import wraps -from iris.exceptions import warn_once_at_level from iris.config import get_logger import iris.exceptions +from iris.exceptions import warn_once_at_level import iris.fileformats.cf import iris.fileformats.pp as pp diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 51c2f41f78..dcd16e1e6a 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -321,7 +321,9 @@ def _split_cell_methods(nc_cell_methods: str) -> List[re.Match]: nc_cell_method_match = _CM_PARSE.match(nc_cell_method_str.strip()) if not nc_cell_method_match: msg = f"Failed to fully parse cell method string: {nc_cell_methods}" - warn_once_at_level(msg, category=iris.exceptions.IrisCfLoadWarning, stacklevel=2) + warn_once_at_level( + msg, category=iris.exceptions.IrisCfLoadWarning, stacklevel=2 + ) continue nc_cell_methods_matches.append(nc_cell_method_match) diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index 1c99181333..ceabac62f8 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -18,12 +18,12 @@ from collections.abc import Iterable, MutableMapping import os import re -from iris.exceptions import warn_once_at_level import numpy as np import numpy.ma as ma import iris.exceptions +from iris.exceptions import warn_once_at_level from iris.fileformats.netcdf import _thread_safe_nc import iris.util diff --git a/lib/iris/fileformats/name_loaders.py b/lib/iris/fileformats/name_loaders.py index 2c11d01138..f6d1f414c3 100644 --- a/lib/iris/fileformats/name_loaders.py +++ b/lib/iris/fileformats/name_loaders.py @@ -8,7 +8,6 @@ import datetime from operator import itemgetter import re -from iris.exceptions import warn_once_at_level import cf_units import numpy as np @@ -16,7 +15,7 @@ import iris.coord_systems from iris.coords import AuxCoord, CellMethod, DimCoord import iris.cube -from iris.exceptions import IrisLoadWarning, TranslationError +from iris.exceptions import IrisLoadWarning, TranslationError, warn_once_at_level import iris.util EARTH_RADIUS = 6371229.0 @@ -265,7 +264,9 @@ def _parse_units(units): try: units = cf_units.Unit(units) except ValueError: - warn_once_at_level("Unknown units: {!r}".format(units), category=IrisLoadWarning) + warn_once_at_level( + "Unknown units: {!r}".format(units), category=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 b9cc4870d1..24c240eaa0 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -16,7 +16,6 @@ from enum import Enum, auto import threading from typing import Union -from iris.exceptions import warn_once_at_level import numpy as np @@ -35,6 +34,7 @@ import iris.coord_systems import iris.coords import iris.exceptions +from iris.exceptions import warn_once_at_level import iris.fileformats.cf from iris.fileformats.netcdf import _thread_safe_nc from iris.fileformats.netcdf.saver import _CF_ATTRS diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index c2f043ebac..77febe7440 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -20,7 +20,6 @@ import re import string from typing import List -from iris.exceptions import warn_once_at_level import cf_units import dask @@ -45,6 +44,7 @@ import iris.coords from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord import iris.exceptions +from iris.exceptions import warn_once_at_level import iris.fileformats.cf from iris.fileformats.netcdf import _dask_locks, _thread_safe_nc import iris.io @@ -2565,7 +2565,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: - warn_once_at_level(delayed_warning, category=iris.exceptions.IrisSaveWarning) + warn_once_at_level( + delayed_warning, category=iris.exceptions.IrisSaveWarning + ) return result_warnings diff --git a/lib/iris/fileformats/nimrod_load_rules.py b/lib/iris/fileformats/nimrod_load_rules.py index 0df1404d8c..521dbf2400 100644 --- a/lib/iris/fileformats/nimrod_load_rules.py +++ b/lib/iris/fileformats/nimrod_load_rules.py @@ -6,7 +6,6 @@ import re import string -from iris.exceptions import warn_once_at_level import cf_units import cftime @@ -19,6 +18,7 @@ CoordinateNotFoundError, IrisNimrodTranslationWarning, TranslationError, + warn_once_at_level, ) __all__ = ["run"] diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index 33140dc382..868ea09ef4 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -11,7 +11,6 @@ import os import re import struct -from iris.exceptions import warn_once_at_level import cf_units import cftime @@ -24,6 +23,7 @@ import iris.config import iris.coord_systems import iris.exceptions +from iris.exceptions import warn_once_at_level # NOTE: this is for backwards-compatitibility *ONLY* # We could simply remove it for v2.0 ? diff --git a/lib/iris/fileformats/pp_save_rules.py b/lib/iris/fileformats/pp_save_rules.py index 501be5e5b4..04aedd18bb 100644 --- a/lib/iris/fileformats/pp_save_rules.py +++ b/lib/iris/fileformats/pp_save_rules.py @@ -5,13 +5,11 @@ """PP Save Rules.""" -from iris.exceptions import warn_once_at_level - import cftime import iris from iris.aux_factory import HybridHeightFactory, HybridPressureFactory -from iris.exceptions import IrisPpClimModifiedWarning +from iris.exceptions import IrisPpClimModifiedWarning, warn_once_at_level from iris.fileformats._ff_cross_references import STASH_TRANS from iris.fileformats._pp_lbproc_pairs import LBPROC_MAP from iris.fileformats.rules import ( diff --git a/lib/iris/fileformats/rules.py b/lib/iris/fileformats/rules.py index 6b835baaa0..d167fb2804 100644 --- a/lib/iris/fileformats/rules.py +++ b/lib/iris/fileformats/rules.py @@ -5,13 +5,13 @@ """Generalised mechanisms for metadata translation and cube construction.""" import collections -from iris.exceptions import warn_once_at_level import cf_units from iris.analysis import Linear import iris.cube import iris.exceptions +from iris.exceptions import warn_once_at_level import iris.fileformats.um_cf_map Factory = collections.namedtuple("Factory", ["factory_class", "args"]) diff --git a/lib/iris/iterate.py b/lib/iris/iterate.py index 8982abcac3..f726fac74a 100644 --- a/lib/iris/iterate.py +++ b/lib/iris/iterate.py @@ -6,11 +6,10 @@ from collections.abc import Iterator import itertools -from iris.exceptions import warn_once_at_level import numpy as np -from iris.exceptions import IrisUserWarning +from iris.exceptions import IrisUserWarning, warn_once_at_level __all__ = ["izip"] diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index 8f0d631b13..b6ad373aa1 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -9,7 +9,6 @@ """ import datetime from itertools import chain, combinations -from iris.exceptions import warn_once_at_level import cf_units from cf_units import Unit @@ -18,6 +17,8 @@ import numpy.ma as ma import pandas as pd +from iris.exceptions import warn_once_at_level + try: from pandas.core.indexes.datetimes import DatetimeIndex # pandas >=0.20 except ImportError: diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 6a6c1bd2dc..811bf5c099 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -10,7 +10,6 @@ import collections import datetime -from iris.exceptions import warn_once_at_level import cartopy.crs as ccrs from cartopy.geodesic import Geodesic @@ -30,7 +29,11 @@ import iris.coord_systems import iris.coords import iris.cube -from iris.exceptions import IrisError, IrisUnsupportedPlottingWarning +from iris.exceptions import ( + IrisError, + IrisUnsupportedPlottingWarning, + warn_once_at_level, +) # Importing iris.palette to register the brewer palettes. import iris.palette From 5852654d28dda1b5697772d2ed2965062ebf6610 Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Mon, 12 Feb 2024 12:03:17 +0000 Subject: [PATCH 7/8] fix tests --- lib/iris/fileformats/_nc_load_rules/helpers.py | 3 +-- lib/iris/fileformats/netcdf/loader.py | 2 +- lib/iris/fileformats/netcdf/saver.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index dcd16e1e6a..31f76512c6 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -15,7 +15,6 @@ """ import re from typing import List -import warnings import cf_units import numpy as np @@ -438,7 +437,7 @@ def parse_cell_methods(nc_cell_methods, cf_name=None): cell_methods.append(cell_method) # only prints one warning, rather than each loop if msg: - warnings.warn(msg, category=UnknownCellMethodWarning) + warn_once_at_level(msg, category=UnknownCellMethodWarning) return tuple(cell_methods) diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py index 24c240eaa0..7142ea9cdd 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -454,7 +454,7 @@ def coord_from_term(term): "Ignoring atmosphere hybrid sigma pressure " "scalar coordinate {!r} bounds.".format(coord_p0.name()) ) - warnings.warn( + warn_once_at_level( msg, category=_WarnComboIgnoringBoundsLoad, ) diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 77febe7440..5c0130a260 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -2829,7 +2829,7 @@ def attr_values_equal(val1, val2): # Catch any demoted attrs where there is already a local version blocked_attrs = demote_attrs & set(cube.attributes.locals) if blocked_attrs: - warnings.warn( + warn_once_at_level( f"Global cube attributes {sorted(blocked_attrs)} " f'of cube "{cube.name()}" were not saved, overlaid ' "by existing local attributes with the same names.", From 7e8871e3346fabea2f479ff8bbc0fdcdf3aad14e Mon Sep 17 00:00:00 2001 From: "stephen.worsley" Date: Mon, 12 Feb 2024 12:23:47 +0000 Subject: [PATCH 8/8] fix tests --- lib/iris/fileformats/netcdf/saver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 5c0130a260..366c17c2b7 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -2813,7 +2813,7 @@ def attr_values_equal(val1, val2): } if invalid_globals: # Some cubes have different global attributes: modify cubes as required. - warnings.warn( + warn_once_at_level( f"Saving the cube global attributes {sorted(invalid_globals)} as local " "(i.e. data-variable) attributes, where possible, since they are not " "the same on all input cubes.",