diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fe2ff11a..b3b9e7be 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,32 +10,37 @@ repos: - id: check-yaml - id: debug-statements - id: end-of-file-fixer + - id: no-commit-to-branch + args: [--branch, main] - id: trailing-whitespace + - repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: python-check-blanket-noqa + description: Require specific codes on noqa comments + - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell description: Check for spelling errors - - repo: https://github.com/psf/black - rev: 25.11.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.4 hooks: - - id: black - description: Format Python code + - id: ruff-check + args: [--fix] + description: Lint Python code with Ruff + - id: ruff-format + description: Format Python code with Ruff - - repo: https://github.com/PyCQA/isort - rev: 7.0.0 + - repo: https://github.com/tox-dev/pyproject-fmt + rev: v2.21.0 hooks: - - id: isort - description: Group and sort Python imports + - id: pyproject-fmt + description: Normalize pyproject.toml formatting - - repo: https://github.com/PyCQA/flake8 - rev: 7.3.0 - hooks: - - id: flake8 - description: Check Python code for correctness, consistency and adherence to best practices - additional_dependencies: [Flake8-pyproject] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.19.0 hooks: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cc1fa69..4bee3406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add comprehensive type hints to `neural_lam/create_graph.py` [\#618](https://github.com/mllam/neural-lam/pull/618) @GiGiKoneti +- Modernize linting and formatting toolchain by migrating `black`/`isort`/`flake8` to Ruff and adding `pyproject-fmt`, `python-check-blanket-noqa`, and `no-commit-to-branch` pre-commit hooks [\#614](https://github.com/mllam/neural-lam/pull/614) @Shivampal157 + ## [v0.6.0](https://github.com/mllam/neural-lam/releases/tag/v0.6.0) diff --git a/neural_lam/datastore/__init__.py b/neural_lam/datastore/__init__.py index dead7713..1837f245 100644 --- a/neural_lam/datastore/__init__.py +++ b/neural_lam/datastore/__init__.py @@ -1,7 +1,7 @@ # Local -from .base import BaseDatastore # noqa -from .mdp import MDPDatastore # noqa -from .npyfilesmeps import NpyFilesDatastoreMEPS # noqa +from .base import BaseDatastore +from .mdp import MDPDatastore +from .npyfilesmeps import NpyFilesDatastoreMEPS DATASTORE_CLASSES = [ MDPDatastore, diff --git a/neural_lam/datastore/mdp.py b/neural_lam/datastore/mdp.py index 114550e6..3911a48f 100644 --- a/neural_lam/datastore/mdp.py +++ b/neural_lam/datastore/mdp.py @@ -114,9 +114,9 @@ def __init__(self, config_path, n_boundary_points=30, reuse_existing=True): if dim_order is None: dim_order = dim_order_ else: - assert ( - dim_order == dim_order_ - ), "all inputs must have the same dimension order" + assert dim_order == dim_order_, ( + "all inputs must have the same dimension order" + ) self.spatial_coordinates = dim_order @@ -489,9 +489,9 @@ def get_xy(self, category: str, stacked: bool) -> ndarray: da_xs = ds_category[xdim] da_ys = ds_category[ydim] - assert ( - da_xs.ndim == da_ys.ndim == 1 - ), f"{xdim} and {ydim} coordinates must be 1D" + assert da_xs.ndim == da_ys.ndim == 1, ( + f"{xdim} and {ydim} coordinates must be 1D" + ) da_x, da_y = xr.broadcast(da_xs, da_ys) da_xy = xr.concat([da_x, da_y], dim="grid_coord") diff --git a/neural_lam/datastore/npyfilesmeps/__init__.py b/neural_lam/datastore/npyfilesmeps/__init__.py index 397a5075..9f1eb9c8 100644 --- a/neural_lam/datastore/npyfilesmeps/__init__.py +++ b/neural_lam/datastore/npyfilesmeps/__init__.py @@ -1,2 +1,2 @@ # Local -from .store import NpyFilesDatastoreMEPS # noqa +from .store import NpyFilesDatastoreMEPS diff --git a/neural_lam/datastore/npyfilesmeps/compute_standardization_stats.py b/neural_lam/datastore/npyfilesmeps/compute_standardization_stats.py index 531db87a..94cf3f9a 100644 --- a/neural_lam/datastore/npyfilesmeps/compute_standardization_stats.py +++ b/neural_lam/datastore/npyfilesmeps/compute_standardization_stats.py @@ -223,9 +223,10 @@ def main( flux_squares.append(torch.mean(flux_batch**2).cpu()) # (,) if distributed and world_size > 1: - means_gathered, squares_gathered = [None] * world_size, [ - None - ] * world_size + means_gathered, squares_gathered = ( + [None] * world_size, + [None] * world_size, + ) flux_means_gathered, flux_squares_gathered = ( [None] * world_size, [None] * world_size, @@ -322,9 +323,9 @@ def main( state_std = state_std.to(device) time_step_int, time_step_unit = get_integer_time(step_length) - assert ( - time_step_unit == "hours" - ), "Only 'hours' time unit is supported by meps datastore." + assert time_step_unit == "hours", ( + "Only 'hours' time unit is supported by meps datastore." + ) used_subsample_len = (65 // time_step_int) * time_step_int diff_means, diff_squares = [], [] @@ -333,8 +334,9 @@ def main( loader_standard, disable=rank != 0 ): if distributed: - init_batch, target_batch = init_batch.to(device), target_batch.to( - device + init_batch, target_batch = ( + init_batch.to(device), + target_batch.to(device), ) init_batch = (init_batch - state_mean) / state_std target_batch = (target_batch - state_mean) / state_std diff --git a/neural_lam/datastore/plot_example.py b/neural_lam/datastore/plot_example.py index 19a81e30..13b4cbd5 100644 --- a/neural_lam/datastore/plot_example.py +++ b/neural_lam/datastore/plot_example.py @@ -157,9 +157,9 @@ def _parse_dict(arg_str): ) args = parser.parse_args() - assert ( - args.datastore_config_path is not None - ), "Specify your datastore config with --datastore_config_path" + assert args.datastore_config_path is not None, ( + "Specify your datastore config with --datastore_config_path" + ) selection = dict(args.selection) index_selection = dict(args.index_selection) diff --git a/neural_lam/gnn_layers.py b/neural_lam/gnn_layers.py index 40528ac2..996774e6 100644 --- a/neural_lam/gnn_layers.py +++ b/neural_lam/gnn_layers.py @@ -247,9 +247,9 @@ class SplitMLPs(nn.Module): def __init__(self, mlps, chunk_sizes): super().__init__() - assert len(mlps) == len( - chunk_sizes - ), "Number of MLPs must match the number of chunks" + assert len(mlps) == len(chunk_sizes), ( + "Number of MLPs must match the number of chunks" + ) self.mlps = nn.ModuleList(mlps) self.chunk_sizes = chunk_sizes diff --git a/neural_lam/metrics.py b/neural_lam/metrics.py index ee5f0bde..13d02f17 100644 --- a/neural_lam/metrics.py +++ b/neural_lam/metrics.py @@ -17,9 +17,9 @@ def get_metric(metric_name): Function implementing the requested metric. """ metric_name_lower = metric_name.lower() - assert ( - metric_name_lower in DEFINED_METRICS - ), f"Unknown metric: {metric_name}" + assert metric_name_lower in DEFINED_METRICS, ( + f"Unknown metric: {metric_name}" + ) return DEFINED_METRICS[metric_name_lower] diff --git a/neural_lam/models/step_predictors/base.py b/neural_lam/models/step_predictors/base.py index 8e9b4fc4..b5ccd14b 100644 --- a/neural_lam/models/step_predictors/base.py +++ b/neural_lam/models/step_predictors/base.py @@ -177,10 +177,10 @@ def prepare_clamping_params(self, datastore: BaseDatastore): sigmoid_center = 0 softplus_center = 0 - normalize_clamping_lim = ( - lambda x, feature_idx: (x - self.state_mean[feature_idx]) - / self.state_std[feature_idx] - ) + def normalize_clamping_lim(x, feature_idx): + return (x - self.state_mean[feature_idx]) / self.state_std[ + feature_idx + ] # Check which clamping functions to use for each feature sigmoid_lower_upper_idx = [] @@ -195,11 +195,11 @@ def prepare_clamping_params(self, datastore: BaseDatastore): for feature_idx, feature in enumerate(state_feature_names): if feature in lower_lims and feature in upper_lims: - assert ( - lower_lims[feature] < upper_lims[feature] - ), f'Invalid clamping limits for feature "{feature}",\ + assert lower_lims[feature] < upper_lims[feature], ( + f'Invalid clamping limits for feature "{feature}",\ lower: {lower_lims[feature]}, larger than\ upper: {upper_lims[feature]}' + ) sigmoid_lower_upper_idx.append(feature_idx) sigmoid_lower_lims.append( normalize_clamping_lim(lower_lims[feature], feature_idx) diff --git a/neural_lam/models/step_predictors/graph/graph_lam.py b/neural_lam/models/step_predictors/graph/graph_lam.py index 48fa4296..afd346c9 100644 --- a/neural_lam/models/step_predictors/graph/graph_lam.py +++ b/neural_lam/models/step_predictors/graph/graph_lam.py @@ -51,9 +51,9 @@ def __init__( m2g_gnn_type=m2g_gnn_type, ) - assert ( - not self.hierarchical - ), "GraphLAM does not use a hierarchical mesh graph" + assert not self.hierarchical, ( + "GraphLAM does not use a hierarchical mesh graph" + ) # grid_dim from data + static + batch_static mesh_dim = self.mesh_static_features.shape[1] diff --git a/neural_lam/utils.py b/neural_lam/utils.py index 176c9f3b..db9c7aae 100644 --- a/neural_lam/utils.py +++ b/neural_lam/utils.py @@ -297,12 +297,12 @@ def loads_file(fn: str) -> Any: m2g_features = m2g_features / longest_edge # Some checks for consistency - assert ( - len(m2m_features) == n_levels - ), "Inconsistent number of levels in mesh" - assert ( - len(mesh_static_features) == n_levels - ), "Inconsistent number of levels in mesh" + assert len(m2m_features) == n_levels, ( + "Inconsistent number of levels in mesh" + ) + assert len(mesh_static_features) == n_levels, ( + "Inconsistent number of levels in mesh" + ) if hierarchical: # Load up and down edges and features diff --git a/pyproject.toml b/pyproject.toml index bccec082..b8bfded7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,132 +1,119 @@ +[build-system] +build-backend = "hatchling.build" +requires = [ "hatch-vcs", "hatchling>=1.27" ] + [project] name = "neural-lam" -dynamic = ["version"] description = "LAM-based data-driven forecasting" -authors = [ - { name = "Joel Oskarsson", email = "joel.oskarsson@outlook.com" }, - { name = "Simon Adamov", email = "Simon.Adamov@meteoswiss.ch" }, - { name = "Leif Denby", email = "lcd@dmi.dk" }, - { name = "Simon Kamuk Christiansen", email = "skc@dmi.dk" }, - { name = "Kasper Hintz", email = "kah@dmi.dk" }, - { name = "Erik Larsson", email = "erik.larsson@liu.se" }, - { name = "Hauke Schulz", email = "has@dmi.dk" }, - { name = "Daniel Holmberg", email = "daniel.holmberg@helsinki.fi" }, -] readme = "README.md" license = { text = "MIT" } - +authors = [ + { name = "Joel Oskarsson", email = "joel.oskarsson@outlook.com" }, + { name = "Simon Adamov", email = "Simon.Adamov@meteoswiss.ch" }, + { name = "Leif Denby", email = "lcd@dmi.dk" }, + { name = "Simon Kamuk Christiansen", email = "skc@dmi.dk" }, + { name = "Kasper Hintz", email = "kah@dmi.dk" }, + { name = "Erik Larsson", email = "erik.larsson@liu.se" }, + { name = "Hauke Schulz", email = "has@dmi.dk" }, + { name = "Daniel Holmberg", email = "daniel.holmberg@helsinki.fi" }, +] +requires-python = ">=3.10" +classifiers = [ + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] +dynamic = [ "version" ] # PEP 621 project metadata # See https://www.python.org/dev/peps/pep-0621/ dependencies = [ - "numpy>=1.24.2", - "wandb>=0.13.10", - "scipy>=1.10.0", - "pytorch-lightning>=2.0.3", - "shapely>=2.0.1", - "networkx>=3.0", - "Cartopy>=0.22.0", - "pyproj>=3.4.1", - "tueplots>=0.0.8", - "matplotlib>=3.7.0", - "plotly>=5.15.0", - "torch>=2.3.0", - "torch-geometric==2.3.1", - "parse>=1.20.2", - "dataclass-wizard<0.31.0", - "mllam-data-prep>=0.5.0", - "mlflow>=2.16.2", - "boto3>=1.35.32", - "nvidia-ml-py>=13.580.82", - "pillow>=9.0.0", + "boto3>=1.35.32", + "cartopy>=0.22", + "dataclass-wizard<0.31", + "matplotlib>=3.7", + "mlflow>=2.16.2", + "mllam-data-prep>=0.5", + "networkx>=3", + "numpy>=1.24.2", + "nvidia-ml-py>=13.580.82", + "parse>=1.20.2", + "pillow>=9", + "plotly>=5.15", + "pyproj>=3.4.1", + "pytorch-lightning>=2.0.3", + "scipy>=1.10", + "shapely>=2.0.1", + "torch>=2.3", + "torch-geometric==2.3.1", + "tueplots>=0.0.8", + "wandb>=0.13.10", ] -requires-python = ">=3.10" [dependency-groups] -dev = ["pre-commit>=3.8.0", "pytest>=8.3.2", "pooch>=1.8.2"] - -[tool.black] -line-length = 80 +dev = [ "pooch>=1.8.2", "pre-commit>=3.8", "pytest>=8.3.2" ] -[tool.isort] -default_section = "THIRDPARTY" # codespell:ignore -profile = "black" -# Headings -import_heading_stdlib = "Standard library" -import_heading_thirdparty = "Third-party" -import_heading_firstparty = "First-party" -import_heading_localfolder = "Local" -# Known modules to avoid misclassification -known_standard_library = [ - # Add standard library modules that may be misclassified by isort -] -known_third_party = [ - # Add third-party modules that may be misclassified by isort - "wandb", +[tool.hatch] +build.targets.sdist.exclude = [ + ".venv/", + "venv/", ] -known_first_party = [ - # Add first-party modules that may be misclassified by isort - "neural_lam", +build.targets.wheel.exclude = [ + ".venv/", + "venv/", ] -line_length = 80 +metadata.core-metadata-version = "2.4" +version.source = "vcs" +version.fallback-version = "0.0.0" -[tool.flake8] -max-line-length = 80 -ignore = [ - "E203", # Allow whitespace before ':' (https://github.com/PyCQA/pycodestyle/issues/373) - "I002", # Don't check for isort configuration - "W503", # Allow line break before binary operator (PEP 8-compatible) -] -per-file-ignores = [ - "__init__.py: F401", # Allow unused imports +[tool.ruff] +target-version = "py310" +line-length = 80 +# Avoid notebook churn from lint-only modernization. +extend-exclude = [ "docs/notebooks/*.ipynb" ] +lint.select = [ "E", "F", "G", "I", "PT", "PTH", "S", "TCH", "UP", "W" ] +lint.ignore = [ + "E203", # Keep Black-compatible whitespace behaviour + "PT006", # Existing parameterize style to be tightened incrementally + "PT007", # Existing parameterize value style to be tightened incrementally + "PTH103", # pathlib migration will be done incrementally + "PTH118", # pathlib migration will be done incrementally + "PTH123", # pathlib migration will be done incrementally + "S101", # Allow assert usage for now (extensive in tests and checks) + "S311", # Pseudo-random usage is non-cryptographic in this codebase + "S603", # subprocess calls are currently intentional and reviewed manually + "S607", # Allow partial executable paths in controlled environments + "UP006", # Typing modernization deferred to follow-up cleanup + "UP007", # Typing modernization deferred to follow-up cleanup + "UP022", # Redirect modernisation deferred to follow-up cleanup + "UP035", # Deprecated import migration deferred to follow-up cleanup + "UP045", # Optional/Union modernization deferred to follow-up cleanup ] +lint.per-file-ignores."__init__.py" = [ "F401" ] +lint.isort.import-heading = { first-party = "First-party", local-folder = "Local", standard-library = "Standard library", third-party = "Third-party" } +lint.isort.known-first-party = [ "neural_lam", "tests" ] +lint.isort.known-third-party = [ "wandb" ] -[tool.codespell] -skip = "requirements/*" - -# Pylint config [tool.pylint] ignore = [ - "create_graph.py", # Disable linting for now, as major rework is planned/expected + "create_graph.py", # Disable linting for now, as major rework is planned/expected ] # Temporary fix for import neural_lam statements until set up as proper package init-hook = 'import sys; sys.path.append(".")' -[tool.pylint.TYPECHECK] -generated-members = ["numpy.*", "torch.*"] -[tool.pylint.'MESSAGES CONTROL'] -disable = [ - "C0114", # 'missing-module-docstring', Do not require module docstrings - "R0901", # 'too-many-ancestors', Allow many layers of sub-classing - "R0902", # 'too-many-instance-attribtes', Allow many attributes - "R0913", # 'too-many-arguments', Allow many function arguments - "R0914", # 'too-many-locals', Allow many local variables - "W0223", # 'abstract-method', Subclasses do not have to override all abstract methods +"MESSAGES CONTROL".disable = [ + "C0114", # 'missing-module-docstring', Do not require module docstrings + "R0901", # 'too-many-ancestors', Allow many layers of sub-classing + "R0902", # 'too-many-instance-attribtes', Allow many attributes + "R0913", # 'too-many-arguments', Allow many function arguments + "R0914", # 'too-many-locals', Allow many local variables + "W0223", # 'abstract-method', Subclasses do not have to override all abstract methods ] -[tool.pylint.DESIGN] -max-statements = 100 # Allow for some more involved functions -[tool.pylint.IMPORTS] -allow-any-import-level = "neural_lam" -[tool.pylint.SIMILARITIES] -min-similarity-lines = 10 - -[build-system] -requires = ["hatchling>=1.27.0", "hatch-vcs"] -build-backend = "hatchling.build" - -[tool.hatch.metadata] -core-metadata-version = "2.4" - -[tool.hatch.version] -source = "vcs" -fallback-version = "0.0.0" +DESIGN.max-statements = 100 # Allow for some more involved functions +IMPORTS.allow-any-import-level = "neural_lam" +SIMILARITIES.min-similarity-lines = 10 +TYPECHECK.generated-members = [ "numpy.*", "torch.*" ] -[tool.hatch.build.targets.sdist] -exclude = [ - ".venv/", - "venv/", -] - -[tool.hatch.build.targets.wheel] -exclude = [ - ".venv/", - "venv/", -] +[tool.codespell] +skip = "requirements/*" diff --git a/tests/conftest.py b/tests/conftest.py index 47237ed5..fca845aa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -57,7 +57,7 @@ def download_meps_example_reduced_dataset(): config_path = dataset_path / "meps_example_reduced.datastore.yaml" - with open(config_path, "r") as f: + with open(config_path) as f: config = yaml.safe_load(f) if "class" in config["projection"]: diff --git a/tests/dummy_datastore.py b/tests/dummy_datastore.py index 3a844d6d..c3c5c04a 100644 --- a/tests/dummy_datastore.py +++ b/tests/dummy_datastore.py @@ -59,17 +59,17 @@ def __init__( step_length : timedelta, optional The step length between timesteps. Defaults to timedelta(hours=1). """ - assert ( - config_path is None - ), "No config file is needed for the dummy datastore" + assert config_path is None, ( + "No config file is needed for the dummy datastore" + ) self._step_length = step_length or timedelta(hours=1) # Ensure n_grid_points is a perfect square n_points_1d = int(np.sqrt(n_grid_points)) - assert ( - n_points_1d * n_points_1d == n_grid_points - ), "n_grid_points must be a perfect square" + assert n_points_1d * n_points_1d == n_grid_points, ( + "n_grid_points must be a perfect square" + ) # create equal area grid lx, ly = self.bbox_size_km @@ -497,7 +497,7 @@ def __init__( self.is_forecast = is_forecast self._forcing_has_ensemble = forcing_has_ensemble self._step_length = timedelta(hours=1) - self._root_path = Path(".") + self._root_path = Path() self._state_feature = np.array(["state_feat_0"], dtype=object) self._forcing_feature = np.array(["forcing_feat_0"], dtype=object) diff --git a/tests/test_datasets.py b/tests/test_datasets.py index 6110dc8e..db15599f 100644 --- a/tests/test_datasets.py +++ b/tests/test_datasets.py @@ -166,9 +166,7 @@ def test_single_batch(datastore_name, split): """ datastore = init_datastore_example(datastore_name) - device_name = ( - torch.device("cuda") if torch.cuda.is_available() else "cpu" - ) # noqa + device_name = torch.device("cuda") if torch.cuda.is_available() else "cpu" graph_name = "1level" diff --git a/tests/test_gnn_layers.py b/tests/test_gnn_layers.py index 166a9b54..43aa426b 100644 --- a/tests/test_gnn_layers.py +++ b/tests/test_gnn_layers.py @@ -216,8 +216,10 @@ def test_output_shapes_match_interaction_net(self): p_out = pnet(send_rep, rec_rep, edge_rep) if update_edges: - assert isinstance(i_out, tuple) and len(i_out) == 2 - assert isinstance(p_out, tuple) and len(p_out) == 2 + assert isinstance(i_out, tuple) + assert len(i_out) == 2 + assert isinstance(p_out, tuple) + assert len(p_out) == 2 assert i_out[0].shape == p_out[0].shape == (n_rec, d_h) assert i_out[1].shape == p_out[1].shape == (n_edges, d_h) else: @@ -338,7 +340,8 @@ def test_update_edges_true_returns_tuple(self): edge_rep = torch.randn(n_edges, d_h) result = pnet(send_rep, rec_rep, edge_rep) - assert isinstance(result, tuple) and len(result) == 2 + assert isinstance(result, tuple) + assert len(result) == 2 assert result[0].shape == (n_rec, d_h) assert result[1].shape == (n_edges, d_h) @@ -711,12 +714,12 @@ def test_deep_stacking(self): send_rep, current_rec, current_edge ) - assert torch.isfinite( - current_rec - ).all(), "Receiver reps contain non-finite values after deep stacking" - assert torch.isfinite( - current_edge - ).all(), "Edge reps contain non-finite values after deep stacking" + assert torch.isfinite(current_rec).all(), ( + "Receiver reps contain non-finite values after deep stacking" + ) + assert torch.isfinite(current_edge).all(), ( + "Edge reps contain non-finite values after deep stacking" + ) def test_high_degree_stability(self): """With many incoming edges per receiver, mean aggregation should diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 616d563d..abbb5306 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -518,12 +518,12 @@ def test_plot_examples_integration_saves_figure( model.plotted_examples = 0 # Verify that the model correctly inferred time step from the datastore. - assert ( - model.time_step_int == time_step - ), f"Expected time_step_int={time_step}, got {model.time_step_int}" - assert ( - model.time_step_unit == time_unit - ), f"Expected time_step_unit={time_unit}, got {model.time_step_unit}" + assert model.time_step_int == time_step, ( + f"Expected time_step_int={time_step}, got {model.time_step_int}" + ) + assert model.time_step_unit == time_unit, ( + f"Expected time_step_unit={time_unit}, got {model.time_step_unit}" + ) # Generate prediction (init_states, target, forcing_features, _batch_times) = batch @@ -745,9 +745,9 @@ def test_create_metric_log_dict_with_metrics_watch(tmp_path): # Verify figure entries are plt.Figure and scalar entries are tensors for key, value in log_dict.items(): - assert isinstance( - value, (plt.Figure, torch.Tensor) - ), f"Unexpected value type for key '{key}': {type(value)}" + assert isinstance(value, (plt.Figure, torch.Tensor)), ( + f"Unexpected value type for key '{key}': {type(value)}" + ) plt.close("all") diff --git a/tests/test_train_model_warnings.py b/tests/test_train_model_warnings.py index a0b5f92a..08fe5d42 100644 --- a/tests/test_train_model_warnings.py +++ b/tests/test_train_model_warnings.py @@ -83,7 +83,7 @@ def capture_init(_self, **kwargs): ): main() - assert ( - "create_gif" in captured_kwargs - ), "create_gif was not forwarded to ForecasterModule" + assert "create_gif" in captured_kwargs, ( + "create_gif was not forwarded to ForecasterModule" + ) assert captured_kwargs["create_gif"] is True