diff --git a/src/bids/layout/tests/conftest.py b/src/bids/layout/tests/conftest.py index d5959045..6451f893 100644 --- a/src/bids/layout/tests/conftest.py +++ b/src/bids/layout/tests/conftest.py @@ -1,4 +1,5 @@ from os.path import join +import shutil import pytest @@ -101,3 +102,10 @@ def layout_synthetic(tests_dir, request, db_dir): def layout_synthetic_nodb(tests_dir, request, db_dir): path = tests_dir / 'data' / 'synthetic' return BIDSLayout(path, derivatives=True) + + +@pytest.fixture +def temporary_dataset(tmp_path, tests_dir): + path = tests_dir / 'data' / 'ds005' + shutil.copytree(path, tmp_path / 'ds005') + return tmp_path / 'ds005' diff --git a/src/bids/layout/tests/test_layout.py b/src/bids/layout/tests/test_layout.py index c0c7089a..737db4a1 100644 --- a/src/bids/layout/tests/test_layout.py +++ b/src/bids/layout/tests/test_layout.py @@ -1170,3 +1170,38 @@ def test_symlinks_in_path(tests_dir, tmp_path): os.symlink(src_sub, link_sub) assert "Subjects: 1 | Sessions: 2 | Runs: 2" in str(BIDSLayout(tmp_path / "7t_trt")) + + +def test_ignore_dotfiles(temporary_dataset): + arbitrary_dotfile = temporary_dataset / '.dotfile' + ds_store = temporary_dataset / 'sub-01' / '.DS_Store' + osx_companion_file = temporary_dataset / '._task-mixedgamblestask_bold.json' + + arbitrary_dotfile.touch() + ds_store.touch() + osx_companion_file.touch() + + # Default behavior + layout = BIDSLayout(temporary_dataset, validate=False) + assert str(temporary_dataset / 'dataset_description.json') in layout.files + assert str(arbitrary_dotfile) not in layout.files + assert str(ds_store) not in layout.files + assert str(osx_companion_file) not in layout.files + + # Explicit ignores do not disable dotfile filtering + indexer = BIDSLayoutIndexer(ignore=['some_ignore']) + layout = BIDSLayout(temporary_dataset, validate=False, indexer=indexer) + assert str(temporary_dataset / 'dataset_description.json') in layout.files + assert str(arbitrary_dotfile) not in layout.files + assert str(ds_store) not in layout.files + assert str(osx_companion_file) not in layout.files + + +def test_empty_directory(temporary_dataset): + anat = temporary_dataset / 'sub-01' / 'anat' + shutil.rmtree(anat) + anat.mkdir() + + layout = BIDSLayout(temporary_dataset) + + assert layout.get(subject='01', datatype='anat') == [] diff --git a/src/bids/layout/validation.py b/src/bids/layout/validation.py index 50732811..cbf4def9 100644 --- a/src/bids/layout/validation.py +++ b/src/bids/layout/validation.py @@ -32,9 +32,12 @@ DEFAULT_LOCATIONS_TO_IGNORE = { re.compile(r"^/(code|models|sourcedata|stimuli)"), - re.compile(r'/\.'), } +ALWAYS_IGNORE = ( + re.compile(r'/\.'), # dotfiles should never be indexed +) + def absolute_path_deprecation_warning(): warnings.warn("The absolute_paths argument will be removed from PyBIDS " "in 0.14. You can easily access the relative path of " @@ -156,9 +159,11 @@ def _sort_patterns(patterns, root): def validate_indexing_args(ignore, force_index, root): if ignore is None: - ignore = list( - DEFAULT_LOCATIONS_TO_IGNORE - set(force_index or []) - ) + ignore = DEFAULT_LOCATIONS_TO_IGNORE - set(force_index or []) + + ignore = list(ignore) + + ignore.extend(ALWAYS_IGNORE) # root has already been validated to be a directory ignore = _sort_patterns(ignore, root)