Skip to content

Investigate a Ruff EXE gate for shebangs, and Ruff as consolidation for black/pyupgrade #6845

Description

@hjmjohnson

Proposal

Investigate adopting ruff in ITK's pre-commit configuration, for two related reasons:

  1. A shebang gate — ruff's flake8-executable (EXE) rules would end the recurring manual shebang sweeps.
  2. Tool consolidation — ruff can subsume black and pyupgrade, ITK's only two Python formatting/lint hooks.

This is a request to investigate and discuss, not a request to merge something. The second half in particular has a real cost and a governance dimension.

Why now: the shebang sweeps

check-shebang-scripts-are-executable verifies that a shebang and the executable bit agree. It does not check that the interpreter named actually resolves, and that gap has cost several PRs:

PR Branch What it fixed
#6840 release-5.4 Marked VCL_ModernizeNaming.py executable to satisfy the hook
Automated review then flagged that its #!python still cannot execute (mode 755, execve → ENOENT)
#6842 main 17 × env pythonenv python3; 2 × /usr/bin/pythonenv python3
#6844 release-5.4 The same, plus #!python and #!/bin/env python (/bin/env does not exist)

Every one of those was found by hand or by an external reviewer. EXE003 flags a shebang that does not name python; EXE001/EXE002 cover the mode pairing on POSIX and Windows. Together they turn a periodic manual audit into a hook.

Current state of Python shebangs after #6842 / #6844

Both branches converge on #!/usr/bin/env python3. Before those PRs:

Shebang main release-5.4
#!/usr/bin/env python3 22 1
#!/usr/bin/env python 17 20
#!/usr/bin/python 4 4
#!python 0 1
#!/bin/env python 0 1

The drift was one-directional: main had been partially modernized while release-5.4 had not, and nothing detected the difference.

Why now: consolidation

ITK's Python hooks today are black (24.2.0) and pyupgrade (v3.21.2), across 180 first-party .py files. Ruff implements both: ruff format for the former, UP rules for the latter.

MONAI has been carrying out exactly this consolidation and has already folded in pyupgrade (Project-MONAI/MONAI#8606), yesqa (#8692), flake8 (#8694) and pycln (#9061). The remaining step — replacing black and isort — is under discussion in Project-MONAI/MONAI#9066, with parity and CI-consistency concerns in #8683.

ITK is a smaller case than MONAI: no isort, no flake8, 180 files rather than 1353. That makes it a cheaper experiment, and it means the decision here is genuinely optional rather than a maintenance necessity.

Caveats worth stating up front

  • ruff format is not byte-identical to black. It is intentionally close but documents known differences. A migration would produce a one-time reformatting diff that has to be reviewed, or accepted as mechanical.
  • Adding ruff for EXE alone still adds a dependency. If the EXE rules are the only motivation, the honest comparison is against a ten-line local hook that greps first lines — cheaper, but one more bespoke script to own.
  • Wrapping-generated Python is not in scope and would need exclusion, as black already handles today.
  • The two goals are separable. An EXE-only ruff configuration is a small, low-risk change; replacing black is a larger conversation. They should probably not be bundled into one PR.

Suggested investigation

  1. Add ruff with only the EXE rules enabled and measure the finding count on both branches — likely zero after STYLE: Standardize Python shebangs on env python3 #6842/STYLE: Standardize Python shebangs on env python3 #6844, which would confirm the gate holds the line rather than creating work.
  2. Separately, run ruff format --diff against a black-clean tree and report the size of the delta.
  3. Report runtime for ruff versus black + pyupgrade on the 180-file set.
  4. Bring the numbers back here before proposing any replacement.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions