Skip to content

Replace pylint + autopep8 with ruff#1300

Open
sbryngelson wants to merge 12 commits intoMFlowCode:masterfrom
sbryngelson:ruff
Open

Replace pylint + autopep8 with ruff#1300
sbryngelson wants to merge 12 commits intoMFlowCode:masterfrom
sbryngelson:ruff

Conversation

@sbryngelson
Copy link
Member

Summary

Resolves #1281 — replaces pylint + autopep8 with ruff for all Python linting and formatting. Ruff is ~200-500x faster (under 0.2s vs 30-90s for the old pylint + autopep8 pipeline across 226 Python files).

What changed

  • Formatting: autopep8 → ruff format. Per-file find | xargs replaced with direct directory-level ruff format calls.
  • Linting: 3 separate pylint invocations → ruff check with rule sets E (pycodestyle), F (pyflakes), W (warnings), I (isort), PL (pylint-equivalent).
  • Auto-fix: ruff check --fix runs automatically in both ./mfc.sh format and ./mfc.sh lint to fix safe issues (import sorting, unused f-prefixes, etc.) before checking.
  • Config: New ruff.toml at repo root (centralized, replaces scattered pylint/autopep8 config). Only 6 rules ignored — all high-count structural/style rules (too-many-branches, magic-value-comparison, etc.).
  • Inline comments: Removed all ~229 # pylint: disable= comments. Added ~12 targeted # noqa: comments for intentional global statements.
  • Code fixes (not just suppression):
    • E731: lambda → named function
    • E741: ambiguous variable name lline
    • E721: !=is not for type comparison
    • PLW1641: added __hash__ to MFCConfig dataclass
    • PLR1722: exit()sys.exit()
    • F541: removed extraneous f prefix from 40 f-strings without placeholders
    • E501: wrapped 2 long lines
    • E702: split semicolon-separated statements
    • PLW2901: renamed 6 overwritten loop variables
  • Dependencies: Replaced pylint and autopep8 with ruff in pyproject.toml.
  • Bug fix: lint.sh used $(pwd)/toolchain/ which resolved symlinks and broke ruff config discovery; switched to relative paths.

Config details (ruff.toml)

line-length = 200
target-version = "py39"
select = ["E", "F", "W", "I", "PL"]
ignore = ["PLR0911", "PLR0912", "PLR0913", "PLR0915", "PLR2004", "PLC0415"]

Per-file ignores for examples/ and benchmarks/ (permissive for user-facing case files).

Test plan

  • ./mfc.sh lint passes (ruff check clean + 165 unit tests)
  • ./mfc.sh format runs without errors
  • ./mfc.sh precheck passes all 5 checks (formatting, spelling, toolchain lint, source lint, doc references)
  • CI passes on all compiler configurations

🤖 Generated with Claude Code

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@sbryngelson sbryngelson marked this pull request as ready for review March 11, 2026 19:09
Copilot AI review requested due to automatic review settings March 11, 2026 19:09
@MFlowCode MFlowCode deleted a comment from cursor bot Mar 11, 2026
@qodo-code-review

This comment has been minimized.

@qodo-code-review

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the Python toolchain’s linting/formatting workflow from pylint/autopep8 to ruff, and then applies ruff-driven cleanup (imports, formatting, some small refactors) across the toolchain plus example/benchmark case files.

Changes:

  • Replace pylint + autopep8 usage with ruff (new ruff.toml, updated bootstrap scripts, updated docs/CLI help text).
  • Apply broad import ordering / formatting updates across toolchain modules, viz utilities, params tooling, and docs generators.
  • Minor functional tweaks (e.g., sys.exit instead of exit, MFCConfig.__hash__ to allow hashing).

Reviewed changes

Copilot reviewed 156 out of 158 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
toolchain/pyproject.toml Replace pylint/autopep8 dependencies with ruff.
toolchain/mfc/viz/viz.py Remove pylint pragmas; reorder imports.
toolchain/mfc/viz/tui.py Remove pylint pragmas; minor refactors/formatting.
toolchain/mfc/viz/test_viz.py Remove pylint pragmas; import/blank-line normalization.
toolchain/mfc/viz/silo_reader.py Replace pylint pragmas with ruff noqa; formatting.
toolchain/mfc/viz/renderer.py Replace pylint pragmas with ruff noqa; reorder imports.
toolchain/mfc/viz/reader.py Replace pylint pragmas with ruff noqa; formatting.
toolchain/mfc/viz/interactive.py Remove pylint pragmas; reorder imports; formatting.
toolchain/mfc/viz/_step_cache.py Replace pylint pragmas; formatting.
toolchain/mfc/validate.py Use sys.exit; reorder imports/strings.
toolchain/mfc/user_guide.py Reformat/normalize strings; minor readability tweaks.
toolchain/mfc/state.py Import normalization; add MFCConfig.__hash__; ruff-style noqa.
toolchain/mfc/sched.py Reformat; tighten conditionals; import normalization.
toolchain/mfc/run/run.py Import normalization; string normalization; formatting.
toolchain/mfc/run/queues.py Import formatting; string normalization.
toolchain/mfc/run/input.py Import normalization; formatting; small cleanup in clean().
toolchain/mfc/run/case_dicts.py Remove pylint pragmas; regex string normalization.
toolchain/mfc/printer.py Import formatting.
toolchain/mfc/params/validate.py Ensure registry population via definitions import; reorder imports.
toolchain/mfc/params/suggest.py Import ordering/formatting.
toolchain/mfc/params/schema.py Import ordering; remove pylint pragma.
toolchain/mfc/params/registry.py Import ordering; remove pylint pragma; lazy import cleanup.
toolchain/mfc/params/namelist_parser.py Remove stray blank line.
toolchain/mfc/params/generators/json_schema_gen.py Ensure definitions import for registry population; reorder imports.
toolchain/mfc/params/generators/docs_gen.py Ensure definitions import; reorder imports; remove pylint pragmas.
toolchain/mfc/params/generators/init.py Import ordering.
toolchain/mfc/params/descriptions.py Remove pylint pragmas; formatting.
toolchain/mfc/params/definitions.py Import ordering; remove pylint pragmas.
toolchain/mfc/params/ast_analyzer.py Import ordering; remove broad-except pylint pragmas.
toolchain/mfc/params/init.py Ensure definitions import occurs early; reorder exports.
toolchain/mfc/params_tests/test_validate.py Import ordering.
toolchain/mfc/params_tests/test_registry.py Remove pylint pragma; formatting.
toolchain/mfc/params_tests/test_integration.py Remove pylint pragma; formatting.
toolchain/mfc/params_tests/test_definitions.py Import ordering.
toolchain/mfc/params_tests/snapshot.py Import ordering.
toolchain/mfc/params_tests/runner.py Import ordering; collapse long imports; minor output tweak.
toolchain/mfc/params_tests/negative_tests.py Import ordering.
toolchain/mfc/params_tests/mutation_tests.py Import ordering; minor output tweak.
toolchain/mfc/params_tests/inventory.py Import ordering.
toolchain/mfc/params_tests/coverage.py Import ordering.
toolchain/mfc/params_cmd.py Remove pylint pragmas; import grouping; formatting.
toolchain/mfc/packer/tol.py Import ordering; signature formatting.
toolchain/mfc/packer/packer.py Import ordering.
toolchain/mfc/packer/pack.py Import ordering.
toolchain/mfc/packer/errors.py Import formatting.
toolchain/mfc/lock.py Replace pylint pragmas with ruff noqa; formatting.
toolchain/mfc/init.py String normalization; import ordering; formatting.
toolchain/mfc/ide.py Remove pylint pragmas; string normalization; formatting.
toolchain/mfc/generate.py Use sys.exit; import ordering; formatting.
toolchain/mfc/gen_physics_docs.py Replace pylint pragmas with ruff noqa; formatting.
toolchain/mfc/gen_case_constraints_docs.py Remove pylint pragmas; refactor long literals; formatting.
toolchain/mfc/count.py Import ordering; minor variable renames; formatting.
toolchain/mfc/completion.py Import ordering; minor output formatting.
toolchain/mfc/common.py Import ordering; replace pylint global pragmas with ruff noqa; formatting.
toolchain/mfc/cli/test_cli.py Remove pylint pragma; add tests for MFCConfig.__hash__.
toolchain/mfc/cli/schema.py Remove pylint pragmas; formatting/spacing normalization.
toolchain/mfc/cli/docs_gen.py Import ordering; collapse helper signature formatting.
toolchain/mfc/cli/commands.py Update lint command wording (pylint→ruff); formatting/indent normalization.
toolchain/mfc/cli/argparse_gen.py Remove pylint pragmas; small is not str fix; formatting.
toolchain/mfc/cli/init.py Import ordering.
toolchain/mfc/clean.py Import ordering.
toolchain/mfc/case_utils.py String normalization; formatting.
toolchain/mfc/bench.py Import ordering; minor print formatting; small refactor.
toolchain/mfc/args.py Import ordering; string normalization; formatting.
toolchain/main.py Remove pylint pragmas; import ordering; formatting; simplify some blocks.
toolchain/indenter.py Import ordering; string normalization; formatting.
toolchain/bootstrap/lint.sh Switch linting from pylint to ruff; add ruff auto-fix step; simplify test invocation.
toolchain/bootstrap/format.sh Switch Python formatting to ruff check --fix-only + ruff format; remove autopep8 pipeline.
toolchain/bootstrap/format_python.sh Remove autopep8-based formatter script.
ruff.toml Add ruff configuration (rules, ignores, per-file ignores, isort config).
examples/scaling/export.py Import ordering.
examples/scaling/benchmark.py Import ordering.
examples/scaling/analyze.py Import ordering.
examples/nD_perfect_reactor/case.py Import ordering; minor f-string spacing.
examples/nD_perfect_reactor/analyze.py Import ordering; string normalization; formatting.
examples/3D_turb_mixing/case.py Import ordering.
examples/3D_TaylorGreenVortex/case.py Import ordering.
examples/3D_TaylorGreenVortex_analytical/case.py Import ordering.
examples/3D_shockdroplet/case.py Import ordering.
examples/3D_shockdroplet_muscl/case.py Import ordering.
examples/3D_recovering_sphere/case.py Import ordering.
examples/3D_rayleigh_taylor/case.py Import ordering.
examples/3D_rayleigh_taylor_muscl/case.py Import ordering.
examples/3D_phasechange_bubble/case.py Import ordering.
examples/3D_lagrange_shbubcollapse/case.py Import ordering.
examples/3D_lagrange_bubblescreen/case.py Import ordering.
examples/3D_IGR_TaylorGreenVortex/case.py Import ordering.
examples/3D_IGR_TaylorGreenVortex_nvidia/case.py Import ordering.
examples/3D_IGR_jet/case.py Import ordering.
examples/3D_IGR_33jet/case.py Import ordering.
examples/2D_zero_circ_vortex/case.py Import ordering.
examples/2D_zero_circ_vortex_analytical/case.py Import ordering.
examples/2D_whale_bubble_annulus/case.py Import ordering.
examples/2D_viscous/case.py Import ordering.
examples/2D_triple_point/case.py Import ordering.
examples/2D_TaylorGreenVortex/case.py Import ordering.
examples/2D_shocktube_phasechange/case.py Import ordering.
examples/2D_shockdroplet/case.py Import ordering.
examples/2D_shockdroplet_muscl/case.py Import ordering.
examples/2D_shockbubble/case.py Import ordering.
examples/2D_rayleigh_taylor/case.py Import ordering.
examples/2D_phasechange_bubble/case.py Import ordering.
examples/2D_mixing_artificial_Ma/case.py Import ordering.
examples/2D_lungwave/case.py Import ordering.
examples/2D_lungwave_horizontal/case.py Import ordering.
examples/2D_laplace_pressure_jump/case.py Import ordering.
examples/2D_lagrange_bubblescreen/case.py Import ordering.
examples/2D_jet/case.py Import ordering.
examples/2D_isentropicvortex/case.py Import ordering; remove unnecessary f-string prefixes.
examples/2D_isentropicvortex_analytical/case.py Import ordering; remove unnecessary f-string prefixes.
examples/2D_IGR_triple_point/case.py Import ordering.
examples/2D_IGR_2fluid/case.py Import ordering.
examples/2D_GreshoVortex/case.py Import ordering.
examples/2D_axisym_shockwatercavity/case.py Import ordering.
examples/2D_axisym_shockbubble/case.py Import ordering.
examples/2D_acoustic_pulse/case.py Import ordering.
examples/2D_acoustic_pulse_analytical/case.py Import ordering.
examples/2D_5wave_quasi1D/case.py Import ordering.
examples/1D_titarevtorro/case.py Import ordering.
examples/1D_titarevtorro_analytical/case.py Import ordering.
examples/1D_sodshocktube/case.py Import ordering.
examples/1D_sodshocktube_muscl/case.py Import ordering.
examples/1D_sodHypo/case.py Import ordering.
examples/1D_shuosher_wenoz5/case.py Import ordering.
examples/1D_shuosher_wenom5/case.py Import ordering.
examples/1D_shuosher_wenojs5/case.py Import ordering.
examples/1D_shuosher_teno7/case.py Import ordering.
examples/1D_shuosher_teno5/case.py Import ordering.
examples/1D_shuosher_old/case.py Import ordering.
examples/1D_shuosher_analytical/case.py Import ordering.
examples/1D_reactive_shocktube/case.py Import ordering; minor f-string spacing.
examples/1D_qbmm/case.py Import ordering; remove stray comment marker.
examples/1D_poly_bubscreen/case.py Import ordering; remove stray comment marker.
examples/1D_multispecies_diffusion/case.py Import ordering; minor f-string spacing.
examples/1D_laxshocktube/case.py Import ordering.
examples/1D_inert_shocktube/case.py Import ordering; minor f-string spacing.
examples/1D_impact/case.py Import ordering.
examples/1D_hypo_2materials/case.py Import ordering.
examples/1D_exp_tube_phasechange/case.py Import ordering.
examples/1D_exp_bubscreen/case.py Import ordering.
examples/1D_convergence/case.py Import ordering; simplify constant strings.
examples/1D_bubblescreen/case.py Import ordering; remove stray comment marker.
examples/0D_bubblecollapse_adap/case.py Import ordering.
docs/documentation/contributing.md Update lint gate description for ruff-based linting.
CLAUDE.md Update developer instructions: ./mfc.sh lint now uses ruff.
benchmarks/viscous_weno5_sgb_acoustic/case.py Import ordering.
benchmarks/igr/case.py Import ordering.
benchmarks/ibm/case.py Import ordering.
benchmarks/hypo_hll/case.py Import ordering.
benchmarks/5eq_rk3_weno3_hllc/case.py Import ordering.

Comment on lines +1 to +25
line-length = 200
target-version = "py39"
exclude = ["_version.py"]

[lint]
select = ["E", "F", "W", "I", "PL"]
ignore = [
# Complexity thresholds (project style)
"PLR0911", # too-many-return-statements
"PLR0912", # too-many-branches
"PLR0913", # too-many-arguments
"PLR0915", # too-many-statements
"PLR2004", # magic-value-comparison
# Import patterns (project style)
"PLC0415", # import-outside-toplevel
# Global variable reads (module-level singleton pattern)
"PLW0602", # global-variable-not-assigned
]

[lint.per-file-ignores]
"examples/**/*.py" = ["F401", "F403", "F405", "F811", "F821", "E402", "E722", "E741"]
"benchmarks/*/case.py" = ["F401", "F403", "F405", "F811", "F821", "E402", "E741"]

[lint.isort]
known-first-party = ["mfc"]

This comment was marked as outdated.

Comment on lines +16 to +18
# Global variable reads (module-level singleton pattern)
"PLW0602", # global-variable-not-assigned
]

This comment was marked as outdated.

if ! find $SEARCH_PATHS -type f 2>/dev/null | grep -E '\.(py)$' \
| xargs --no-run-if-empty -L 1 -P ${JOBS:-1} $SHELL toolchain/bootstrap/format_python.sh; then
# Format Python files with ruff (auto-fix lint issues, then format)
ruff check --fix-only $SEARCH_PATHS

This comment was marked as outdated.

Comment on lines +56 to +58
# Format Python files with ruff (auto-fix lint issues, then format)
ruff check --fix-only $SEARCH_PATHS
if ! ruff format $SEARCH_PATHS; then

This comment was marked as outdated.

@github-actions

This comment has been minimized.

@sbryngelson sbryngelson marked this pull request as draft March 11, 2026 20:51
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

sbryngelson and others added 7 commits March 14, 2026 09:35
- Replace autopep8 with ruff format, pylint with ruff check
- Add ruff.toml config at repo root (E, F, W, I, PL rule sets)
- Enable ruff check --fix in format.sh and lint.sh for safe auto-fixes
- Remove all ~229 inline pylint: disable comments
- Fix code violations: E731 (lambda), E741 (ambiguous var), E721 (type
  comparison), PLW1641 (hash), PLR1722 (sys.exit), F541 (f-string),
  E501 (line length), E702 (semicolons), PLW2901 (loop var overwrite)
- Add ~12 noqa comments for intentional global statements (PLW0603/PLW0602)
- Remove 8 zero-violation rules from ignore list to keep config minimal
- Fix lint.sh symlink path resolution breaking ruff config discovery
- Replace pylint and autopep8 deps with ruff in pyproject.toml

Resolves MFlowCode#1281

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-fix

- Add PLW0602 (global-variable-not-assigned) to ruff.toml ignore list
  instead of scattering noqa comments across ~19 call sites
- Change ruff check --fix to keep stderr visible (> /dev/null || true
  instead of > /dev/null 2>&1 || true) so config/invocation errors
  are not silently swallowed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use dataclasses.fields() instead of dataclasses.asdict() in
  MFCConfig.__hash__ to avoid issues with nested types and sorted()
- Delete format_python.sh which is no longer referenced by anything

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use ruff check --fix-only instead of --fix || true so genuine ruff
  failures propagate while still tolerating unfixable violations
- Update CLAUDE.md: Pylint -> Ruff in lint command description
- Add 4 unit tests for MFCConfig.__hash__ (hash/eq contract, set/dict usage)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap ruff check --fix-only in error checking in format.sh so a
missing or broken ruff installation does not silently succeed.

Add isinstance guard to MFCConfig.__eq__ so comparisons with
non-MFCConfig objects return NotImplemented instead of raising
AttributeError.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

sbryngelson and others added 2 commits March 14, 2026 11:15
Run indenter.py on all files in a single Python process instead of
spawning 84 separate processes. Move convergence loop from per-file
(format_file.sh) to top-level (format.sh) with md5sum comparison.
Delete the now-unused format_file.sh wrapper.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Run format first (modifies files), then lint, spelling, source lint,
and doc refs in parallel. Skip slow matplotlib rendering tests during
precheck via MFC_SKIP_RENDER_TESTS=1 (CI still runs the full suite).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions

This comment has been minimized.

Restore specific error messages (raw directives, double precision,
junk patterns) lost during precheck parallelization. Clarify that
--fix-only in format.sh only errors if ruff itself fails to run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sbryngelson sbryngelson marked this pull request as ready for review March 14, 2026 15:53
@github-actions
Copy link

Claude Code Review

Head SHA: 7da4061

Files changed: 165 (selected key files below)

  • ruff.toml (new)
  • toolchain/bootstrap/format.sh
  • toolchain/bootstrap/lint.sh
  • toolchain/bootstrap/precheck.sh
  • toolchain/bootstrap/format_file.sh (deleted)
  • toolchain/bootstrap/format_python.sh (deleted)
  • toolchain/pyproject.toml
  • toolchain/mfc/case_validator.py (+891/-1300)
  • toolchain/mfc/build.py, case.py, main.py, and 150+ example/toolchain files

Summary

  • Replaces pylint + autopep8 with ruff for linting and formatting — a straightforward, well-motivated modernisation.
  • All ~229 # pylint: disable= comments removed; ~12 # noqa: comments added for intentional global uses.
  • Several real code fixes applied alongside the style changes (lambda→named function, sys.exit(), __hash__, loop variable shadowing, f-string cleanup).
  • precheck.sh now runs spell check and lint in parallel background jobs, improving local iteration speed.
  • Bug fix in lint.sh: cd toolchain replaces cd "$(pwd)/toolchain" to avoid symlink resolution breaking ruff config discovery.

Findings

1. ruff.toml — undocumented ignore rule PLW0602

# ruff.toml, line 17
"PLW0602",  # global-variable-not-assigned

PLW0602 is present in the config but not mentioned in the PR description's "6 rules ignored" claim. It's a valid addition (the codebase uses module-level singleton global writes without re-reads), but worth noting for reviewers.

2. toolchain/bootstrap/format.sh — unquoted variable in find call

# format.sh, approx. line 50
FORTRAN_FILES=$(find $FORTRAN_DIRS -type f 2>/dev/null | ...)

$FORTRAN_DIRS is unquoted, which would word-split on paths containing spaces. Not a practical issue for this codebase, but find $FORTRAN_DIRS should ideally be find $FORTRAN_DIRS with quoted individual elements or use an array. Low priority.

3. toolchain/bootstrap/precheck.sh — source lint messages may be empty-line-filtered

# precheck.sh, approx. line 138
echo -e "$SOURCE_MSGS" | while read -r msg; do
    [ -n "$msg" ] && error "$msg"
done

Messages are appended with \n separators, so read -r will produce empty strings between items and the [ -n "$msg" ] guard is correct. However, if a message itself contains a newline it would be split. This is a minor robustness issue — the previous code printed errors immediately on detection, which was cleaner.

4. toolchain/bootstrap/format.shruff check --fix-only exit code semantics

if ! ruff check --fix-only $PYTHON_DIRS; then
    error "ruff failed to run. Check your ruff installation."
    exit 1
fi

ruff check --fix-only exits 0 even if unfixable violations remain (by design), so this guard only catches ruff installation failures. This is consistent with the inline comment and is correct behaviour, but the error message could confuse users who misread the intent. Consider error "ruff --fix-only failed to execute." for clarity.

5. examples/2D_IGR_2fluid/case.py — module-level variable l still present

# examples/2D_IGR_2fluid/case.py, line 11 (post-patch)
l = 1
eps = 1e-6

E741 (ambiguous variable name l) is suppressed for all examples/**/*.py via per-file-ignores. This is a deliberate policy choice, so not a blocker, but l is a known readability hazard (visually identical to 1 or I in many fonts). Worth flagging as a future cleanup opportunity.


No issues with

  • The ruff.toml rule selections (E, F, W, I, PL) and the 6 complexity ignores are reasonable for a large scientific codebase.
  • Import reordering across 100+ example files is mechanically correct (all alphabetical, stdlib-first).
  • f-string removals (e.g. "- (y - yc)**2d0)" replacing f"- (y - yc)**2d0)") are safe — no placeholders were present.
  • The cd toolchain symlink-resolution fix is correct and the root cause is well-explained.
  • toolchain/pyproject.toml: removing autopep8 comment is fine; ruff subsumes both tools.

🤖 Generated with Claude Code

@github-actions
Copy link

Claude Code Review

Head SHA: 7da40610974c395d2f785ba9fe4c845b78b51553

Files changed: 165 (toolchain infrastructure, 80+ example/benchmark case files, docs)

Key files reviewed:

  • ruff.toml (new)
  • toolchain/bootstrap/format.sh, lint.sh, precheck.sh
  • toolchain/bootstrap/format_file.sh, format_python.sh (deleted)
  • toolchain/indenter.py
  • toolchain/main.py, toolchain/mfc/build.py, toolchain/mfc/case.py, toolchain/mfc/case_validator.py

Summary

  • Replaces pylint + autopep8 with ruff for both linting and formatting — clean migration with meaningful speed gains
  • Removes ~229 # pylint: disable= comments; adds ~12 targeted # noqa: comments
  • indenter.py upgraded to accept multiple files in one invocation (eliminates per-file subprocess overhead)
  • precheck.sh now parallelises spell check and toolchain lint against the (serial) Fortran format pass
  • Bug fix: lint.sh switched from /home/runner/work/MFC/MFC/toolchain/ (symlink-resolving) to relative path, fixing ruff config discovery
  • Non-formatting code fixes are substantive and correct (lambda→named function, exit()sys.exit(), type comparison with is not, loop-variable shadowing)

Findings

1. Minor: ruff.toml ignore list has 7 rules; PR description says 6
toolchain/../ruff.toml line 14:

"PLW0602",  # global-variable-not-assigned

PLW0602 is present in the config but not mentioned in the PR summary. Not a bug, but worth noting so reviewers don't wonder if it was accidental.

2. Fortran convergence check hashes all files jointly, not per-file
toolchain/bootstrap/format.sh (new loop):

old_hash=d41d8cd98f00b204e9800998ecf8427e  -
# ... run indenter + fprettify on all files ...
new_hash=d41d8cd98f00b204e9800998ecf8427e  -

The old format_file.sh compared each file's content individually and broke out of its loop per file. The new approach combines all files into one hash. This is functionally equivalent for steady-state detection but means a cycle in any single file will run all files an extra iteration. Low impact in practice, but worth knowing.

3. format.sh: unquoted $FORTRAN_DIRS in find
toolchain/bootstrap/format.sh:

FORTRAN_FILES=

When using custom --path arguments with spaces (unusual but possible), word-splitting is intentional here. The default paths (src, toolchain/ examples/ benchmarks/) are fine. Not a regression vs. the old code.

4. precheck.sh: source lint still runs inline (correct and intentional)
The parallel refactor correctly keeps source-grep checks inline (fast) and background-dispatches only the slower ./mfc.sh spelling and ./mfc.sh lint checks. No issue — just confirming the design is sound.

5. __hash__ added to MFCConfig dataclass (correct fix)
toolchain/mfc/state.py — PLW1641 fix adds __hash__ consistent with the existing __eq__. The companion TestMFCConfigHash unit test is a good addition.


Minor observations (no action required)

  • examples/2D_IGR_2fluid/case.py retains l = 1 (E741 ambiguous name) — covered by the examples/**/*.py per-file ignore, which is the right call for user-facing case files.
  • Long-string reformatting in case_validator.py (multi-line parens → single long strings) trades readability for ruff compliance; E501 is not in the selected rule set so these are not enforced by ruff. Consider whether the multi-line form was preferable — no correctness impact either way.
  • toolchain/mfc/build.py: # pylint: disable=consider-using-with comments removed from subprocess.Popen calls. The Popen objects are consumed immediately in the same scope, so the context-manager form is not required here. ✓

Overall this is a high-quality, well-scoped migration. The mechanical changes (import ordering, quote normalisation, # pylint: comment removal) look correct throughout the 165-file sweep. No correctness issues found.

Store per-file md5 hashes in build/.cache/format/ after formatting.
On subsequent runs, only re-format files whose hash changed. Combined
with parallel precheck, total precommit time drops from 35s to 13s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link

Claude Code Review

Head SHA: 9ed57abd02c0343d8f71b57f8db50bac7ef6416e
Files changed: 165 (+7238 / −6600)

Key files:

  • ruff.toml (new — centralized config)
  • toolchain/bootstrap/format.sh (autopep8 → ruff format + Fortran hash cache)
  • toolchain/bootstrap/lint.sh (pylint → ruff check)
  • toolchain/bootstrap/precheck.sh (parallelized phases)
  • toolchain/pyproject.toml (deps: pylint+autopep8 → ruff)
  • toolchain/mfc/build.py, case_validator.py, main.py, … (pylint comment cleanup, isort)
  • ~120 example/benchmark case.py files (isort reordering only)

Summary

  • Replaces pylint + autopep8 with ruff for linting and formatting — sound decision, ruff is significantly faster and consolidates two tools into one.
  • All four dependency locations are updated consistently (pyproject.toml, shell scripts, docs).
  • The bug fix in lint.sh (symlink-resolved $(pwd) → relative cd toolchain) is correct and necessary for ruff config discovery.
  • Code fixes (E731 lambda→function, E721 type comparison, PLR1722 exit()sys.exit(), F541 empty f-strings, PLW2901 loop variable shadowing) are all improvements.
  • case_validator.py string collapsing to single lines is ruff-enforced formatting, acceptable at line-length = 200.

Findings

1. toolchain/bootstrap/format.sh — hash cache key collision risk (low severity)

The Fortran format cache key is derived from:

cache_key="$f"

Two files with paths that differ only in slash vs. underscore (e.g., src/foo/bar.fpp and src_foo/bar.fpp) would map to the same cache key. In practice MFC source paths won't collide, but consider using sha256sum or URL-encoding instead of tr '/' '_' to be safe.

2. toolchain/bootstrap/format.sh — unquoted $FORTRAN_DIRS / $PYTHON_DIRS in find

FORTRAN_FILES=$(find $FORTRAN_DIRS ...)

When PYTHON_DIRS="toolchain/ examples/ benchmarks/" this works by design (bash word-splits to three args), but it's a footgun: if a user passes a path with spaces via SEARCH_PATHS, the paths array will be split unexpectedly. The previous code had the same issue, but this is a good opportunity to quote the array correctly as "${PATHS[@]}".

3. toolchain/bootstrap/precheck.shMFC_SKIP_RENDER_TESTS=1 diverges from CI

# Skip slow rendering tests (matplotlib/imageio) during local precheck.
# CI runs the full suite via ./mfc.sh lint without this variable.
export MFC_SKIP_RENDER_TESTS=1

This means ./mfc.sh precheck can pass locally while ./mfc.sh lint (as run by CI) fails on render tests. The PR checklist and CLAUDE.md say precheck is the CI gate equivalent — this silent divergence could cause confusion. Consider: (a) document this prominently, or (b) make precheck run the full suite with a --skip-render flag that CI doesn't use.

4. ruff.tomlPLW0602 globally silenced

"PLW0602",  # global-variable-not-assigned

This suppresses the rule for the entire codebase, not just the files with the module-level singleton pattern. A per-file-ignores scoped to the specific modules (e.g., toolchain/mfc/state.py) would be more precise and would still catch accidental global uses elsewhere.


Minor / Non-blocking

  • examples/2D_IGR_2fluid/case.py still has l = 1 (ambiguous name E741), but this is covered by the per-file-ignore for examples/ — intentional.
  • The DIRTY_FILES newline-separated accumulation (DIRTY_FILES+=$'\n') is fragile with filenames containing newlines, but that's an extreme edge case and consistent with existing toolchain conventions.
  • toolchain/bootstrap/format_python.sh and toolchain/bootstrap/format_file.sh are deleted — verify no external scripts call them directly outside of format.sh.

Overall this is a clean, well-scoped modernization. The findings above are mostly low-severity or style; none should block merging. CI green would confirm correctness.

🤖 Generated with Claude Code

Collapse 40,316 indexed parameter entries (e.g., patch_ib(1)%radius
through patch_ib(1000)%radius) into 241 regex patterns using JSON
schema patternProperties. Schema compiles in 0.1s instead of 7s.
Total precommit hook time drops from ~13s to ~8-10s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link

Claude Code Review

Head SHA: ee73c1f

Files changed: 165 (7,267 additions / 6,599 deletions)

Selected files reviewed:

  • ruff.toml (new)
  • toolchain/bootstrap/format.sh
  • toolchain/bootstrap/lint.sh
  • toolchain/bootstrap/precheck.sh
  • toolchain/indenter.py
  • toolchain/main.py
  • toolchain/pyproject.toml
  • toolchain/mfc/case_validator.py (largest change)
  • Representative examples/*/case.py and benchmarks/*/case.py files

Summary

  • Replaces pylint + autopep8 with ruff for Python linting and formatting — a clean, well-scoped migration.
  • format.sh gains a hash-based Fortran caching layer to skip unchanged files, and consolidates Python formatting with ruff format.
  • precheck.sh parallelizes spell-check and lint using background subshells with temp-file result collection.
  • indenter.py is upgraded to accept multiple files in one invocation (nargs="+"), enabling the batch Fortran formatting path.
  • ~229 # pylint: disable= comments removed; ~12 # noqa: PLW0602 comments added for intentional global statements.
  • Per-file code fixes (E731, E741, E721, PLR1722, F541, etc.) are real improvements, not just suppression.

Findings

1. PLW0602 suppressed globally but also via # noqa comments — redundant
ruff.toml line 1357 ignores PLW0602 globally, so all the # noqa: PLW0602 inline comments added throughout the codebase are unused. Since RUF100 (unused-noqa) is not in the selected ruleset, this won't error, but it's misleading. Consider either removing the per-line # noqa comments (rule is globally ignored) or keeping the rule enabled and suppressing only at call sites.

2. format.sh — hash cache key collision risk
toolchain/bootstrap/format.sh, cache-key generation:

cache_key=$(echo "$f" | tr '/' '_')

Paths like src/a/b.fpp and src/a_b.fpp both map to src_a_b.fpp, so a collision is theoretically possible. In MFC's current layout this is unlikely to trigger, but using a hash of the full path (e.g. echo "$f" | md5sum | cut -d' ' -f1) would be safer.

3. lint.shruff check --fix-only scope differs from format.sh
In format.sh the auto-fix scope is toolchain/ examples/ benchmarks/ (all Python files), but in lint.sh it is toolchain/ examples/*/case.py benchmarks/*/case.py (only case.py files). Non-case.py Python files in examples/ subdirectories get auto-fixed during format but are not linted or auto-fixed during ./mfc.sh lint. This is a minor consistency gap; the per-file-ignores in ruff.toml already exempts examples from most rules, so the practical impact is small.

4. ruff check --fix-only exit-code comment is slightly misleading
toolchain/bootstrap/format.sh line ~1470:

# --fix-only exits 0 even when unfixable violations remain
if ! ruff check --fix-only $PYTHON_DIRS; then
    error "ruff failed to run. Check your ruff installation."

The comment is correct, but the error message says "ruff failed to run" — it could also exit non-zero on a syntax error in a Python file. A more accurate message would be "ruff check --fix-only exited non-zero (syntax error or ruff not installed)."

5. format.sh — Fortran steady-state loop operates on aggregate hash, not per-file
Old format_file.sh checked old_file == new_file per-file per-iteration. The new batch loop hashes all dirty files combined. If iteration 1 stabilizes file A but not B, the loop reruns indenter.py + fprettify on all dirty files (including already-stable A). Functionally correct and bounded at 4 iterations, but slightly less efficient than the old approach. Not a bug.

6. ruff.tomlE501 not explicitly ignored, but line-length = 200 is permissive
Long lines over 200 characters will be flagged; the prior autopep8 --max-line-length 200 setting is correctly carried over. No issue, just confirming parity.


No blocking issues found. The migration is clean and the ruff.toml config is sensibly minimal. Items 1–4 above are low-severity observations.

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Replace pylint + autopep8 with ruff for Python linting and formatting

2 participants