diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b620a6de3..f32a1a1a73 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,18 +12,13 @@ repos: - id: check-case-conflict - id: check-merge-conflict - id: check-vcs-permalinks - - repo: https://github.com/grantjenks/blue - rev: v0.9.1 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.9 hooks: - - id: blue - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 - hooks: - - id: flake8 + - id: ruff + args: [--fix, --show-fix, --exit-non-zero-on-fix] + exclude: "^(doc|nisext|tools)/" + - id: ruff-format exclude: "^(doc|nisext|tools)/" - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.5.1 diff --git a/nibabel/benchmarks/bench_arrayproxy_slicing.py b/nibabel/benchmarks/bench_arrayproxy_slicing.py index dc9acfdedd..b96cc8b8f6 100644 --- a/nibabel/benchmarks/bench_arrayproxy_slicing.py +++ b/nibabel/benchmarks/bench_arrayproxy_slicing.py @@ -56,7 +56,6 @@ def bench_arrayproxy_slicing(): - print_git_title('\nArrayProxy gzip slicing') # each test is a tuple containing @@ -100,7 +99,6 @@ def fmt_sliceobj(sliceobj): return f"[{', '.join(slcstr)}]" with InTemporaryDirectory(): - print(f'Generating test data... ({int(round(np.prod(SHAPE) * 4 / 1048576.0))} MB)') data = np.array(np.random.random(SHAPE), dtype=np.float32) @@ -128,7 +126,6 @@ def fmt_sliceobj(sliceobj): seeds = [np.random.randint(0, 2**32) for s in SLICEOBJS] for ti, test in enumerate(tests): - label = get_test_label(test) have_igzip, keep_open, sliceobj = test seed = seeds[SLICEOBJS.index(sliceobj)] diff --git a/nibabel/cmdline/diff.py b/nibabel/cmdline/diff.py index b409c7205d..d20a105e76 100755 --- a/nibabel/cmdline/diff.py +++ b/nibabel/cmdline/diff.py @@ -231,7 +231,6 @@ def get_data_diff(files, max_abs=0, max_rel=0, dtype=np.float64): diffs1 = [None] * (i + 1) for j, d2 in enumerate(data[i + 1 :], i + 1): - if d1.shape == d2.shape: abs_diff = np.abs(d1 - d2) mean_abs = (np.abs(d1) + np.abs(d2)) * 0.5 @@ -255,7 +254,6 @@ def get_data_diff(files, max_abs=0, max_rel=0, dtype=np.float64): max_rel_diff = 0 if np.any(candidates): - diff_rec = OrderedDict() # so that abs goes before relative diff_rec['abs'] = max_abs_diff.astype(dtype) @@ -268,7 +266,6 @@ def get_data_diff(files, max_abs=0, max_rel=0, dtype=np.float64): diffs1.append({'CMP': 'incompat'}) if any(diffs1): - diffs['DATA(diff %d:)' % (i + 1)] = diffs1 return diffs diff --git a/nibabel/dft.py b/nibabel/dft.py index ee34595b3f..aeb8accbb5 100644 --- a/nibabel/dft.py +++ b/nibabel/dft.py @@ -161,7 +161,7 @@ def as_nifti(self): data = numpy.ndarray( (len(self.storage_instances), self.rows, self.columns), dtype=numpy.int16 ) - for (i, si) in enumerate(self.storage_instances): + for i, si in enumerate(self.storage_instances): if i + 1 != si.instance_number: raise InstanceStackError(self, i, si) logger.info('reading %d/%d' % (i + 1, len(self.storage_instances))) @@ -243,7 +243,7 @@ def dicom(self): def _get_subdirs(base_dir, files_dict=None, followlinks=False): dirs = [] - for (dirpath, dirnames, filenames) in os.walk(base_dir, followlinks=followlinks): + for dirpath, dirnames, filenames in os.walk(base_dir, followlinks=followlinks): abs_dir = os.path.realpath(dirpath) if abs_dir in dirs: raise CachingError(f'link cycle detected under {base_dir}') diff --git a/nibabel/ecat.py b/nibabel/ecat.py index 1db902d10a..85de9184b5 100644 --- a/nibabel/ecat.py +++ b/nibabel/ecat.py @@ -513,7 +513,6 @@ def read_subheaders(fileobj, mlist, endianness): class EcatSubHeader: - _subhdrdtype = subhdr_dtype _data_type_codes = data_type_codes diff --git a/nibabel/freesurfer/tests/test_mghformat.py b/nibabel/freesurfer/tests/test_mghformat.py index 189f1a9dd7..d69587811b 100644 --- a/nibabel/freesurfer/tests/test_mghformat.py +++ b/nibabel/freesurfer/tests/test_mghformat.py @@ -460,6 +460,7 @@ def test_as_byteswapped(self): for endianness in (None,) + LITTLE_CODES: with pytest.raises(ValueError): hdr.as_byteswapped(endianness) + # Note that contents is not rechecked on swap / copy class DC(self.header_class): def check_fix(self, *args, **kwargs): diff --git a/nibabel/info.py b/nibabel/info.py index a608932fa8..d7873de211 100644 --- a/nibabel/info.py +++ b/nibabel/info.py @@ -108,4 +108,4 @@ .. _Digital Object Identifier: https://en.wikipedia.org/wiki/Digital_object_identifier .. _zenodo: https://zenodo.org -""" # noqa: E501 +""" # noqa: E501 diff --git a/nibabel/openers.py b/nibabel/openers.py index 90c7774d12..d69412fb85 100644 --- a/nibabel/openers.py +++ b/nibabel/openers.py @@ -86,7 +86,6 @@ def _gzip_open( mtime: int = 0, keep_open: bool = False, ) -> gzip.GzipFile: - if not HAVE_INDEXED_GZIP or mode != 'rb': gzip_file = DeterministicGzipFile(filename, mode, compresslevel, mtime=mtime) @@ -129,6 +128,7 @@ class Opener: passed to opening method when `fileish` is str. Change of defaults as for \*args """ + gz_def = (_gzip_open, ('mode', 'compresslevel', 'mtime', 'keep_open')) bz2_def = (BZ2File, ('mode', 'buffering', 'compresslevel')) zstd_def = (_zstd_open, ('mode', 'level_or_option', 'zstd_dict')) diff --git a/nibabel/streamlines/tck.py b/nibabel/streamlines/tck.py index 43df2f87e0..358c579362 100644 --- a/nibabel/streamlines/tck.py +++ b/nibabel/streamlines/tck.py @@ -309,7 +309,6 @@ def _read_header(cls, fileobj): offset_data = 0 with Opener(fileobj) as f: - # Record start position start_position = f.tell() diff --git a/nibabel/streamlines/tests/test_tractogram.py b/nibabel/streamlines/tests/test_tractogram.py index 30294be438..58aac1d4ba 100644 --- a/nibabel/streamlines/tests/test_tractogram.py +++ b/nibabel/streamlines/tests/test_tractogram.py @@ -165,7 +165,6 @@ def setup_module(): def check_tractogram_item(tractogram_item, streamline, data_for_streamline={}, data_for_points={}): - assert_array_equal(tractogram_item.streamline, streamline) assert len(tractogram_item.data_for_streamline) == len(data_for_streamline) diff --git a/nibabel/streamlines/tests/test_tractogram_file.py b/nibabel/streamlines/tests/test_tractogram_file.py index 53a7fb662b..71e2326ecf 100644 --- a/nibabel/streamlines/tests/test_tractogram_file.py +++ b/nibabel/streamlines/tests/test_tractogram_file.py @@ -8,7 +8,6 @@ def test_subclassing_tractogram_file(): - # Missing 'save' method class DummyTractogramFile(TractogramFile): @classmethod diff --git a/nibabel/streamlines/trk.py b/nibabel/streamlines/trk.py index 966b133d1f..0b11f5684e 100644 --- a/nibabel/streamlines/trk.py +++ b/nibabel/streamlines/trk.py @@ -366,7 +366,6 @@ def _read(): tractogram = LazyTractogram.from_data_func(_read) else: - # Speed up loading by guessing a suitable buffer size. with Opener(fileobj) as f: old_file_position = f.tell() @@ -773,6 +772,4 @@ def __str__(self): swap_yz: {swap_yz} swap_zx: {swap_zx} n_count: {NB_STREAMLINES} -hdr_size: {hdr_size}""".format( - **vars - ) +hdr_size: {hdr_size}""".format(**vars) diff --git a/nibabel/tests/test_funcs.py b/nibabel/tests/test_funcs.py index 10f6e90813..5e59bc63b6 100644 --- a/nibabel/tests/test_funcs.py +++ b/nibabel/tests/test_funcs.py @@ -58,7 +58,6 @@ def test_concat(): # Loop over every possible axis, including None (explicit and implied) for axis in list(range(-(dim - 2), (dim - 1))) + [None, '__default__']: - # Allow testing default vs. passing explicit param if axis == '__default__': np_concat_kwargs = dict(axis=-1) diff --git a/nibabel/tests/test_image_types.py b/nibabel/tests/test_image_types.py index da2f93e21f..bc50c8417e 100644 --- a/nibabel/tests/test_image_types.py +++ b/nibabel/tests/test_image_types.py @@ -88,7 +88,6 @@ def check_img(img_path, img_klass, sniff_mode, sniff, expect_success, msg): irrelevant=b'a' * (sizeof_hdr - 1), # A too-small sniff, query bad_sniff=b'a' * sizeof_hdr, # Bad sniff, should fail ).items(): - for klass in img_klasses: if klass == expected_img_klass: # Class will load unless you pass a bad sniff, diff --git a/nibabel/tests/test_nifti1.py b/nibabel/tests/test_nifti1.py index c7c4d1d84b..a5b9427bc4 100644 --- a/nibabel/tests/test_nifti1.py +++ b/nibabel/tests/test_nifti1.py @@ -731,7 +731,6 @@ def unshear_44(affine): class TestNifti1SingleHeader(TestNifti1PairHeader): - header_class = Nifti1Header def test_empty(self): diff --git a/nibabel/tests/test_parrec.py b/nibabel/tests/test_parrec.py index 6035d47f8d..90227f51cd 100644 --- a/nibabel/tests/test_parrec.py +++ b/nibabel/tests/test_parrec.py @@ -884,7 +884,6 @@ def test_dualTR(): def test_ADC_map(): # test reading an apparent diffusion coefficient map with open(ADC_PAR) as fobj: - # two truncation warnings expected because general_info indicates: # 1.) multiple directions # 2.) multiple b-values diff --git a/nibabel/tests/test_spatialimages.py b/nibabel/tests/test_spatialimages.py index 5cad23a22f..d187863e37 100644 --- a/nibabel/tests/test_spatialimages.py +++ b/nibabel/tests/test_spatialimages.py @@ -400,7 +400,7 @@ def test_slicer(self): in_data_template = np.arange(240, dtype=np.int16) base_affine = np.eye(4) t_axis = None - for dshape in ((4, 5, 6, 2), (8, 5, 6)): # Time series # Volume + for dshape in ((4, 5, 6, 2), (8, 5, 6)): # Time series # Volume in_data = in_data_template.copy().reshape(dshape) img = img_klass(in_data, base_affine.copy()) diff --git a/nisext/py3builder.py b/nisext/py3builder.py index 24bd298364..53515d6c5b 100644 --- a/nisext/py3builder.py +++ b/nisext/py3builder.py @@ -6,7 +6,7 @@ except ImportError: # 2.x - no parsing of code from distutils.command.build_py import build_py -else: # Python 3 +else: # Python 3 # Command to also apply 2to3 to doctests from distutils import log diff --git a/nisext/testers.py b/nisext/testers.py index 07f71af696..80bbe1facc 100644 --- a/nisext/testers.py +++ b/nisext/testers.py @@ -135,9 +135,7 @@ def run_mod_cmd(mod_name, pkg_path, cmd, script_dir=None, print_location=True): os.environ['PYTHONPATH'] = r'"{pkg_path}"' else: os.environ['PYTHONPATH'] = r'"{pkg_path}"' + os.path.pathsep + PYTHONPATH -""".format( - **locals() - ) +""".format(**locals()) if print_location: p_loc = f'print({mod_name}.__file__);' else: @@ -155,9 +153,7 @@ def run_mod_cmd(mod_name, pkg_path, cmd, script_dir=None, print_location=True): {paths_add} import {mod_name} {p_loc} -{cmd}""".format( - **locals() - ) +{cmd}""".format(**locals()) ) res = back_tick(f'{PYTHON} script.py', ret_err=True) finally: diff --git a/pyproject.toml b/pyproject.toml index 14095b8f22..6286aaf4de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,21 +109,32 @@ __version__ = version = {version!r} __version_tuple__ = version_tuple = {version_tuple!r} ''' -[tool.blue] -line_length = 99 -target-version = ["py38"] -force-exclude = """ -( - _version.py - | nibabel/externals/ - | versioneer.py -) -""" +[tool.ruff] +line-length = 99 +exclude = ["version.py", "nibabel/externals", "versioneer.py"] -[tool.isort] -profile = "black" -line_length = 99 -extend_skip = ["_version.py", "externals"] +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F", "I", "Q"] +ignore = [ + # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "W191", + "E111", + "E114", + "E117", + "D206", + "D300", + "Q000", + "Q001", + "Q002", + "Q003", + "COM812", + "COM819", + "ISC001", + "ISC002", +] + +[tool.ruff.format] +quote-style = "single" [tool.mypy] python_version = "3.11" diff --git a/tools/markdown_release_notes.py b/tools/markdown_release_notes.py index 66e7876036..73bdbf7752 100644 --- a/tools/markdown_release_notes.py +++ b/tools/markdown_release_notes.py @@ -27,7 +27,7 @@ def main(): if in_release_notes: break in_release_notes = match.group(1) == version - next(f) # Skip the underline + next(f) # Skip the underline continue if in_release_notes: diff --git a/tox.ini b/tox.ini index cc2b263cb1..7a741f85db 100644 --- a/tox.ini +++ b/tox.ini @@ -139,15 +139,11 @@ commands = description = Check our style guide labels = check deps = - flake8 - blue - # Broken extras, remove when fix is released - isort[colors]!=5.13.1 + ruff>=0.1.9 skip_install = true commands = - blue --check --diff --color nibabel - isort --check --diff --color nibabel - flake8 nibabel + ruff --check --diff nibabel + ruff --format --check --diff nibabel [testenv:style-fix] description = Auto-apply style guide to the extent possible