From 655357377fa20ccf3990fd290aaf161b0edf5900 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 3 Mar 2026 10:16:05 +0100 Subject: [PATCH 1/3] [MAINT] switch to ruff --- .flake8 | 15 ----- .pre-commit-config.yaml | 44 ++++++++------ bidsmreye/_parsers.py | 2 +- bidsmreye/bids_utils.py | 7 +-- bidsmreye/configuration.py | 8 +-- bidsmreye/methods.py | 2 +- bidsmreye/quality_control.py | 2 +- bidsmreye/report.py | 1 - bidsmreye/visualize.py | 38 ++++++------ pyproject.toml | 114 ++++++++++++++++++++++++++++++++--- 10 files changed, 162 insertions(+), 71 deletions(-) delete mode 100644 .flake8 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index db1a275..0000000 --- a/.flake8 +++ /dev/null @@ -1,15 +0,0 @@ -[flake8] -count = True -show-source = True -statistics = True -exclude = - *build - .git - __pycache__ - _version.py -ignore = D100, D103, W503 -max-line-length = 90 -max_complexity = 15 -max_function_length = 150 -max_parameters_amount = 15 -max_returns_amount = 5 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ad1b95..b23fa63 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,15 +5,25 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - - id: end-of-file-fixer - - id: check-added-large-files + - id: check-ast - id: check-case-conflict - id: check-json - id: check-merge-conflict + - id: check-toml - id: check-yaml - - id: debug-statements + - id: end-of-file-fixer + - id: mixed-line-ending + args: [--fix=lf] - id: trailing-whitespace + # Checks for .rst files +- repo: https://github.com/pre-commit/pygrep-hooks + rev: v1.10.0 + hooks: + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + - repo: https://github.com/jumanjihouse/pre-commit-hook-yamlfmt rev: 0.2.3 hooks: @@ -36,12 +46,8 @@ repos: rev: v2.10 hooks: - id: import-linter - -- repo: https://github.com/pycqa/isort - rev: 8.0.1 - hooks: - - id: isort - args: [--settings-path, pyproject.toml] + args: [--verbose] + language: python - repo: https://github.com/adamchainz/blacken-docs rev: 1.20.0 @@ -56,6 +62,16 @@ repos: - id: black args: [--config=pyproject.toml] + # Lint and format Python code +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.15.1 + hooks: + - id: ruff-check + # args: [--statistics] + args: [--fix, --show-fixes, --unsafe-fixes] + - id: ruff-format + # args: [--diff] + - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.19.1 hooks: @@ -71,13 +87,7 @@ repos: args: [--toml=pyproject.toml] additional_dependencies: [tomli] -- repo: https://github.com/pycqa/flake8 - rev: 7.3.0 - hooks: - - id: flake8 - exclude: tests_.*.py|version.*.py|setup.py # ignore tests and versioneer related code - args: [--config, .flake8, --verbose] - additional_dependencies: [flake8-docstrings, flake8-use-fstring, flake8-functions, flake8-bugbear] - ci: autoupdate_commit_msg: 'chore: update pre-commit hooks' + autofix_commit_msg: 'style: pre-commit fixes' + autoupdate_schedule: monthly diff --git a/bidsmreye/_parsers.py b/bidsmreye/_parsers.py index d807161..a56793c 100644 --- a/bidsmreye/_parsers.py +++ b/bidsmreye/_parsers.py @@ -10,7 +10,7 @@ def _base_parser(formatter_class: type[HelpFormatter] = HelpFormatter) -> ArgumentParser: parser = ArgumentParser( description=( - "BIDS app using deepMReye to decode " "eye motion for fMRI time series data." + "BIDS app using deepMReye to decode eye motion for fMRI time series data." ), epilog=""" For a more readable version of this help section, diff --git a/bidsmreye/bids_utils.py b/bidsmreye/bids_utils.py index 782a71c..70cd13d 100644 --- a/bidsmreye/bids_utils.py +++ b/bidsmreye/bids_utils.py @@ -36,11 +36,8 @@ def check_layout(cfg: Config, layout: BIDSLayout, for_file: str = "bold") -> Non :raises RuntimeError: _description_ """ desc = layout.get_dataset_description() - if ( - "DatasetType" not in desc - and "PipelineDescription" not in desc - or "DatasetType" in desc - and desc["DatasetType"] != "derivative" + if ("DatasetType" not in desc and "PipelineDescription" not in desc) or ( + "DatasetType" in desc and desc["DatasetType"] != "derivative" ): raise RuntimeError( "DatasetType must be 'derivative' in dataset_description.json\n." diff --git a/bidsmreye/configuration.py b/bidsmreye/configuration.py index 3f44ce7..cbf35c8 100644 --- a/bidsmreye/configuration.py +++ b/bidsmreye/configuration.py @@ -74,7 +74,7 @@ def __attrs_post_init__(self) -> None: self.bids_filter = get_bids_filter_config() self.output_dir = self.output_dir / "bidsmreye" - if not self.output_dir: + if not self.output_dir.exists(): self.output_dir.mkdir(parents=True, exist_ok=True) database_path = self.input_dir / "pybids_db" @@ -136,7 +136,7 @@ def check_argument(self, attribute: str, layout_in: BIDSLayout) -> Config: self.listify(attribute) # convert all run values to integers - if attribute in {"run"}: + if attribute == "run": for i, j in enumerate(value): value[i] = int(j) tmp = [int(j) for j in getattr(self, attribute)] @@ -155,7 +155,7 @@ def check_argument(self, attribute: str, layout_in: BIDSLayout) -> Config: # run and space can be empty if their entity are not used # we will figure out the values for run # in subject / task wise manner later on - if attribute not in ["run"]: + if attribute != "run": setattr(self, attribute, value) if attribute not in ["run", "space"] and not getattr(self, attribute): @@ -236,7 +236,7 @@ def get_config(config_file: Path | None = None, default: str = "") -> dict[str, my_path = Path(__file__).absolute().parent / "config" config_file = my_path / default - if config_file is None or not Path(config_file).exists(): + if not Path(config_file).exists(): raise FileNotFoundError(f"Config file {config_file} not found") with open(config_file) as ff: diff --git a/bidsmreye/methods.py b/bidsmreye/methods.py index fe9c7cb..61e6f14 100644 --- a/bidsmreye/methods.py +++ b/bidsmreye/methods.py @@ -29,7 +29,7 @@ def methods( :rtype: Path """ if output_dir is None: - output_dir = Path(".") + output_dir = Path() if isinstance(output_dir, str): output_dir = Path(output_dir) output_dir = output_dir / "logs" diff --git a/bidsmreye/quality_control.py b/bidsmreye/quality_control.py index 18bb072..ac2d349 100644 --- a/bidsmreye/quality_control.py +++ b/bidsmreye/quality_control.py @@ -325,7 +325,7 @@ def compute_robust_outliers( indices.pop(i) tmp = time_series[indices] - tmp.dropna(inplace=True) + tmp = tmp.dropna() # median of all pair-wise distances distance.append(np.median(abs(this_timepoint - tmp))) diff --git a/bidsmreye/report.py b/bidsmreye/report.py index 30576cf..7cd88b2 100644 --- a/bidsmreye/report.py +++ b/bidsmreye/report.py @@ -66,7 +66,6 @@ def generate_report(output_dir: Path, subject_label: str, action: str) -> None: if __name__ == "__main__": - cwd = Path("/home/remi/github/cpp-lln-lab/bidsMReye") output_dir = cwd / "outputs" / "moae_fmriprep" / "derivatives" / "bidsmreye" diff --git a/bidsmreye/visualize.py b/bidsmreye/visualize.py index 23797ca..6c1e63e 100644 --- a/bidsmreye/visualize.py +++ b/bidsmreye/visualize.py @@ -17,14 +17,14 @@ from bidsmreye.utils import check_if_file_found, set_this_filter LINE_WIDTH = 3 -FONT_SIZE = dict(size=14) +FONT_SIZE = {"size": 14} GRID_COLOR = "grey" LINE_COLOR = "rgb(0, 150, 175)" BG_COLOR = "rgb(255,255,255)" HEAT_MAP_COLOR = "gnbu" MARKER_SIZE = 10 -TICK_FONT = dict(family="arial", color="black", size=14) +TICK_FONT = {"family": "arial", "color": "black", "size": 14} X_POSITION_1 = 1 X_POSITION_2 = 1.5 @@ -113,7 +113,7 @@ def plot_group_boxplot( go.Box( x=np.ones(nb_data_points) * X_POSITION[i], y=qc_data[this_column], - marker=dict(size=MARKER_SIZE, color=COLORS[i]), + marker={"size": MARKER_SIZE, "color": COLORS[i]}, name=trace_names[i], ), row=row, @@ -128,7 +128,7 @@ def plot_group_boxplot( fig.update_yaxes( row=row, col=col, - title=dict(text=yaxes_title, font=FONT_SIZE), + title={"text": yaxes_title, "font": FONT_SIZE}, ) @@ -186,7 +186,7 @@ def group_report(cfg: Config) -> None: ) fig.update_yaxes( - title=dict(standoff=0, font=FONT_SIZE), + title={"standoff": 0, "font": FONT_SIZE}, showline=True, linewidth=LINE_WIDTH - 1, linecolor="black", @@ -215,9 +215,9 @@ def group_report(cfg: Config) -> None: boxmean=True, width=0.2, hovertext=qc_data["filename"], - marker=dict(size=MARKER_SIZE), + marker={"size": MARKER_SIZE}, fillcolor="rgb(200, 200, 200)", - line=dict(color="black"), + line={"color": "black"}, ) fig.update_layout( @@ -226,17 +226,17 @@ def group_report(cfg: Config) -> None: paper_bgcolor=BG_COLOR, height=800, width=800, - title=dict( - text=f"""bidsmreye: group report
+ title={ + "text": f"""bidsmreye: group report
Summary
- Date and time: {datetime.now():%Y-%m-%d, %H:%M}
- bidsmreye version: {__version__}
""", - x=0.05, - y=0.95, - font=dict(size=19, color="black"), - ), - margin=dict(t=150, b=10, l=100, r=10, pad=0), + "x": 0.05, + "y": 0.95, + "font": {"size": 19, "color": "black"}, + }, + margin={"t": 150, "b": 10, "l": 100, "r": 10, "pad": 0}, ) fig.show() @@ -288,7 +288,7 @@ def visualize_eye_gaze_data( fig.update_xaxes( row=3, col=1, - title=dict(text="Time (s)", standoff=16, font=FONT_SIZE), + title={"text": "Time (s)", "standoff": 16, "font": FONT_SIZE}, tickfont=TICK_FONT, ) @@ -376,7 +376,7 @@ def plot_time_series( griddash="dot", gridwidth=0.5, ticksuffix="°", - title=dict(text=title_text, standoff=0, font=FONT_SIZE), + title={"text": title_text, "standoff": 0, "font": FONT_SIZE}, tickfont=FONT_SIZE, ) @@ -428,7 +428,7 @@ def plot_heat_map(fig: Any, eye_gaze_data: pd.DataFrame) -> None: x=X, y=Y, opacity=0.4, - line=dict(color="black", width=1, dash="dash"), + line={"color": "black", "width": 1, "dash": "dash"}, ), row=1, col=3, @@ -448,7 +448,7 @@ def plot_heat_map(fig: Any, eye_gaze_data: pd.DataFrame) -> None: col=3, range=value_range(X), ticksuffix="°", - title=dict(text="X", standoff=16, font=FONT_SIZE), + title={"text": "X", "standoff": 16, "font": FONT_SIZE}, tickfont=TICK_FONT, ) fig.update_yaxes( @@ -456,7 +456,7 @@ def plot_heat_map(fig: Any, eye_gaze_data: pd.DataFrame) -> None: col=3, range=value_range(Y), ticksuffix="°", - title=dict(text="Y", standoff=16, font=FONT_SIZE), + title={"text": "Y", "standoff": 16, "font": FONT_SIZE}, tickfont=TICK_FONT, ) diff --git a/pyproject.toml b/pyproject.toml index 809296c..6beb246 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,22 +113,18 @@ layers = [ name = "Layered architecture" type = "layers" -[tool.isort] -combine_as_imports = true -line_length = 90 -profile = "black" -skip_gitignore = true - [tool.mypy] check_untyped_defs = true disallow_any_generics = true disallow_incomplete_defs = false disallow_untyped_defs = false -# enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +enable_error_code = ["redundant-expr", "truthy-bool"] +# enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] # TODO exclude = ['tests/'] no_implicit_optional = true plugins = ["numpy.typing.mypy_plugin", "pydantic.mypy"] warn_redundant_casts = true +# warn_unreachable = true # TODO warn_unused_ignores = true [[tool.mypy.overrides]] @@ -161,8 +157,112 @@ warn_untyped_fields = true [tool.pytest.ini_options] addopts = "-ra --cov bidsmreye --strict-config --strict-markers --doctest-modules --showlocals -s -vv --durations=0" doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS" +filterwarnings = ["error"] junit_family = "xunit2" log_cli_level = "INFO" +log_level = "INFO" minversion = "6.0" testpaths = ["tests"] xfail_strict = true + +[tool.ruff] +extend-exclude = ["bidsmreye/_version.py"] +include = [ + "pyproject.toml", + "bidsmreye/**/*.py", + "tools/**/*.py", + "doc/**/*.py" +] +indent-width = 4 +line-length = 90 + +[tool.ruff.format] +docstring-code-format = true +docstring-code-line-length = "dynamic" +indent-style = "space" +line-ending = "auto" +quote-style = "double" +skip-magic-trailing-comma = false + +[tool.ruff.lint] +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +fixable = ["ALL"] +ignore = [ + "ARG001", + "ARG002", + "ERA001", + "D100", + "D103", + "D203", + "D213", + "N802", + "N803", + "N806", + "N816", + "N815", + "PLR2004", + "PD003", + "PGH003", + "PTH100", + "PTH107", + "PTH103", + "PTH123", + "SIM115", + "NPY002", + # Avoid linter rules conflicting with the formatter + # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules + "COM812", + "COM819", + "E111", + "E114", + "E117", + "Q000", + "Q001", + "Q002", + "Q003", + "W191" +] +# List of all the ruff rules (includes why the rule matters) +# https://docs.astral.sh/ruff/rules/ +select = [ + "ARG", + "B", + "C4", + "C90", + "D", + "E", + "ERA", + "F", + "FLY", + "FURB", + "I", + "N", + "NPY", + "PERF", + "PIE", + "PTH", + "PD", + "PGH", + "PLR", + "RUF", + "SIM", + "UP", + "W" +] +unfixable = [] + +[tool.ruff.lint.mccabe] +max-complexity = 15 + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["D104"] + +[tool.ruff.lint.pylint] +# https://docs.astral.sh/ruff/settings/#lint_pylint_max-args +max-args = 27 +# https://docs.astral.sh/ruff/settings/#lint_pylint_max-branches +max-branches = 22 +# https://docs.astral.sh/ruff/settings/#lint_pylint_max-returns +max-returns = 8 +# https://docs.astral.sh/ruff/settings/#lint_pylint_max-statements +max-statements = 96 From c983c48a228f68c76c3356f1cb7893c0bc04176b Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Tue, 3 Mar 2026 10:18:16 +0100 Subject: [PATCH 2/3] lower complexity thresholds --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6beb246..43be542 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -259,10 +259,10 @@ max-complexity = 15 [tool.ruff.lint.pylint] # https://docs.astral.sh/ruff/settings/#lint_pylint_max-args -max-args = 27 +max-args = 15 # https://docs.astral.sh/ruff/settings/#lint_pylint_max-branches -max-branches = 22 +max-branches = 10 # https://docs.astral.sh/ruff/settings/#lint_pylint_max-returns -max-returns = 8 +max-returns = 6 # https://docs.astral.sh/ruff/settings/#lint_pylint_max-statements -max-statements = 96 +max-statements = 80 From 8d09b79f742f5a67d4cd08dbb6d15f34a65d1779 Mon Sep 17 00:00:00 2001 From: Remi Gau Date: Mon, 23 Mar 2026 14:37:28 +0100 Subject: [PATCH 3/3] Apply suggestion from @Remi-Gau --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 43be542..b06e3bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,7 +157,6 @@ warn_untyped_fields = true [tool.pytest.ini_options] addopts = "-ra --cov bidsmreye --strict-config --strict-markers --doctest-modules --showlocals -s -vv --durations=0" doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS" -filterwarnings = ["error"] junit_family = "xunit2" log_cli_level = "INFO" log_level = "INFO"