From eea57208befb3f7d577fee62b8326abba65f47f9 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Mon, 27 May 2024 18:56:46 -0400 Subject: [PATCH 1/3] chore: Add pre-commit configuration --- .pre-commit-config.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..86b96a3 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-added-large-files + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.4.3 + hooks: + - id: ruff + args: [ --fix ] + - id: ruff-format + - id: ruff + args: [ --select, ISC001, --fix ] From 0b8a3a048737b45e47aaa968e7f968fcb2970056 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Mon, 27 May 2024 19:01:27 -0400 Subject: [PATCH 2/3] chore: Configure ruff and disable black --- pyproject.toml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c0a26d2..855693a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,3 +67,47 @@ exclude_lines = [ "if __name__ == .__main__.:", "if TYPE_CHECKING:", ] + +# Disable black +[tool.black] +exclude = ".*" + +[tool.ruff] +line-length = 99 +extend-exclude = ["_version.py"] + +[tool.ruff.lint] +extend-select = [ + "F", + "E", + "W", + "I", + "UP", + "YTT", + "S", + "BLE", + "B", + "A", + # "CPY", + "C4", + "DTZ", + "T10", + # "EM", + "EXE", + "ISC", + "ICN", + "PT", + "Q", +] +ignore = [ + "ISC001", +] + +[tool.ruff.lint.flake8-quotes] +inline-quotes = "single" + +[tool.ruff.lint.extend-per-file-ignores] +"*/test_*.py" = ["S101"] + +[tool.ruff.format] +quote-style = "single" From 52e4d95693435d18be4d787642ab0d2227f15614 Mon Sep 17 00:00:00 2001 From: Chris Markiewicz Date: Mon, 27 May 2024 19:04:47 -0400 Subject: [PATCH 3/3] chore: Apply ruff fixes and format --- src/bids_validator/__init__.py | 3 + src/bids_validator/bids_validator.py | 7 +- src/bids_validator/test_bids_validator.py | 152 ++++++++++++---------- 3 files changed, 92 insertions(+), 70 deletions(-) diff --git a/src/bids_validator/__init__.py b/src/bids_validator/__init__.py index cf39560..d380698 100644 --- a/src/bids_validator/__init__.py +++ b/src/bids_validator/__init__.py @@ -1,6 +1,9 @@ """BIDS validator common Python package.""" + from .bids_validator import BIDSValidator + __all__ = ['BIDSValidator'] from . import _version + __version__ = _version.get_versions()['version'] diff --git a/src/bids_validator/bids_validator.py b/src/bids_validator/bids_validator.py index 846de9d..2d2a9e0 100644 --- a/src/bids_validator/bids_validator.py +++ b/src/bids_validator/bids_validator.py @@ -1,4 +1,5 @@ """Validation class for BIDS projects.""" + import logging import os import re @@ -139,8 +140,10 @@ def parse(cls, path): path = path.replace(os.sep, '/') if not path.startswith('/'): - raise ValueError("Path must be relative to root of a BIDS dataset," - " and must include a leading forward slash `/`.") + raise ValueError( + 'Path must be relative to root of a BIDS dataset,' + ' and must include a leading forward slash `/`.' + ) for regex in cls.regexes: match = re.match(regex, path[1:]) diff --git a/src/bids_validator/test_bids_validator.py b/src/bids_validator/test_bids_validator.py index 2cd83c5..32c46d6 100644 --- a/src/bids_validator/test_bids_validator.py +++ b/src/bids_validator/test_bids_validator.py @@ -7,18 +7,16 @@ import os -import pytest import datalad.api +import pytest from bids_validator import BIDSValidator HOME = os.path.expanduser('~') TEST_DATA_DICT = { - 'eeg_matchingpennies': ( - 'https://gin.g-node.org/sappelhoff/eeg_matchingpennies' - ), - } + 'eeg_matchingpennies': ('https://gin.g-node.org/sappelhoff/eeg_matchingpennies'), +} EXCLUDE_KEYWORDS = ['git', 'datalad', 'sourcedata', 'bidsignore'] @@ -61,94 +59,112 @@ def test_datasets(validator, fname): assert validator.is_bids(fname) -@pytest.mark.parametrize('fname, matches', [ - ('/T1w.json', True), - ('/dataset_description.json', True), - ('/README', True), - ('/CHANGES', True), - ('/participants.tsv', True), - ('/sub-01/anat/sub-01_T1w.nii.gz', False), -]) +@pytest.mark.parametrize( + ('fname', 'matches'), + [ + ('/T1w.json', True), + ('/dataset_description.json', True), + ('/README', True), + ('/CHANGES', True), + ('/participants.tsv', True), + ('/sub-01/anat/sub-01_T1w.nii.gz', False), + ], +) def test_top_level(validator, fname, matches): """Test that is_top_level returns true for top-level files.""" assert validator.is_top_level(fname) is matches -@pytest.mark.parametrize('fname, matches', [ - ('/sourcedata/unstructured_data.nii.gz', True), - ('/sourcedata/dicom_dir/xyz.dcm', True), - ('/code/my_analysis/analysis.py', True), - ('/derivatives/preproc/sub-01/anat/sub-01_desc-preproc_T1w.nii.gz', True), - ('/stimuli/pic.jpg', True), - ('/sub-01/anat/sub-01_T1w.nii.gz', False), -]) +@pytest.mark.parametrize( + ('fname', 'matches'), + [ + ('/sourcedata/unstructured_data.nii.gz', True), + ('/sourcedata/dicom_dir/xyz.dcm', True), + ('/code/my_analysis/analysis.py', True), + ('/derivatives/preproc/sub-01/anat/sub-01_desc-preproc_T1w.nii.gz', True), + ('/stimuli/pic.jpg', True), + ('/sub-01/anat/sub-01_T1w.nii.gz', False), + ], +) def test_associated_data(validator, fname, matches): """Test that is_associated_data returns true for associated data.""" assert validator.is_associated_data(fname) is matches -@pytest.mark.parametrize('fname, matches', [ - ('/sub-01/ses-1/sub-01_ses-1_scans.tsv', True), - ('/sub-01/ses-1/sub-01_ses-1_scans.json', True), - ('/sub-01/sub-01_scans.tsv', True), - ('/sub-01/sub-01_scans.json', True), - ('/sub-01/ses-1/sub-01_ses-1_task-rest_bold.json', True), - ('/sub-01/sub-01_task-rest_bold.json', True), - ('/sub-01/ses-1/sub-01_ses-1_asl.json', True), - ('/sub-01/sub-01_asl.json', True), - ('/sub-01/ses-1/sub-01_ses-1_pet.json', True), - ('/sub-01/sub-01_pet.json', True), - ('/sub-01/ses-1/sub-01_ses-1_proc-test_channels.tsv', True), - ('/sub-01/ses-1/sub-01_ses-1_channels.json', True), - ('/sub-01/sub-01_proc-test_channels.tsv', True), - ('/sub-01/sub-01_channels.json', True), - ('/sub-01/ses-1/sub-01_ses-1_space-CapTrak_electrodes.tsv', True), - ('/sub-01/ses-1/sub-01_ses-1_coordsystem.json', True), - ('/sub-01/sub-01_space-CapTrak_electrodes.tsv', True), - ('/sub-01/sub-01_coordsystem.json', True), - ('/sub-01/ses-1/sub-01_ses-1_motion.json', True), - ('/sub-01/sub-01_motion.json', True), - ('/sub-01/ses-1/sub-01_ses-1_TEM.json', True), - ('/sub-01/sub-01_TEM.json', True), - ('/sub-01/ses-1/sub-01_ses-1_nirs.json', True), - ('/sub-01/sub-01_nirs.json', True), - # Mismatch sessions - ('/sub-01/sub-01_ses-1_scans.tsv', False), - ('/sub-01/sub-01_ses-1_scans.json', False), - ('/sub-01/ses-1/sub-01_ses-2_scans.tsv', False), - # File-level - ('/sub-01/ses-1/func/sub-01_ses-1_task-rest_bold.nii.gz', False), - ('/sub-01/anat/sub-01_T1w.nii.gz', False), -]) +@pytest.mark.parametrize( + ('fname', 'matches'), + [ + ('/sub-01/ses-1/sub-01_ses-1_scans.tsv', True), + ('/sub-01/ses-1/sub-01_ses-1_scans.json', True), + ('/sub-01/sub-01_scans.tsv', True), + ('/sub-01/sub-01_scans.json', True), + ('/sub-01/ses-1/sub-01_ses-1_task-rest_bold.json', True), + ('/sub-01/sub-01_task-rest_bold.json', True), + ('/sub-01/ses-1/sub-01_ses-1_asl.json', True), + ('/sub-01/sub-01_asl.json', True), + ('/sub-01/ses-1/sub-01_ses-1_pet.json', True), + ('/sub-01/sub-01_pet.json', True), + ('/sub-01/ses-1/sub-01_ses-1_proc-test_channels.tsv', True), + ('/sub-01/ses-1/sub-01_ses-1_channels.json', True), + ('/sub-01/sub-01_proc-test_channels.tsv', True), + ('/sub-01/sub-01_channels.json', True), + ('/sub-01/ses-1/sub-01_ses-1_space-CapTrak_electrodes.tsv', True), + ('/sub-01/ses-1/sub-01_ses-1_coordsystem.json', True), + ('/sub-01/sub-01_space-CapTrak_electrodes.tsv', True), + ('/sub-01/sub-01_coordsystem.json', True), + ('/sub-01/ses-1/sub-01_ses-1_motion.json', True), + ('/sub-01/sub-01_motion.json', True), + ('/sub-01/ses-1/sub-01_ses-1_TEM.json', True), + ('/sub-01/sub-01_TEM.json', True), + ('/sub-01/ses-1/sub-01_ses-1_nirs.json', True), + ('/sub-01/sub-01_nirs.json', True), + # Mismatch sessions + ('/sub-01/sub-01_ses-1_scans.tsv', False), + ('/sub-01/sub-01_ses-1_scans.json', False), + ('/sub-01/ses-1/sub-01_ses-2_scans.tsv', False), + # File-level + ('/sub-01/ses-1/func/sub-01_ses-1_task-rest_bold.nii.gz', False), + ('/sub-01/anat/sub-01_T1w.nii.gz', False), + ], +) def test_session_level(validator, fname, matches): """Test that is_session_level returns true for session level files.""" assert validator.is_session_level(fname) is matches -@pytest.mark.parametrize('fname, matches', [ - ('/sub-01/sub-01_sessions.tsv', True), - ('/sub-01/sub-01_sessions.json', True), - ('/sub-01/anat/sub-01_T1w.nii.gz', False), -]) +@pytest.mark.parametrize( + ('fname', 'matches'), + [ + ('/sub-01/sub-01_sessions.tsv', True), + ('/sub-01/sub-01_sessions.json', True), + ('/sub-01/anat/sub-01_T1w.nii.gz', False), + ], +) def test_subject_level(validator, fname, matches): """Test that is_subject_level returns true for subject level files.""" assert validator.is_subject_level(fname) is matches -@pytest.mark.parametrize('fname, matches', [ - ('/phenotype/measure.tsv', True), - ('/phenotype/measure.json', True), - ('/sub-01/anat/sub-01_T1w.nii.gz', False), -]) +@pytest.mark.parametrize( + ('fname', 'matches'), + [ + ('/phenotype/measure.tsv', True), + ('/phenotype/measure.json', True), + ('/sub-01/anat/sub-01_T1w.nii.gz', False), + ], +) def test_phenotpic(validator, fname, matches): """Test that is_phenotypic returns true for phenotypic files.""" assert validator.is_phenotypic(fname) is matches -@pytest.mark.parametrize('fname, matches', [ - ('/sub-01/ses-1/func/sub-01_ses-1_task-rest_bold.nii.gz', True), - ('/sub-01/anat/sub-01_T1w.nii.gz', True), -]) +@pytest.mark.parametrize( + ('fname', 'matches'), + [ + ('/sub-01/ses-1/func/sub-01_ses-1_task-rest_bold.nii.gz', True), + ('/sub-01/anat/sub-01_T1w.nii.gz', True), + ], +) def test_file_level(validator, fname, matches): """Test that is_file returns true for file level files.""" assert validator.is_file(fname) is matches