From a20a0441397ce19f5209078b3b5bfb782b39cb87 Mon Sep 17 00:00:00 2001 From: Gavin Evans Date: Fri, 26 Sep 2025 09:06:25 +0100 Subject: [PATCH 1/2] Edits to ensure that plugins return a CubeList, rather than a tuple, if multiple cubes are returned. --- improver/cli/__init__.py | 8 +++++++- .../cloud_condensation_level.py | 12 +++++++----- improver/utilities/spatial.py | 6 +++--- .../test_CloudCondensationLevel.py | 3 ++- .../test_GradientBetweenAdjacentGridSquares.py | 5 ++++- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/improver/cli/__init__.py b/improver/cli/__init__.py index 54ab67a4ff..72eefe55ff 100644 --- a/improver/cli/__init__.py +++ b/improver/cli/__init__.py @@ -353,7 +353,13 @@ def with_output( result = wrapped(*args, **kwargs) - if output and (isinstance(result, Cube) or isinstance(result, CubeList)): + # If result is a Cube or CubeList or an iterable containing only Cubes, + # save as netCDF + if ( + output + and (isinstance(result, (Cube, CubeList))) + or all([isinstance(x, Cube) for x in result]) + ): save_netcdf(result, output, compression_level, least_significant_digit) if pass_through_output: return ObjectAsStr(result, output) diff --git a/improver/psychrometric_calculations/cloud_condensation_level.py b/improver/psychrometric_calculations/cloud_condensation_level.py index 8f0c9440da..29e031e16c 100644 --- a/improver/psychrometric_calculations/cloud_condensation_level.py +++ b/improver/psychrometric_calculations/cloud_condensation_level.py @@ -45,7 +45,7 @@ def __init__(self, model_id_attr: str = None): model_id_attr=model_id_attr ) - def process(self, *cubes: Union[Cube, CubeList]) -> Tuple[Cube, Cube]: + def process(self, *cubes: Union[Cube, CubeList]) -> CubeList[Cube, Cube]: """ Call HumidityMixingRatio followed by CloudCondensationLevel to calculate cloud condensation level. @@ -138,7 +138,7 @@ def humidity_delta(p2, p, t, q): ).astype(np.float32) return ccl_pressure, ccl_temperature - def process(self, *cubes: Union[Cube, CubeList]) -> Tuple[Cube, Cube]: + def process(self, *cubes: Union[Cube, CubeList]) -> CubeList[Cube, Cube]: """ Calculates the cloud condensation level from the near-surface inputs. Values will be limited to the surface values where the calculated pressure is greater than @@ -164,7 +164,9 @@ def process(self, *cubes: Union[Cube, CubeList]) -> Tuple[Cube, Cube]: mask = ccl_pressure > self.pressure.data ccl_pressure = np.where(mask, self.pressure.data, ccl_pressure) ccl_temperature = np.where(mask, self.temperature.data, ccl_temperature) - return ( - self._make_ccl_cube(ccl_temperature, is_temperature=True), - self._make_ccl_cube(ccl_pressure, is_temperature=False), + return CubeList( + [ + self._make_ccl_cube(ccl_temperature, is_temperature=True), + self._make_ccl_cube(ccl_pressure, is_temperature=False), + ] ) diff --git a/improver/utilities/spatial.py b/improver/utilities/spatial.py index 65f86593f0..71806526b5 100644 --- a/improver/utilities/spatial.py +++ b/improver/utilities/spatial.py @@ -681,7 +681,7 @@ def _create_output_cube(gradient: Cube, name: str) -> Cube: ) return grad_cube - def process(self, cube: Cube) -> Tuple[Cube, Cube]: + def process(self, cube: Cube) -> CubeList[Cube, Cube]: """ Calculate the gradient along the x and y axes and return the result in separate cubes. The difference along each axis is @@ -699,7 +699,7 @@ def process(self, cube: Cube) -> Tuple[Cube, Cube]: y-axis. """ axis = ["x", "y"] - gradients = [] + gradients = CubeList([]) diffs = DifferenceBetweenAdjacentGridSquares()(cube) distances = DistanceBetweenGridSquares()(cube) @@ -713,7 +713,7 @@ def process(self, cube: Cube) -> Tuple[Cube, Cube]: grad_cube = grad_cube.regrid(cube, iris.analysis.Linear()) gradients.append(grad_cube) - return tuple(gradients) + return gradients def maximum_within_vicinity( diff --git a/improver_tests/psychrometric_calculations/cloud_condensation_level/test_CloudCondensationLevel.py b/improver_tests/psychrometric_calculations/cloud_condensation_level/test_CloudCondensationLevel.py index bfc5e77152..3bb978da70 100644 --- a/improver_tests/psychrometric_calculations/cloud_condensation_level/test_CloudCondensationLevel.py +++ b/improver_tests/psychrometric_calculations/cloud_condensation_level/test_CloudCondensationLevel.py @@ -9,7 +9,7 @@ import numpy as np import pytest from iris.coords import AuxCoord -from iris.cube import Cube +from iris.cube import Cube, CubeList from improver.metadata.constants.attributes import MANDATORY_ATTRIBUTES from improver.psychrometric_calculations.cloud_condensation_level import ( @@ -125,6 +125,7 @@ def test_basic( humidity.data = np.full_like(humidity.data, humidity_value) result = CloudCondensationLevel()([temperature, pressure, humidity]) metadata_ok(result, temperature) + assert isinstance(result, CubeList) assert np.isclose(result[0].data, expected_t, atol=1e-2).all() assert np.isclose(result[1].data, expected_p, atol=1e-0).all() diff --git a/improver_tests/utilities/spatial/test_GradientBetweenAdjacentGridSquares.py b/improver_tests/utilities/spatial/test_GradientBetweenAdjacentGridSquares.py index e97dd41cf1..085c498115 100644 --- a/improver_tests/utilities/spatial/test_GradientBetweenAdjacentGridSquares.py +++ b/improver_tests/utilities/spatial/test_GradientBetweenAdjacentGridSquares.py @@ -6,6 +6,7 @@ import numpy as np import pytest +from iris.cube import CubeList from improver.metadata.constants.attributes import MANDATORY_ATTRIBUTE_DEFAULTS from improver.synthetic_data.set_up_test_cubes import set_up_variable_cube @@ -135,7 +136,9 @@ def test_metadata( expected_x_coord = cube.coord(axis="x").copy(expected_x_points) expected_y_coord = cube.coord(axis="y").copy(expected_y_points) plugin = GradientBetweenAdjacentGridSquares(regrid=regrid) - result_x, result_y = plugin(cube) + result = plugin(cube) + assert isinstance(result, CubeList) + result_x, result_y = result for result, name in [(result_x, "x"), (result_y, "y")]: assert result.name() == f"gradient_of_air_temperature_wrt_{name}" assert result.units == "K m-1" From 64144317f98fbba1812ed6974864e812a8230229 Mon Sep 17 00:00:00 2001 From: Gavin Evans Date: Fri, 26 Sep 2025 10:01:28 +0100 Subject: [PATCH 2/2] Modify cli/init.py. --- improver/cli/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/improver/cli/__init__.py b/improver/cli/__init__.py index 72eefe55ff..7962b50334 100644 --- a/improver/cli/__init__.py +++ b/improver/cli/__init__.py @@ -357,8 +357,11 @@ def with_output( # save as netCDF if ( output - and (isinstance(result, (Cube, CubeList))) - or all([isinstance(x, Cube) for x in result]) + and result + and ( + (isinstance(result, (Cube, CubeList))) + or all([isinstance(x, Cube) for x in result]) + ) ): save_netcdf(result, output, compression_level, least_significant_digit) if pass_through_output: