From 1227b2f36374d1dea5b3a0bb9f69c8643b73dd2e Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Wed, 16 Oct 2024 13:41:43 +0200 Subject: [PATCH] Enable ruff flake8-bugbear rule (#2536) Co-authored-by: Manuel Schlund <32543114+schlunma@users.noreply.github.com> --- doc/contributing.rst | 2 +- doc/recipe/preprocessor.rst | 4 +- esmvalcore/_recipe/check.py | 20 ++--- esmvalcore/_recipe/recipe.py | 12 +-- esmvalcore/cmor/_fixes/fix.py | 4 +- esmvalcore/config/_config_object.py | 12 +-- esmvalcore/config/_config_validators.py | 4 +- esmvalcore/config/_validated_config.py | 1 + esmvalcore/experimental/_warnings.py | 1 + esmvalcore/preprocessor/__init__.py | 4 +- esmvalcore/preprocessor/_derive/__init__.py | 2 +- esmvalcore/preprocessor/_derive/ctotal.py | 4 +- esmvalcore/preprocessor/_derive/ohc.py | 2 +- esmvalcore/preprocessor/_io.py | 2 +- esmvalcore/preprocessor/_mapping.py | 4 +- esmvalcore/preprocessor/_regrid.py | 4 +- esmvalcore/preprocessor/_regrid_esmpy.py | 10 +-- esmvalcore/preprocessor/_shared.py | 2 +- pyproject.toml | 5 ++ .../integration/preprocessor/_io/test_load.py | 6 +- tests/integration/recipe/test_check.py | 75 +++++++++++++++++++ tests/integration/recipe/test_recipe.py | 6 +- tests/integration/test_task.py | 8 +- .../experimental/test_run_recipe.py | 2 +- tests/unit/cmor/test_fix.py | 2 +- tests/unit/preprocessor/_area/test_area.py | 2 +- .../_multimodel/test_multimodel.py | 6 +- tests/unit/preprocessor/_other/test_other.py | 24 +++--- tests/unit/test_dataset.py | 2 +- tests/unit/test_iris_helpers.py | 8 +- 30 files changed, 164 insertions(+), 76 deletions(-) diff --git a/doc/contributing.rst b/doc/contributing.rst index 8737fda13e..81a8ffb012 100644 --- a/doc/contributing.rst +++ b/doc/contributing.rst @@ -609,7 +609,7 @@ that feature should be removed in version 2.7: "ESMValCore version 2.5 and is scheduled for removal in " "version 2.7. Add additional text (e.g., description of " "alternatives) here.") - warnings.warn(deprecation_msg, ESMValCoreDeprecationWarning) + warnings.warn(deprecation_msg, ESMValCoreDeprecationWarning, stacklevel=2) # Other code diff --git a/doc/recipe/preprocessor.rst b/doc/recipe/preprocessor.rst index 0954b26daa..92bd400e61 100644 --- a/doc/recipe/preprocessor.rst +++ b/doc/recipe/preprocessor.rst @@ -611,7 +611,7 @@ See also :func:`esmvalcore.preprocessor.weighting_landsea_fraction`. .. _masking: Masking -======= +======== Introduction to masking ----------------------- @@ -2451,7 +2451,7 @@ See also :func:`esmvalcore.preprocessor.linear_trend_stderr`. .. _detrend: Detrend -======= +======== ESMValCore also supports detrending along any dimension using the preprocessor function 'detrend'. diff --git a/esmvalcore/_recipe/check.py b/esmvalcore/_recipe/check.py index e4f60e9d7f..9a26ef4561 100644 --- a/esmvalcore/_recipe/check.py +++ b/esmvalcore/_recipe/check.py @@ -44,12 +44,12 @@ def ncl_version(): try: cmd = [ncl, "-V"] version = subprocess.check_output(cmd, universal_newlines=True) - except subprocess.CalledProcessError: - logger.error("Failed to execute '%s'", " ".join(" ".join(cmd))) + except subprocess.CalledProcessError as exc: + logger.error("Failed to execute '%s'", " ".join(cmd)) raise RecipeError( "Recipe contains NCL scripts, but your NCL " "installation appears to be broken." - ) + ) from exc version = version.strip() logger.info("Found NCL version %s", version) @@ -383,7 +383,7 @@ def _check_duration_periods(timerange): f"{timerange[0]} is not valid duration according to ISO 8601." + "\n" + str(exc) - ) + ) from exc elif timerange[1].startswith("P"): try: isodate.parse_duration(timerange[1]) @@ -393,7 +393,7 @@ def _check_duration_periods(timerange): f"{timerange[1]} is not valid duration according to ISO 8601." + "\n" + str(exc) - ) + ) from exc def _check_format_years(date): @@ -423,7 +423,7 @@ def _check_timerange_values(date, timerange): "for dates and duration periods, or be " "set to '*' to load available years. " f"Got {timerange} instead." + "\n" + str(exc) - ) + ) from exc def valid_time_selection(timerange): @@ -584,7 +584,7 @@ def _check_regular_stat(step, step_settings): try: get_iris_aggregator(operator, **operator_kwargs) except ValueError as exc: - raise RecipeError(f"Invalid options for {step}: {exc}") + raise RecipeError(f"Invalid options for {step}: {exc}") from exc def _check_mm_stat(step, step_settings): @@ -594,11 +594,11 @@ def _check_mm_stat(step, step_settings): try: (operator, kwargs) = _get_operator_and_kwargs(stat) except ValueError as exc: - raise RecipeError(str(exc)) + raise RecipeError(str(exc)) from exc try: get_iris_aggregator(operator, **kwargs) except ValueError as exc: - raise RecipeError(f"Invalid options for {step}: {exc}") + raise RecipeError(f"Invalid options for {step}: {exc}") from exc def regridding_schemes(settings: dict): @@ -645,4 +645,4 @@ def regridding_schemes(settings: dict): f"https://docs.esmvaltool.org/projects/ESMValCore/en/latest" f"/recipe/preprocessor.html#generic-regridding-schemes for " f"details." - ) + ) from exc diff --git a/esmvalcore/_recipe/recipe.py b/esmvalcore/_recipe/recipe.py index 41002bbc1b..55e789d6f4 100644 --- a/esmvalcore/_recipe/recipe.py +++ b/esmvalcore/_recipe/recipe.py @@ -418,11 +418,11 @@ def _update_multiproduct(input_products, order, preproc_dir, step): called from the input products, the products that are created here need to be added to their ancestors products' settings (). """ - products = {p for p in input_products if step in p.settings} - if not products: + multiproducts = {p for p in input_products if step in p.settings} + if not multiproducts: return input_products, {} - settings = list(products)[0].settings[step] + settings = list(multiproducts)[0].settings[step] if step == "ensemble_statistics": check.ensemble_statistics_preproc(settings) @@ -431,14 +431,16 @@ def _update_multiproduct(input_products, order, preproc_dir, step): check.multimodel_statistics_preproc(settings) grouping = settings.get("groupby", None) - downstream_settings = _get_downstream_settings(step, order, products) + downstream_settings = _get_downstream_settings(step, order, multiproducts) relevant_settings = { "output_products": defaultdict(dict) } # pass to ancestors output_products = set() - for identifier, products in _group_products(products, by_key=grouping): + for identifier, products in _group_products( + multiproducts, by_key=grouping + ): common_attributes = _get_common_attributes(products, settings) statistics = settings.get("statistics", []) diff --git a/esmvalcore/cmor/_fixes/fix.py b/esmvalcore/cmor/_fixes/fix.py index 5aa41f6486..973ac57d0b 100644 --- a/esmvalcore/cmor/_fixes/fix.py +++ b/esmvalcore/cmor/_fixes/fix.py @@ -141,7 +141,7 @@ def get_cube_from_list( Raises ------ - Exception + ValueError No cube is found. Returns @@ -155,7 +155,7 @@ def get_cube_from_list( for cube in cubes: if cube.var_name == short_name: return cube - raise Exception(f'Cube for variable "{short_name}" not found') + raise ValueError(f'Cube for variable "{short_name}" not found') def fix_data(self, cube: Cube) -> Cube: """Apply fixes to the data of the cube. diff --git a/esmvalcore/config/_config_object.py b/esmvalcore/config/_config_object.py index baa344f829..489e2301b2 100644 --- a/esmvalcore/config/_config_object.py +++ b/esmvalcore/config/_config_object.py @@ -92,7 +92,7 @@ def __init__(self, *args, **kwargs): "Do not instantiate `Config` objects directly, this will lead " "to unexpected behavior. Use `esmvalcore.config.CFG` instead." ) - warnings.warn(msg, UserWarning) + warnings.warn(msg, UserWarning, stacklevel=2) # TODO: remove in v2.14.0 @classmethod @@ -313,7 +313,7 @@ def load_from_file( "ESMValCore version 2.12.0 and is scheduled for removal in " "version 2.14.0. Please use `CFG.load_from_dirs()` instead." ) - warnings.warn(msg, ESMValCoreDeprecationWarning) + warnings.warn(msg, ESMValCoreDeprecationWarning, stacklevel=2) self.clear() self.update(Config._load_user_config(filename)) @@ -399,7 +399,9 @@ def reload(self) -> None: f"alternatively use a custom `--config_dir`) and omit " f"`--config_file`." ) - warnings.warn(deprecation_msg, ESMValCoreDeprecationWarning) + warnings.warn( + deprecation_msg, ESMValCoreDeprecationWarning, stacklevel=2 + ) self.update(Config._load_user_config(raise_exception=False)) return @@ -505,7 +507,7 @@ def __init__(self, config: dict, name: str = "session"): "to unexpected behavior. Use " "`esmvalcore.config.CFG.start_session` instead." ) - warnings.warn(msg, UserWarning) + warnings.warn(msg, UserWarning, stacklevel=2) def set_session_name(self, name: str = "session"): """Set the name for the session. @@ -556,7 +558,7 @@ def config_dir(self): "ESMValCore version 2.12.0 and is scheduled for removal in " "version 2.14.0." ) - warnings.warn(msg, ESMValCoreDeprecationWarning) + warnings.warn(msg, ESMValCoreDeprecationWarning, stacklevel=2) if self.get("config_file") is None: return None return Path(self["config_file"]).parent diff --git a/esmvalcore/config/_config_validators.py b/esmvalcore/config/_config_validators.py index 9cc85bee5e..dd3f2d268d 100644 --- a/esmvalcore/config/_config_validators.py +++ b/esmvalcore/config/_config_validators.py @@ -311,7 +311,7 @@ def validate_extra_facets_dir(value): "ESMValCore version 2.12.0 and is scheduled for removal in " "version 2.14.0. Please use a list instead." ) - warnings.warn(msg, ESMValCoreDeprecationWarning) + warnings.warn(msg, ESMValCoreDeprecationWarning, stacklevel=2) value = list(value) return validate_pathlist(value) @@ -371,7 +371,7 @@ def _handle_deprecation( f"been deprecated in ESMValCore version {deprecated_version} and is " f"scheduled for removal in version {remove_version}.{more_info}" ) - warnings.warn(deprecation_msg, ESMValCoreDeprecationWarning) + warnings.warn(deprecation_msg, ESMValCoreDeprecationWarning, stacklevel=2) # TODO: remove in v2.14.0 diff --git a/esmvalcore/config/_validated_config.py b/esmvalcore/config/_validated_config.py index 898abf3bb8..dca0a543f4 100644 --- a/esmvalcore/config/_validated_config.py +++ b/esmvalcore/config/_validated_config.py @@ -121,6 +121,7 @@ def check_missing(self): warnings.warn( f"`{key}` is not defined{more_info}", MissingConfigParameter, + stacklevel=1, ) def copy(self): diff --git a/esmvalcore/experimental/_warnings.py b/esmvalcore/experimental/_warnings.py index ddc474f568..548dc4e853 100644 --- a/esmvalcore/experimental/_warnings.py +++ b/esmvalcore/experimental/_warnings.py @@ -14,4 +14,5 @@ def _warning_formatter(message, category, filename, lineno, line=None): "\n Thank you for trying out the new ESMValCore API." "\n Note that this API is experimental and may be subject to change." "\n More info: https://github.com/ESMValGroup/ESMValCore/issues/498", + stacklevel=1, ) diff --git a/esmvalcore/preprocessor/__init__.py b/esmvalcore/preprocessor/__init__.py index 3429078a5d..851aae49f0 100644 --- a/esmvalcore/preprocessor/__init__.py +++ b/esmvalcore/preprocessor/__init__.py @@ -717,13 +717,13 @@ def _run(self, _): if step in product.settings: product.apply(step, self.debug) if block == blocks[-1]: - product.cubes # pylint: disable=pointless-statement + product.cubes # noqa: B018 pylint: disable=pointless-statement product.close() saved.add(product.filename) for product in self.products: if product.filename not in saved: - product.cubes # pylint: disable=pointless-statement + product.cubes # noqa: B018 pylint: disable=pointless-statement product.close() metadata_files = write_metadata( diff --git a/esmvalcore/preprocessor/_derive/__init__.py b/esmvalcore/preprocessor/_derive/__init__.py index 065845ef4d..add5d822e6 100644 --- a/esmvalcore/preprocessor/_derive/__init__.py +++ b/esmvalcore/preprocessor/_derive/__init__.py @@ -27,7 +27,7 @@ def _get_all_derived_variables(): module = importlib.import_module( f"esmvalcore.preprocessor._derive.{short_name}" ) - derivers[short_name] = getattr(module, "DerivedVariable") + derivers[short_name] = module.DerivedVariable return derivers diff --git a/esmvalcore/preprocessor/_derive/ctotal.py b/esmvalcore/preprocessor/_derive/ctotal.py index 8d8d00faef..159289f13e 100644 --- a/esmvalcore/preprocessor/_derive/ctotal.py +++ b/esmvalcore/preprocessor/_derive/ctotal.py @@ -37,12 +37,12 @@ def calculate(cubes): c_soil_cube = cubes.extract_cube( Constraint(name="soil_mass_content_of_carbon") ) - except iris.exceptions.ConstraintMismatchError: + except iris.exceptions.ConstraintMismatchError as exc: raise ValueError( f"No cube from {cubes} can be loaded with " f"standard name CMIP5: soil_carbon_content " f"or CMIP6: soil_mass_content_of_carbon" - ) + ) from exc c_veg_cube = cubes.extract_cube( Constraint(name="vegetation_carbon_content") ) diff --git a/esmvalcore/preprocessor/_derive/ohc.py b/esmvalcore/preprocessor/_derive/ohc.py index 05590c9f3b..6cea2b06f5 100644 --- a/esmvalcore/preprocessor/_derive/ohc.py +++ b/esmvalcore/preprocessor/_derive/ohc.py @@ -74,7 +74,7 @@ def calculate(cubes): contains_dimension=t_coord_dim, dim_coords=False ) ] - for coord, dims in dim_coords + aux_coords: + for coord, _ in dim_coords + aux_coords: cube.remove_coord(coord) new_cube = cube * volume new_cube *= RHO_CP diff --git a/esmvalcore/preprocessor/_io.py b/esmvalcore/preprocessor/_io.py index d30255ec13..5f83b1946c 100644 --- a/esmvalcore/preprocessor/_io.py +++ b/esmvalcore/preprocessor/_io.py @@ -321,7 +321,7 @@ def _sort_cubes_by_time(cubes): msg = "One or more cubes {} are missing".format( cubes ) + " time coordinate: {}".format(str(exc)) - raise ValueError(msg) + raise ValueError(msg) from exc except TypeError as error: msg = ( "Cubes cannot be sorted " diff --git a/esmvalcore/preprocessor/_mapping.py b/esmvalcore/preprocessor/_mapping.py index ccbeed2816..a84df1e67e 100644 --- a/esmvalcore/preprocessor/_mapping.py +++ b/esmvalcore/preprocessor/_mapping.py @@ -57,10 +57,10 @@ def ref_to_dims_index_as_index(cube, ref): """Get dim for index ref.""" try: dim = int(ref) - except (ValueError, TypeError): + except (ValueError, TypeError) as exc: raise ValueError( "{} Incompatible type {} for slicing".format(ref, type(ref)) - ) + ) from exc if dim < 0 or dim > cube.ndim: msg = ( "Requested an iterator over a dimension ({}) " diff --git a/esmvalcore/preprocessor/_regrid.py b/esmvalcore/preprocessor/_regrid.py index a8558f6ee1..2fc34e4d85 100644 --- a/esmvalcore/preprocessor/_regrid.py +++ b/esmvalcore/preprocessor/_regrid.py @@ -588,7 +588,7 @@ def _load_scheme(src_cube: Cube, tgt_cube: Cube, scheme: str | dict): "version 2.11.0, ESMValCore is able to determine the most " "suitable regridding scheme based on the input data." ) - warnings.warn(msg, ESMValCoreDeprecationWarning) + warnings.warn(msg, ESMValCoreDeprecationWarning, stacklevel=2) scheme = "nearest" if scheme == "linear_extrapolate": @@ -601,7 +601,7 @@ def _load_scheme(src_cube: Cube, tgt_cube: Cube, scheme: str | dict): "latest/recipe/preprocessor.html#generic-regridding-schemes)." "This is an exact replacement." ) - warnings.warn(msg, ESMValCoreDeprecationWarning) + warnings.warn(msg, ESMValCoreDeprecationWarning, stacklevel=2) scheme = "linear" loaded_scheme = Linear(extrapolation_mode="extrapolate") logger.debug("Loaded regridding scheme %s", loaded_scheme) diff --git a/esmvalcore/preprocessor/_regrid_esmpy.py b/esmvalcore/preprocessor/_regrid_esmpy.py index b2cb559406..e4d0b40ba6 100755 --- a/esmvalcore/preprocessor/_regrid_esmpy.py +++ b/esmvalcore/preprocessor/_regrid_esmpy.py @@ -8,7 +8,7 @@ try: import ESMF as esmpy # noqa: N811 except ImportError: - raise exc + raise exc from None import warnings import iris @@ -78,8 +78,8 @@ def __init__( ): """Initialize class instance.""" # These regridders are not lazy, so load source and target data once. - src_cube.data # pylint: disable=pointless-statement - tgt_cube.data # pylint: disable=pointless-statement + src_cube.data # # noqa: B018 pylint: disable=pointless-statement + tgt_cube.data # # noqa: B018 pylint: disable=pointless-statement self.src_cube = src_cube self.tgt_cube = tgt_cube self.method = method @@ -100,7 +100,7 @@ def __call__(self, cube: Cube) -> Cube: """ # These regridders are not lazy, so load source data once. - cube.data # pylint: disable=pointless-statement + cube.data # # noqa: B018 pylint: disable=pointless-statement src_rep, dst_rep = get_grid_representants(cube, self.tgt_cube) regridder = build_regridder( src_rep, dst_rep, self.method, mask_threshold=self.mask_threshold @@ -140,7 +140,7 @@ def __init__(self, mask_threshold: float = 0.99): "`esmvalcore.preprocessor.regrid_schemes.IrisESMFRegrid` " "instead." ) - warnings.warn(msg, ESMValCoreDeprecationWarning) + warnings.warn(msg, ESMValCoreDeprecationWarning, stacklevel=2) self.mask_threshold = mask_threshold def __repr__(self) -> str: diff --git a/esmvalcore/preprocessor/_shared.py b/esmvalcore/preprocessor/_shared.py index 49272771b5..2355215800 100644 --- a/esmvalcore/preprocessor/_shared.py +++ b/esmvalcore/preprocessor/_shared.py @@ -95,7 +95,7 @@ def get_iris_aggregator( except (ValueError, TypeError) as exc: raise ValueError( f"Invalid kwargs for operator '{operator}': {str(exc)}" - ) + ) from exc return (aggregator, aggregator_kwargs) diff --git a/pyproject.toml b/pyproject.toml index 5a45ca2ab9..5abbbaa1d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ disable = [ line-length = 79 [tool.ruff.lint] select = [ + "B", "E", # pycodestyle "F", # pyflakes "I", # isort @@ -42,5 +43,9 @@ select = [ ignore = [ "E501", # Disable line-too-long as this is taken care of by the formatter. ] +[tool.ruff.lint.per-file-ignores] +"tests/**.py" = [ + "B011", # `assert False` is valid test code. +] [tool.ruff.lint.isort] known-first-party = ["esmvalcore"] diff --git a/tests/integration/preprocessor/_io/test_load.py b/tests/integration/preprocessor/_io/test_load.py index 1df7c3bb55..4c76ba2651 100644 --- a/tests/integration/preprocessor/_io/test_load.py +++ b/tests/integration/preprocessor/_io/test_load.py @@ -90,7 +90,7 @@ def test_callback_remove_attributes_from_coords(self): ) for coord in cube.coords(): for attr in attributes: - self.assertTrue(attr not in cube.attributes) + self.assertTrue(attr not in coord.attributes) def test_callback_fix_lat_units(self): """Test callback for fixing units.""" @@ -118,7 +118,9 @@ def test_fail_empty_cubes(self, mock_load_raw): def load_with_warning(*_, **__): """Mock load with a warning.""" warnings.warn( - "This is a custom expected warning", category=UserWarning + "This is a custom expected warning", + category=UserWarning, + stacklevel=2, ) return CubeList([Cube(0)]) diff --git a/tests/integration/recipe/test_check.py b/tests/integration/recipe/test_check.py index d168a3e010..1be8324037 100644 --- a/tests/integration/recipe/test_check.py +++ b/tests/integration/recipe/test_check.py @@ -1,6 +1,7 @@ """Integration tests for :mod:`esmvalcore._recipe.check`.""" import os.path +import subprocess from pathlib import Path from typing import Any, List from unittest import mock @@ -15,6 +16,80 @@ from esmvalcore.exceptions import RecipeError from esmvalcore.preprocessor import PreprocessorFile + +def test_ncl_version(mocker): + ncl = "/path/to/ncl" + mocker.patch.object( + check, + "which", + autospec=True, + return_value=ncl, + ) + mocker.patch.object( + check.subprocess, + "check_output", + autospec=True, + return_value="6.6.2\n", + ) + check.ncl_version() + + +def test_ncl_version_too_low(mocker): + ncl = "/path/to/ncl" + mocker.patch.object( + check, + "which", + autospec=True, + return_value=ncl, + ) + mocker.patch.object( + check.subprocess, + "check_output", + autospec=True, + return_value="6.3.2\n", + ) + with pytest.raises( + RecipeError, + match="NCL version 6.4 or higher is required", + ): + check.ncl_version() + + +def test_ncl_version_no_ncl(mocker): + mocker.patch.object( + check, + "which", + autospec=True, + return_value=None, + ) + with pytest.raises( + RecipeError, + match="cannot find an NCL installation", + ): + check.ncl_version() + + +def test_ncl_version_broken(mocker): + ncl = "/path/to/ncl" + mocker.patch.object( + check, + "which", + autospec=True, + return_value=ncl, + ) + mocker.patch.object( + check.subprocess, + "check_output", + autospec=True, + side_effect=subprocess.CalledProcessError(1, [ncl, "-V"]), + ) + with pytest.raises( + RecipeError, + match="NCL installation appears to be broken", + ): + check.ncl_version() + + ERR_ALL = "Looked for files matching%s" ERR_RANGE = "No input data available for years {} in files:\n{}" VAR = { diff --git a/tests/integration/recipe/test_recipe.py b/tests/integration/recipe/test_recipe.py index ae1ff9b5b7..5c58e9dc1a 100644 --- a/tests/integration/recipe/test_recipe.py +++ b/tests/integration/recipe/test_recipe.py @@ -1750,7 +1750,7 @@ def test_extract_shape_raises( def _test_output_product_consistency(products, preprocessor, statistics): product_out = defaultdict(list) - for i, product in enumerate(products): + for product in products: settings = product.settings.get(preprocessor) if settings: output_products = settings["output_products"] @@ -1760,7 +1760,7 @@ def _test_output_product_consistency(products, preprocessor, statistics): product_out[identifier, statistic].append(preproc_file) # Make sure that output products are consistent - for (identifier, statistic), value in product_out.items(): + for (_, statistic), value in product_out.items(): assert statistic in statistics assert len(set(value)) == 1, "Output products are not equal" @@ -1908,7 +1908,7 @@ def test_multi_model_statistics_exclude(tmp_path, patched_datafinder, session): assert len(product_out) == len(statistics) assert "OBS" not in product_out - for id, prods in product_out: + for id, _ in product_out: assert id != "OBS" assert id == "CMIP5" task._initialize_product_provenance() diff --git a/tests/integration/test_task.py b/tests/integration/test_task.py index 2fb56b2cc4..cb8c632fbf 100644 --- a/tests/integration/test_task.py +++ b/tests/integration/test_task.py @@ -341,7 +341,9 @@ def _get_diagnostic_tasks(tmp_path, diagnostic_text, extension): def test_diagnostic_run_task(monkeypatch, executable, diag_text, tmp_path): """Run DiagnosticTask that will not fail.""" - def _run(self, input_filesi=[]): + def _run(self, input_filesi=None): + if input_filesi is None: + input_filesi = [] print(f"running task {self.name}") task = _get_diagnostic_tasks(tmp_path, diag_text, executable[1]) @@ -356,7 +358,9 @@ def test_diagnostic_run_task_fail( ): """Run DiagnosticTask that will fail.""" - def _run(self, input_filesi=[]): + def _run(self, input_filesi=None): + if input_filesi is None: + input_filesi = [] print(f"running task {self.name}") task = _get_diagnostic_tasks(tmp_path, diag_text[0], executable[1]) diff --git a/tests/sample_data/experimental/test_run_recipe.py b/tests/sample_data/experimental/test_run_recipe.py index 141cc74c57..d0d6b079e7 100644 --- a/tests/sample_data/experimental/test_run_recipe.py +++ b/tests/sample_data/experimental/test_run_recipe.py @@ -95,7 +95,7 @@ def test_run_recipe( assert isinstance(output.read_main_log(), str) assert isinstance(output.read_main_log_debug(), str) - for task, task_output in output.items(): + for _, task_output in output.items(): assert isinstance(task_output, TaskOutput) assert len(task_output) > 0 diff --git a/tests/unit/cmor/test_fix.py b/tests/unit/cmor/test_fix.py index 8c005f1400..4279d60df6 100644 --- a/tests/unit/cmor/test_fix.py +++ b/tests/unit/cmor/test_fix.py @@ -101,7 +101,7 @@ def test_get_second_cube(self): def test_get_default_raises(self): """Check that the default raises (Fix is not a cube).""" - with pytest.raises(Exception): + with pytest.raises(ValueError): self.fix.get_cube_from_list(self.cubes) def test_get_default(self): diff --git a/tests/unit/preprocessor/_area/test_area.py b/tests/unit/preprocessor/_area/test_area.py index 9e88002aaa..99eaf3f150 100644 --- a/tests/unit/preprocessor/_area/test_area.py +++ b/tests/unit/preprocessor/_area/test_area.py @@ -1333,7 +1333,7 @@ def test_update_shapefile_path_abs(session, tmp_path): # Test with Path and str object for shapefile_in in (shapefile, str(shapefile)): - shapefile_out = _update_shapefile_path(shapefile, session=session) + shapefile_out = _update_shapefile_path(shapefile_in, session=session) assert isinstance(shapefile_out, Path) assert shapefile_out == shapefile diff --git a/tests/unit/preprocessor/_multimodel/test_multimodel.py b/tests/unit/preprocessor/_multimodel/test_multimodel.py index 39c11c944c..653cd61038 100644 --- a/tests/unit/preprocessor/_multimodel/test_multimodel.py +++ b/tests/unit/preprocessor/_multimodel/test_multimodel.py @@ -195,11 +195,7 @@ def get_cubes_for_validation_test(frequency, lazy=False): def get_cube_for_equal_coords_test(num_cubes): """Set up cubes with equal auxiliary coordinates.""" - cubes = [] - - for num in range(num_cubes): - cube = generate_cube_from_dates("monthly") - cubes.append(cube) + cubes = [generate_cube_from_dates("monthly") for _ in range(num_cubes)] # Create cubes that have one exactly equal coordinate ('year'), one # coordinate with matching names ('m') and one coordinate with non-matching diff --git a/tests/unit/preprocessor/_other/test_other.py b/tests/unit/preprocessor/_other/test_other.py index a2237bfb6a..c50bed0a83 100644 --- a/tests/unit/preprocessor/_other/test_other.py +++ b/tests/unit/preprocessor/_other/test_other.py @@ -120,9 +120,9 @@ def test_histogram_defaults(cube, lazy): ) np.testing.assert_allclose(result.data.mask, [False] * 10) bin_coord = result.coord("air_temperature") - bin_coord.shape == (10,) - bin_coord.dtype == np.float64 - bin_coord.bounds_dtype == np.float64 + assert bin_coord.shape == (10,) + assert bin_coord.dtype == np.float64 + assert bin_coord.bounds_dtype == np.float64 np.testing.assert_allclose( bin_coord.points, [0.35, 1.05, 1.75, 2.45, 3.15, 3.85, 4.55, 5.25, 5.95, 6.65], @@ -196,9 +196,9 @@ def test_histogram_over_time(cube, lazy, weights, normalization): np.testing.assert_allclose(result.data, expected_data) np.testing.assert_allclose(result.data.mask, expected_data.mask) bin_coord = result.coord("air_temperature") - bin_coord.shape == (10,) - bin_coord.dtype == np.float64 - bin_coord.bounds_dtype == np.float64 + assert bin_coord.shape == (3,) + assert bin_coord.dtype == np.float64 + assert bin_coord.bounds_dtype == np.float64 np.testing.assert_allclose(bin_coord.points, [5.5, 7.5, 9.5]) np.testing.assert_allclose( bin_coord.bounds, @@ -231,9 +231,9 @@ def test_histogram_fully_masked(cube, lazy, normalization): ) np.testing.assert_equal(result.data.mask, [True] * 10) bin_coord = result.coord("air_temperature") - bin_coord.shape == (10,) - bin_coord.dtype == np.float64 - bin_coord.bounds_dtype == np.float64 + assert bin_coord.shape == (10,) + assert bin_coord.dtype == np.float64 + assert bin_coord.bounds_dtype == np.float64 np.testing.assert_allclose( bin_coord.points, [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5], @@ -303,9 +303,9 @@ def test_histogram_weights(cube, lazy, weights, normalization): np.testing.assert_allclose(result.data, expected_data) np.testing.assert_allclose(result.data.mask, expected_data.mask) bin_coord = result.coord("air_temperature") - bin_coord.shape == (10,) - bin_coord.dtype == np.float64 - bin_coord.bounds_dtype == np.float64 + assert bin_coord.shape == (3,) + assert bin_coord.dtype == np.float64 + assert bin_coord.bounds_dtype == np.float64 np.testing.assert_allclose(bin_coord.points, [1.0, 3.0, 6.0]) np.testing.assert_allclose( bin_coord.bounds, diff --git a/tests/unit/test_dataset.py b/tests/unit/test_dataset.py index 1348dc0ebf..232803b627 100644 --- a/tests/unit/test_dataset.py +++ b/tests/unit/test_dataset.py @@ -110,7 +110,7 @@ def test_session_setter(): assert ds._session is None assert ds.supplementaries[0]._session is None - ds.session + ds.session # noqa: B018 assert isinstance(ds.session, Session) assert ds.session == ds.supplementaries[0].session diff --git a/tests/unit/test_iris_helpers.py b/tests/unit/test_iris_helpers.py index ccfd6fbbf6..1b5066ae51 100644 --- a/tests/unit/test_iris_helpers.py +++ b/tests/unit/test_iris_helpers.py @@ -329,10 +329,10 @@ def test_rechunk_cube_partly_lazy(cube_3d, complete_dims): input_cube = cube_3d.copy() # Realize some arrays - input_cube.data - input_cube.coord("xyz").points - input_cube.coord("xyz").bounds - input_cube.cell_measure("cell_measure").data + input_cube.data # noqa: B018 + input_cube.coord("xyz").points # noqa: B018 + input_cube.coord("xyz").bounds # noqa: B018 + input_cube.cell_measure("cell_measure").data # noqa: B018 result = rechunk_cube(input_cube, complete_dims, remaining_dims=2)