diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 5fdec1e11e..c60925699f 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -22,7 +22,7 @@ jobs: # - docker-py3-pip- (shared) # - ubuntu py37 pip- # - os-latest-pip- (shared) - flake8-py3: + lint-py3: runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/weekly-preview.yml b/.github/workflows/weekly-preview.yml index 9f63c5bc90..caab3eb8ce 100644 --- a/.github/workflows/weekly-preview.yml +++ b/.github/workflows/weekly-preview.yml @@ -5,7 +5,7 @@ on: - cron: "0 2 * * 0" # 02:00 of every Sunday jobs: - flake8-py3: + lint-py3: runs-on: ubuntu-latest strategy: matrix: diff --git a/monai/apps/auto3dseg/bundle_gen.py b/monai/apps/auto3dseg/bundle_gen.py index 87a612cae2..d053ea37ef 100644 --- a/monai/apps/auto3dseg/bundle_gen.py +++ b/monai/apps/auto3dseg/bundle_gen.py @@ -208,7 +208,7 @@ def _create_cmd(self, train_params: None | dict = None) -> tuple[str, str]: config_files = [] if os.path.isdir(config_dir): for file in sorted(os.listdir(config_dir)): - if file.endswith("yaml") or file.endswith("json"): + if file.endswith(("yaml", "json")): # Python Fire may be confused by single-quoted WindowsPath config_files.append(Path(os.path.join(config_dir, file)).as_posix()) diff --git a/monai/apps/detection/transforms/box_ops.py b/monai/apps/detection/transforms/box_ops.py index 404854c4c0..6e08a88e59 100644 --- a/monai/apps/detection/transforms/box_ops.py +++ b/monai/apps/detection/transforms/box_ops.py @@ -179,7 +179,7 @@ def flip_boxes( spatial_dims: int = get_spatial_dims(boxes=boxes) spatial_size = ensure_tuple_rep(spatial_size, spatial_dims) if flip_axes is None: - flip_axes = tuple(range(0, spatial_dims)) + flip_axes = tuple(range(spatial_dims)) flip_axes = ensure_tuple(flip_axes) # flip box diff --git a/monai/auto3dseg/algo_gen.py b/monai/auto3dseg/algo_gen.py index 38ac542cff..5ad15c7d7a 100644 --- a/monai/auto3dseg/algo_gen.py +++ b/monai/auto3dseg/algo_gen.py @@ -25,23 +25,18 @@ class Algo: def set_data_stats(self, *args, **kwargs): """Provide dataset (and summaries) so that the model creation can depend on the input datasets.""" - pass def train(self, *args, **kwargs): """Read training/validation data and output a model.""" - pass def predict(self, *args, **kwargs): """Read test data and output model predictions.""" - pass def get_score(self, *args, **kwargs): """Returns the model quality measurement based on training and validation datasets.""" - pass def get_output_path(self, *args, **kwargs): """Returns the algo output paths for scripts location""" - pass class AlgoGen(Randomizable): @@ -70,31 +65,24 @@ class AlgoGen(Randomizable): def set_data_stats(self, *args, **kwargs): # type ignore """Provide dataset summaries/properties so that the generator can be conditioned on the input datasets.""" - pass def set_budget(self, *args, **kwargs): """Provide computational budget so that the generator outputs algorithms that requires reasonable resources.""" - pass def set_score(self, *args, **kwargs): """Feedback from the previously generated algo, the score can be used for new Algo generations.""" - pass def get_data_stats(self, *args, **kwargs): """Get current dataset summaries.""" - pass def get_budget(self, *args, **kwargs): """Get the current computational budget.""" - pass def get_history(self, *args, **kwargs): """Get the previously generated algo.""" - pass def generate(self): """Generate new Algo -- based on data_stats, budget, and history of previous algo generations.""" - pass def run_algo(self, *args, **kwargs): """ @@ -104,4 +92,3 @@ def run_algo(self, *args, **kwargs): implemented separately is preferred to run them. In this case the controller should also report back the scores and the algo history, so that the future ``AlgoGen.generate`` can leverage the information. """ - pass diff --git a/monai/data/box_utils.py b/monai/data/box_utils.py index b09b86b605..68a0435285 100644 --- a/monai/data/box_utils.py +++ b/monai/data/box_utils.py @@ -591,7 +591,7 @@ def convert_box_mode( # check validity of corners spatial_dims = get_spatial_dims(boxes=boxes_t) - for axis in range(0, spatial_dims): + for axis in range(spatial_dims): if (corners[spatial_dims + axis] < corners[axis]).sum() > 0: warnings.warn("Given boxes has invalid values. The box size must be non-negative.") @@ -731,7 +731,7 @@ def is_valid_box_values(boxes: NdarrayOrTensor) -> bool: whether ``boxes`` is valid """ spatial_dims = get_spatial_dims(boxes=boxes) - for axis in range(0, spatial_dims): + for axis in range(spatial_dims): if (boxes[:, spatial_dims + axis] < boxes[:, axis]).sum() > 0: return False return True @@ -1041,7 +1041,7 @@ def spatial_crop_boxes( # makes sure the bounding boxes are within the patch spatial_dims = get_spatial_dims(boxes=boxes, spatial_size=roi_end) - for axis in range(0, spatial_dims): + for axis in range(spatial_dims): boxes_t[:, axis] = boxes_t[:, axis].clamp(min=roi_start_t[axis], max=roi_end_t[axis] - TO_REMOVE) boxes_t[:, axis + spatial_dims] = boxes_t[:, axis + spatial_dims].clamp( min=roi_start_t[axis], max=roi_end_t[axis] - TO_REMOVE @@ -1133,7 +1133,7 @@ def non_max_suppression( # initialize the list of picked indexes pick = [] - idxs = torch.Tensor(list(range(0, boxes_sort.shape[0]))).to(device=boxes_t.device, dtype=torch.long) + idxs = torch.Tensor(list(range(boxes_sort.shape[0]))).to(device=boxes_t.device, dtype=torch.long) # keep looping while some indexes still remain in the indexes list while len(idxs) > 0: diff --git a/monai/fl/client/client_algo.py b/monai/fl/client/client_algo.py index 3dc9f5785d..4b60668b07 100644 --- a/monai/fl/client/client_algo.py +++ b/monai/fl/client/client_algo.py @@ -34,7 +34,6 @@ def initialize(self, extra: dict | None = None) -> None: Args: extra: optional extra information, e.g. dict of `ExtraItems.CLIENT_NAME` and/or `ExtraItems.APP_ROOT`. """ - pass def finalize(self, extra: dict | None = None) -> None: """ @@ -43,7 +42,6 @@ def finalize(self, extra: dict | None = None) -> None: Args: extra: Dict with additional information that can be provided by the FL system. """ - pass def abort(self, extra: dict | None = None) -> None: """ @@ -53,8 +51,6 @@ def abort(self, extra: dict | None = None) -> None: extra: Dict with additional information that can be provided by the FL system. """ - pass - class ClientAlgoStats(BaseClient): diff --git a/monai/metrics/loss_metric.py b/monai/metrics/loss_metric.py index 3136f42f4a..3613576a7d 100644 --- a/monai/metrics/loss_metric.py +++ b/monai/metrics/loss_metric.py @@ -16,10 +16,10 @@ import torch from torch.nn.modules.loss import _Loss +from monai.config import TensorOrList from monai.metrics.utils import do_metric_reduction from monai.utils import MetricReduction -from ..config import TensorOrList from .metric import CumulativeIterationMetric diff --git a/monai/networks/blocks/feature_pyramid_network.py b/monai/networks/blocks/feature_pyramid_network.py index 759a4efe0d..96083e7c0d 100644 --- a/monai/networks/blocks/feature_pyramid_network.py +++ b/monai/networks/blocks/feature_pyramid_network.py @@ -85,7 +85,6 @@ def forward(self, results: list[Tensor], x: list[Tensor], names: list[str]): - the extended set of results of the FPN - the extended set of names for the results """ - pass class LastLevelMaxPool(ExtraFPNBlock): diff --git a/monai/networks/nets/dints.py b/monai/networks/nets/dints.py index 18e24373a0..f87fcebea8 100644 --- a/monai/networks/nets/dints.py +++ b/monai/networks/nets/dints.py @@ -627,7 +627,6 @@ def __init__( def forward(self, x): """This function to be implemented by the architecture instances or search spaces.""" - pass class TopologyInstance(TopologyConstruction): diff --git a/monai/networks/trt_compiler.py b/monai/networks/trt_compiler.py index 2df7189ad4..d54b61d89b 100644 --- a/monai/networks/trt_compiler.py +++ b/monai/networks/trt_compiler.py @@ -98,8 +98,6 @@ class ShapeError(Exception): Exception class to report errors from setting TRT plan input shapes """ - pass - class TRTEngine: """ diff --git a/monai/transforms/regularization/array.py b/monai/transforms/regularization/array.py index 445a9340f2..6b979e564a 100644 --- a/monai/transforms/regularization/array.py +++ b/monai/transforms/regularization/array.py @@ -17,10 +17,9 @@ import torch from monai.data.meta_obj import get_track_meta +from monai.transforms.transform import RandomizableTransform from monai.utils.type_conversion import convert_to_dst_type, convert_to_tensor -from ..transform import RandomizableTransform - __all__ = ["MixUp", "CutMix", "CutOut", "Mixer"] diff --git a/monai/transforms/regularization/dictionary.py b/monai/transforms/regularization/dictionary.py index d8815e47b9..e3e2c4a150 100644 --- a/monai/transforms/regularization/dictionary.py +++ b/monai/transforms/regularization/dictionary.py @@ -18,10 +18,10 @@ from monai.config import KeysCollection from monai.config.type_definitions import NdarrayOrTensor from monai.data.meta_obj import get_track_meta +from monai.transforms.transform import MapTransform, RandomizableTransform from monai.utils import convert_to_tensor from monai.utils.misc import ensure_tuple -from ..transform import MapTransform, RandomizableTransform from .array import CutMix, CutOut, MixUp __all__ = ["MixUpd", "MixUpD", "MixUpDict", "CutMixd", "CutMixD", "CutMixDict", "CutOutd", "CutOutD", "CutOutDict"] diff --git a/monai/transforms/traits.py b/monai/transforms/traits.py index 45d081f2e6..33ff52015b 100644 --- a/monai/transforms/traits.py +++ b/monai/transforms/traits.py @@ -74,8 +74,6 @@ class RandomizableTrait: implementors of MONAI transforms. """ - pass - class MultiSampleTrait: """ @@ -85,8 +83,6 @@ class MultiSampleTrait: of MONAI transforms. """ - pass - class ThreadUnsafe: """ @@ -98,8 +94,6 @@ class ThreadUnsafe: its extensions, where the transform cache is built with multiple threads. """ - pass - class ReduceTrait: """ @@ -108,5 +102,3 @@ class ReduceTrait: This interface can be extended from by people adapting transforms to the MONAI framework as well as by implementors of MONAI transforms. """ - - pass diff --git a/monai/utils/deprecate_utils.py b/monai/utils/deprecate_utils.py index d4f239cd23..1249c51919 100644 --- a/monai/utils/deprecate_utils.py +++ b/monai/utils/deprecate_utils.py @@ -19,10 +19,9 @@ from types import FunctionType from typing import Any, TypeVar +from monai import __version__ from monai.utils.module import version_leq -from .. import __version__ - __all__ = ["deprecated", "deprecated_arg", "DeprecatedError", "deprecated_arg_default"] T = TypeVar("T", type, Callable) @@ -201,7 +200,7 @@ def _wrapper(*args, **kwargs): # if name is specified and new_name is not specified kwargs[new_name] = kwargs[name] try: - sig.bind(*args, **kwargs).arguments + _ = sig.bind(*args, **kwargs).arguments except TypeError: # multiple values for new_name using both args and kwargs kwargs.pop(new_name, None) diff --git a/monai/utils/profiling.py b/monai/utils/profiling.py index 5c880bbe1f..5eda00459e 100644 --- a/monai/utils/profiling.py +++ b/monai/utils/profiling.py @@ -337,7 +337,7 @@ def profile_iter(self, name, iterable): class _Iterable: - def __iter__(_self): # noqa: B902, N805 pylint: disable=E0213 + def __iter__(_self): # noqa: N805 pylint: disable=E0213 do_iter = True orig_iter = iter(iterable) caller = getframeinfo(stack()[1][0]) diff --git a/pyproject.toml b/pyproject.toml index f9fab1141a..4af0ab4a75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,12 +39,19 @@ all = true exclude = "monai/bundle/__main__.py" [tool.ruff] -line-length = 133 +line-length = 120 target-version = "py39" [tool.ruff.lint] select = [ - "E", "F", "W", # flake8 + "B", # flake8-bugbear - https://docs.astral.sh/ruff/rules/#flake8-bugbear-b + "C90", # mccabe (complexity) - https://docs.astral.sh/ruff/rules/#mccabe-c90 + "E", # pycodestyle errors - https://docs.astral.sh/ruff/rules/#error-e + "F", # pyflakes - https://docs.astral.sh/ruff/rules/#pyflakes-f + "N", # pep8-naming - https://docs.astral.sh/ruff/rules/#pep8-naming-n + "PIE", # flake8-pie - https://docs.astral.sh/ruff/rules/#flake8-pie-pie + "TID", # flake8-tidy-imports - https://docs.astral.sh/ruff/rules/#flake8-tidy-imports-tid + "W", # pycodestyle warnings - https://docs.astral.sh/ruff/rules/#warning-w "NPY", # NumPy specific rules "UP", # pyupgrade # "RUF100", # aka yesqa @@ -53,7 +60,28 @@ extend-ignore = [ "E741", # ambiguous variable name "F401", # unused import "NPY002", # numpy-legacy-random + "E203", # whitespace before ':' (pycodestyle) + "E501", # line too long (pycodestyle) + "C408", # unnecessary collection call (flake8-comprehensions) + "N812", # lowercase imported as non lowercase (pep8-naming) + "B023", # function uses loop variable (flake8-bugbear) + "B905", # zip() without an explicit strict= parameter (flake8-bugbear) + "B028", # no explicit stacklevel keyword argument found (flake8-bugbear) +] + +[tool.ruff.lint.per-file-ignores] +"tests/**" = [ + "B018", + "C901", + "N999", + "N801" ] +"monai/apps/detection/utils/ATSS_matcher.py" = [ + "N999" +] + +[tool.ruff.lint.mccabe] +max-complexity = 50 # todo lower this treshold when yesqa id replaced with Ruff's RUF100 [tool.pytype] # Space-separated list of files or directories to exclude. diff --git a/requirements-dev.txt b/requirements-dev.txt index 1dc2141cf6..5faf8a7532 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,9 +10,6 @@ tensorboard>=2.12.0 # https://github.com/Project-MONAI/MONAI/issues/7434 scikit-image>=0.19.0 tqdm>=4.47.0 lmdb -flake8>=3.8.1 -flake8-bugbear<=24.2.6 # https://github.com/Project-MONAI/MONAI/issues/7690 -flake8-comprehensions mccabe pep8-naming pycodestyle diff --git a/runtests.sh b/runtests.sh index 18cb0ab73a..eb7a2d6ce0 100755 --- a/runtests.sh +++ b/runtests.sh @@ -43,7 +43,6 @@ doBlackFormat=false doBlackFix=false doIsortFormat=false doIsortFix=false -doFlake8Format=false doPylintFormat=false doRuffFormat=false doRuffFix=false @@ -60,7 +59,7 @@ NUM_PARALLEL=1 PY_EXE=${MONAI_PY_EXE:-$(which python)} function print_usage { - echo "runtests.sh [--codeformat] [--autofix] [--black] [--isort] [--flake8] [--pylint] [--ruff]" + echo "runtests.sh [--codeformat] [--autofix] [--black] [--isort] [--pylint] [--ruff]" echo " [--clangformat] [--precommit] [--pytype] [-j number] [--mypy]" echo " [--unittests] [--disttests] [--coverage] [--quick] [--min] [--net] [--build] [--list_tests]" echo " [--dryrun] [--copyright] [--clean] [--help] [--version] [--path] [--formatfix]" @@ -80,7 +79,6 @@ function print_usage { echo " --autofix : format code using \"isort\" and \"black\"" echo " --black : perform \"black\" code format checks" echo " --isort : perform \"isort\" import sort checks" - echo " --flake8 : perform \"flake8\" code format checks" echo " --pylint : perform \"pylint\" code format checks" echo " --ruff : perform \"ruff\" code format checks" echo " --clangformat : format csrc code using \"clang-format\"" @@ -267,7 +265,6 @@ do -f|--codeformat) doBlackFormat=true doIsortFormat=true - doFlake8Format=true # doPylintFormat=true # https://github.com/Project-MONAI/MONAI/issues/7094 doRuffFormat=true doCopyRight=true @@ -299,9 +296,6 @@ do --isort) doIsortFormat=true ;; - --flake8) - doFlake8Format=true - ;; --pylint) doPylintFormat=true ;; @@ -533,32 +527,6 @@ then set -e # enable exit on failure fi - -if [ $doFlake8Format = true ] -then - set +e # disable exit on failure so that diagnostics can be given on failure - echo "${separator}${blue}flake8${noColor}" - - # ensure that the necessary packages for code format testing are installed - if ! is_pip_installed flake8 - then - install_deps - fi - ${cmdPrefix}"${PY_EXE}" -m flake8 --version - - ${cmdPrefix}"${PY_EXE}" -m flake8 "$homedir" --count --statistics - - flake8_status=$? - if [ ${flake8_status} -ne 0 ] - then - print_style_fail_msg - exit ${flake8_status} - else - echo "${green}passed!${noColor}" - fi - set -e # enable exit on failure -fi - if [ $doPylintFormat = true ] then set +e # disable exit on failure so that diagnostics can be given on failure diff --git a/setup.cfg b/setup.cfg index ab03b906c1..59fab0b54d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -179,36 +179,6 @@ pyamg = # segment-anything = # segment-anything @ git+https://github.com/facebookresearch/segment-anything@6fdee8f2727f4506cfbbe553e23b895e27956588#egg=segment-anything -[flake8] -select = B,C,E,F,N,P,T4,W,B9 -max_line_length = 120 -# C408 ignored because we like the dict keyword argument syntax -# E501 is not flexible enough, we're using B950 instead -# N812 lowercase 'torch.nn.functional' imported as non lowercase 'F' -# B023 https://github.com/Project-MONAI/MONAI/issues/4627 -# B028 https://github.com/Project-MONAI/MONAI/issues/5855 -# B907 https://github.com/Project-MONAI/MONAI/issues/5868 -# B908 https://github.com/Project-MONAI/MONAI/issues/6503 -# B036 https://github.com/Project-MONAI/MONAI/issues/7396 -# E704 https://github.com/Project-MONAI/MONAI/issues/7421 -ignore = - E203 - E501 - E741 - W503 - W504 - C408 - N812 - B023 - B905 - B028 - B907 - B908 - B036 - E704 -per_file_ignores = __init__.py: F401, __main__.py: F401 -exclude = *.pyi,.git,.eggs,monai/_version.py,versioneer.py,venv,.venv,_version.py - [isort] known_first_party = monai profile = black diff --git a/tests/handlers/test_handler_regression_metrics.py b/tests/handlers/test_handler_regression_metrics.py index a3ec9f071a..fdbc82090d 100644 --- a/tests/handlers/test_handler_regression_metrics.py +++ b/tests/handlers/test_handler_regression_metrics.py @@ -70,7 +70,7 @@ def test_compute(self): for batch in batch_dims: for spatial in spatial_dims: for base in base_dims: - mt_fn_obj = mt_fn(**{"save_details": False}) + mt_fn_obj = mt_fn(save_details=False) # create random tensor in_tensor_a1 = torch.rand((batch,) + (base,) * (spatial - 1)).to(device) diff --git a/tests/integration/test_integration_classification_2d.py b/tests/integration/test_integration_classification_2d.py index 88351292b0..c20e4593f3 100644 --- a/tests/integration/test_integration_classification_2d.py +++ b/tests/integration/test_integration_classification_2d.py @@ -236,7 +236,6 @@ def tearDown(self): os.remove(os.path.join(self.data_dir, "best_metric_model.pth")) except FileNotFoundError: warnings.warn("not found best_metric_model.pth, training skipped?") - pass def train_and_infer(self, idx=0): results = [] diff --git a/tests/utils/type_conversion/test_convert_data_type.py b/tests/utils/type_conversion/test_convert_data_type.py index 4c1905b188..08995034d0 100644 --- a/tests/utils/type_conversion/test_convert_data_type.py +++ b/tests/utils/type_conversion/test_convert_data_type.py @@ -74,7 +74,6 @@ class TestTensor(torch.Tensor): __test__ = False # indicate to pytest that this class is not intended for collection - pass class TestConvertDataType(unittest.TestCase):