Repository: bids-standard/pybids

Issue:
get_metadata function and .git directories from datalad datasets
Hi,
In testing my BIDS -> NIDM converter on an openfmri dataset retrieved using datalad, I find that the get_metadata function searches the .git directory embedded in the dataset.  I've changed my code to ignore directories starting with "." but it appears the get_metadata function is searching the whole BIDS directory tree when looking for metadata and doesn't ignore .git.  The error:

json_data = bids_layout.get_metadata(file_tpl.filename)
  File "/Users/dbkeator/anaconda/envs/python35/lib/python3.5/site-packages/pybids-0.1-py3.5.egg/bids/grabbids/bids_layout.py", line 33, in get_metadata
    " project." % path)
ValueError: File '/Users/dbkeator/Documents/datasets.datalad.org/openfmri/ds000030/.git/annex/objects/KM/vk/MD5E-s11637742--75cab8005361c2504ba5a7f02ecbacd7.nii.gz/MD5E-s11637742--75cab8005361c2504ba5a7f02ecbacd7.nii.gz' could not be found in the current BIDS project.


Discussion:
Thanks for reporting - could you check this branch - https://github.com/INCF/pybids/pull/65?
Hi,
I don't think this will solve my issue.  I am getting valid BIDS filenames and the get_metadata function, when called, is searching the directory tree and getting hung up on the .git folders.  

Basically my code that results in this error is:
for file_tpl in bids_layout.get(subject=subject_id, extensions=['.nii', '.nii.gz']):
     json_data = bids_layout.get_metadata(file_tpl.filename)

AFAIK non bids files will not be index on the branch I mentioned (right @tyarkoni?)
@chrisfilo @tyarkoni If you set index_associated = False it should ignore .git files. But I've just realized that we didn't cover it in our tests. We'll check it with @hyperswitcher and add tests to the validator.  
Yes @chrisfilo, I think that's right. As soon as I release the next version of grabbit and grabbids, there will be new `include` and `exclude` keys in the config (thanks to @adelavega's PR) that should allow you to manually exclude directories (though hopefully @lodurality's suggestion works as well, as it's probably a better place to start).
@tyarkoni @chrisfilo @hyperswitcher pushed additional tests and it shouldn't index .git folder if index_associated=False.  
Didn't see the code yet, but may be don't index any .directory, not just .git please ( .datalad and .heudiconv for me)
@lodurality When I add the function parameter  index_associated = False to get_metadata I now don't receive an json data as I did with that parameter excluded?  
Relevant code:

```python
for file_tpl in bids_layout.get(subject=subject_id, extensions=['.nii', '.nii.gz']):
    json_data = bids_layout.get_metadata(file_tpl.filename, index_associated = False)
```
The `index_associated` argument should occur in the `BIDSLayout` initialization, not in the `get_metadata` call. That said, that won't work either right now, because it looks like the `BIDSLayout` isn't set up to internally pass that argument onto the `BIDSValidator`. I'll fix this shortly. Thanks for reporting this!

Resolving patch:
diff --git a/bids/grabbids/bids_layout.py b/bids/grabbids/bids_layout.py
--- a/bids/grabbids/bids_layout.py
+++ b/bids/grabbids/bids_layout.py
@@ -8,12 +8,14 @@
 from .bids_validator import BIDSValidator
 from grabbit import Layout
 
-__all__ = ['BIDSLayout', 'BIDSValidator']
+__all__ = ['BIDSLayout']
 
 
 class BIDSLayout(Layout):
-    def __init__(self, path, config=None, validate=False, **kwargs):
-        self.validator = BIDSValidator()
+
+    def __init__(self, path, config=None, validate=False,
+                 index_associated=True, **kwargs):
+        self.validator = BIDSValidator(index_associated=index_associated)
         self.validate = validate
         if config is None:
             root = dirname(abspath(__file__))
@@ -59,6 +61,17 @@ def _get_nearest_helper(self, path, extension, type=None, **kwargs):
             return None
 
     def get_metadata(self, path, **kwargs):
+        ''' Returns metadata found in JSON sidecars for the specified file.
+        Args:
+            path (str): Path to the file to get metadata for.
+            kwargs (dict): Optional keyword arguments to pass onto
+                get_nearest().
+        Notes:
+            A dictionary containing metadata extracted from all matching .json
+            files is returned. In cases where the same key is found in multiple
+            files, the values in files closer to the input filename will take
+            precedence, per the inheritance rules in the BIDS specification.
+        '''
         potentialJSONs = self._get_nearest_helper(path, '.json', **kwargs)
 
         if not isinstance(potentialJSONs, list):
diff --git a/bids/grabbids/bids_validator.py b/bids/grabbids/bids_validator.py
--- a/bids/grabbids/bids_validator.py
+++ b/bids/grabbids/bids_validator.py
@@ -1,18 +1,20 @@
-import os
 import re
 
-__all__ = ['BIDSLayout', 'BIDSValidator']
+__all__ = ['BIDSValidator']
+
 
 class BIDSValidator():
     """An object for BIDS (Brain Imaging Data Structure) verification in a data.
 
-    The main method of this class is `is_bids()`. You should use it for checking whether a file path compatible with BIDS.
+    The main method of this class is `is_bids()`. You should use it for
+    checking whether a file path compatible with BIDS.
 
     Parameters
     ----------
     index_associated : bool, default: True
-        Specifies if an associated data should be checked. If it is true then any file paths in directories `code/`,
-        `derivatives/`, `sourcedata/` and `stimuli/` will pass the validation, else they won't.
+        Specifies if an associated data should be checked. If it is true then
+        any file paths in directories `code/`, `derivatives/`, `sourcedata/`
+        and `stimuli/` will pass the validation, else they won't.
 
     Examples
     --------
@@ -28,17 +30,18 @@ class BIDSValidator():
     True
     """
 
-    def __init__(self, index_associated = True):
-        self.anat_suffixes =  ["T1w", "T2w", "T1map", "T2map",
-                               "T1rho", "FLAIR", "PD", "PDT2",
-                               "inplaneT1", "inplaneT2","angio",
-                               "defacemask", "SWImagandphase"]
+    def __init__(self, index_associated=True):
+        self.anat_suffixes = ["T1w", "T2w", "T1map", "T2map",
+                              "T1rho", "FLAIR", "PD", "PDT2",
+                              "inplaneT1", "inplaneT2", "angio",
+                              "defacemask", "SWImagandphase"]
         self.index_associated = index_associated
 
     def is_bids(self, path):
         """Checks if a file path appropriate for BIDS.
 
-        Main method of the validator. uses other class methods for checking different aspects of the file path.
+        Main method of the validator. uses other class methods for checking
+        different aspects of the file path.
 
         Parameters
         ----------
@@ -51,199 +54,197 @@ def is_bids(self, path):
         >>> validator = BIDSValidator()
         >>> validator.is_bids("/sub-01/ses-test/anat/sub-01_ses-test_rec-CSD_run-23_T1w.nii.gz")
         True
-        >>> validator.is_bids("/sub-01/ses-test/sub-01_run-01_dwi.bvec") #missed session in the filename
+        >>> validator.is_bids("/sub-01/ses-test/sub-01_run-01_dwi.bvec") # missed session in the filename
         False
         """
 
         return (
-            self.is_top_level(path)       |
+            self.is_top_level(path) |
             self.is_associated_data(path) |
-            self.is_session_level(path)   |
-            self.is_subject_level(path)   |
-            self.is_anat(path)            |
-            self.is_dwi(path)             |
-            self.is_func(path)            |
-            self.is_behavioral(path)      |
-            self.is_cont(path)            |
-            self.is_field_map(path)       |
+            self.is_session_level(path) |
+            self.is_subject_level(path) |
+            self.is_anat(path) |
+            self.is_dwi(path) |
+            self.is_func(path) |
+            self.is_behavioral(path) |
+            self.is_cont(path) |
+            self.is_field_map(path) |
             self.is_phenotypic(path)
         )
 
-
-    #Check if the file has appropriate name for a top level file#
     def is_top_level(self, path):
-
+        ''' Check if the file has appropriate name for a top-level file. '''
         fixed_top_level_names = ["/README", "/CHANGES", "/dataset_description.json", "/participants.tsv",
-            "/participants.json", "/phasediff.json", "/phase1.json", "/phase2.json" ,"/fieldmap.json"]
+                                 "/participants.json", "/phasediff.json", "/phase1.json", "/phase2.json", "/fieldmap.json"]
 
         func_top_re = re.compile('^\\/(?:ses-[a-zA-Z0-9]+_)?(?:recording-[a-zA-Z0-9]+_)?task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_echo-[0-9]+)?'
-            + '(_bold.json|_sbref.json|_events.json|_events.tsv|_physio.json|_stim.json|_beh.json)$')
+                                 + '(_bold.json|_sbref.json|_events.json|_events.tsv|_physio.json|_stim.json|_beh.json)$')
         func_top_flag = False if func_top_re.search(path) is None else True
 
         anat_top_re = re.compile('^\\/(?:ses-[a-zA-Z0-9]+_)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+_)?'
-            + '(' + "|".join(self.anat_suffixes) + ').json$')
+                                 + '(' + "|".join(self.anat_suffixes) + ').json$')
         anat_top_flag = False if anat_top_re.search(path) is None else True
 
         dwi_top_re = re.compile('^\\/(?:ses-[a-zA-Z0-9]+)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_)?'
-            + 'dwi.(?:json|bval|bvec)$')
+                                + 'dwi.(?:json|bval|bvec)$')
         dwi_top_flag = False if dwi_top_re.search(path) is None else True
 
-        multi_dir_fieldmap_re = re.compile('^\\/(?:dir-[a-zA-Z0-9]+)_epi.json$')
-        multi_dir_fieldmap_flag = False if multi_dir_fieldmap_re.search(path) is None else True
+        multi_dir_fieldmap_re = re.compile(
+            '^\\/(?:dir-[a-zA-Z0-9]+)_epi.json$')
+        multi_dir_fieldmap_flag = False if multi_dir_fieldmap_re.search(
+            path) is None else True
 
         other_top_files_re = re.compile('^\\/(?:ses-[a-zA-Z0-9]+_)?(?:recording-[a-zA-Z0-9]+)?(?:task-[a-zA-Z0-9]+_)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?'
-            + '(_physio.json|_stim.json)$')
-        other_top_files_flag = False if other_top_files_re.search(path) is None else True
+                                        + '(_physio.json|_stim.json)$')
+        other_top_files_flag = False if other_top_files_re.search(
+            path) is None else True
 
         check_index = path in fixed_top_level_names
         return (check_index |
                 func_top_flag | anat_top_flag |
-        dwi_top_flag | multi_dir_fieldmap_flag | other_top_files_flag)
+                dwi_top_flag | multi_dir_fieldmap_flag | other_top_files_flag)
 
-    #Check if file is appropriate associated data.
     def is_associated_data(self, path):
-        associated_data_re = re.compile('^\\/(?:code|derivatives|sourcedata|stimuli)\\/(?:.*)$')
+        ''' Check if file is appropriate associated data. '''
+        if not self.index_associated:
+            return False
+        associated_data_re = re.compile(
+            '^\\/(?:code|derivatives|sourcedata|stimuli)\\/(?:.*)$')
         associated_data_flag = associated_data_re.search(path)
-        associated_data_flag = associated_data_flag is not None
-        return associated_data_flag and self.index_associated
-
+        return associated_data_flag is not None
 
-    #Check if file is phenotypic data.
     def is_phenotypic(self, path):
+        ''' Check if file is phenotypic data. '''
         phenotypic_data = re.compile('^\\/(?:phenotype)\\/(?:.*.tsv|.*.json)$')
         return phenotypic_data.search(path) is not None
 
-
-    #Check if the file has appropriate name for a session level
-
     def is_session_level(self, path):
+        ''' Check if the file has appropriate name for a session level. '''
         scans_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?\\1(_\\2)?(_scans.tsv|_scans.json)$')
+                              '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                              '\\/)?\\1(_\\2)?(_scans.tsv|_scans.json)$')
 
         func_ses_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?\\1(_\\2)?task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_echo-[0-9]+)?'
-            + '(_bold.json|_sbref.json|_events.json|_events.tsv|_physio.json|_stim.json)$')
+                                 '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                                 '\\/)?\\1(_\\2)?task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_echo-[0-9]+)?'
+                                 + '(_bold.json|_sbref.json|_events.json|_events.tsv|_physio.json|_stim.json)$')
 
         anat_ses_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+_)?'
-            + '(' + ("|").join(self.anat_suffixes) + ').json$')
+                                 '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                                 '\\/)?\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+_)?'
+                                 + '(' + ("|").join(self.anat_suffixes) + ').json$')
 
         dwi_ses_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_)?'
-            + 'dwi.(?:json|bval|bvec)$')
+                                '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                                '\\/)?\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_)?'
+                                + 'dwi.(?:json|bval|bvec)$')
 
         return (self.conditional_match(scans_re, path) |
                 self.conditional_match(func_ses_re, path) |
                 self.conditional_match(anat_ses_re, path) |
                 self.conditional_match(dwi_ses_re, path))
 
-
-     #Check if the file has appropriate name for a subject level
-
     def is_subject_level(self, path):
+        ''' Check if the file has appropriate name for a subject level. '''
         scans_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/\\1(_sessions.tsv|_sessions.json)$')
+                              '\\/\\1(_sessions.tsv|_sessions.json)$')
         return scans_re.search(path) is not None
 
-
-    #Check if the file has a name appropriate for an anatomical scan
     def is_anat(self, path):
+        ''' Check if the file has a name appropriate for an anatomical scan.
+        '''
         anat_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?anat' +
-            '\\/\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?_(?:'
-            + "|".join(self.anat_suffixes)
-            + ').(nii.gz|nii|json)$')
+                             '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                             '\\/)?anat' +
+                             '\\/\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?_(?:'
+                             + "|".join(self.anat_suffixes)
+                             + ').(nii.gz|nii|json)$')
         return self.conditional_match(anat_re, path)
 
-    #Check if the file has a name appropriate for a diffusion scan
     def is_dwi(self, path):
-        suffixes = ["dwi", "sbref"];
+        ''' Check if the file has a name appropriate for a diffusion scan. '''
+        suffixes = ["dwi", "sbref"]
         anat_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?dwi' +
-            '\\/\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?_(?:'
-            + ("|").join(suffixes)
-            + ').(nii.gz|nii|json|bvec|bval)$')
+                             '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                             '\\/)?dwi' +
+                             '\\/\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?_(?:'
+                             + ("|").join(suffixes)
+                             + ').(nii.gz|nii|json|bvec|bval)$')
         return self.conditional_match(anat_re, path)
 
-    #Check if the file has a name appropriate for a fieldmap scan
     def is_field_map(self, path):
-        suffixes = ["phasediff", "phase1", "phase2", "magnitude1", "magnitude2", "magnitude", "fieldmap", "epi"];
+        ''' Check if the file has a name appropriate for a fieldmap scan. '''
+        suffixes = ["phasediff", "phase1", "phase2", "magnitude1",
+                    "magnitude2", "magnitude", "fieldmap", "epi"]
         anat_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?fmap' +
-            '\\/\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_dir-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?_(?:'
-            + ("|").join(suffixes)
-            + ').(nii.gz|nii|json)$')
+                             '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                             '\\/)?fmap' +
+                             '\\/\\1(_\\2)?(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_dir-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?_(?:'
+                             + ("|").join(suffixes)
+                             + ').(nii.gz|nii|json)$')
         return self.conditional_match(anat_re, path)
 
-    #Check if the file has a name appropriate for a functional scan
     def is_func(self, path):
+        ''' Check if the file has a name appropriate for a functional scan. '''
         func_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?func' +
-            '\\/\\1(_\\2)?_task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_echo-[0-9]+)?'
-            + '(?:_bold.nii.gz|_bold.nii|_bold.json|_sbref.nii.gz|_sbref.json|_events.json|_events.tsv|_physio.tsv.gz|_stim.tsv.gz|_physio.json|_stim.json|_defacemask.nii.gz|_defacemask.nii)$')
+                             '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                             '\\/)?func' +
+                             '\\/\\1(_\\2)?_task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?(?:_echo-[0-9]+)?'
+                             + '(?:_bold.nii.gz|_bold.nii|_bold.json|_sbref.nii.gz|_sbref.json|_events.json|_events.tsv|_physio.tsv.gz|_stim.tsv.gz|_physio.json|_stim.json|_defacemask.nii.gz|_defacemask.nii)$')
         return self.conditional_match(func_re, path)
 
-    #Check if the file has a name appropriate for a behavioral data
     def is_behavioral(self, path):
+        ''' Check if the file has a name appropriate for behavioral data. '''
         func_beh = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?beh' +
-            '\\/\\1(_\\2)?_task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?'
-            + '(?:_beh.json|_events.json|_events.tsv|_physio.tsv.gz|_stim.tsv.gz|_physio.json|_stim.json)$')
+                              '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                              '\\/)?beh' +
+                              '\\/\\1(_\\2)?_task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?'
+                              + '(?:_beh.json|_events.json|_events.tsv|_physio.tsv.gz|_stim.tsv.gz|_physio.json|_stim.json)$')
         return self.conditional_match(func_beh, path)
 
-    #Check if the file has a name appropriate for a functional bold
     def is_func_bold(self, path):
+        ''' Check if the file has a name appropriate for functional bold. '''
         func_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?func' +
-            '\\/\\1(_\\2)?_task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?' +\
-                            '(?:_run-[0-9]+)?(?:_echo-[0-9]+)?'
-            + '(?:_bold.nii.gz|_bold.nii|_sbref.nii.gz|_sbref.nii)$')
+                             '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                             '\\/)?func' +
+                             '\\/\\1(_\\2)?_task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?' +
+                             '(?:_run-[0-9]+)?(?:_echo-[0-9]+)?'
+                             + '(?:_bold.nii.gz|_bold.nii|_sbref.nii.gz|_sbref.nii)$')
         return self.conditional_match(func_re, path)
 
-    #Check if the file has a name appropriate for physiological and continuous recordings
     def is_cont(self, path):
+        ''' Check if the file has a name appropriate for physiological and
+        continuous recordings. '''
         cont_re = re.compile('^\\/(sub-[a-zA-Z0-9]+)' +
-            '\\/(?:(ses-[a-zA-Z0-9]+)' +
-            '\\/)?(?:func|beh)' +
-            '\\/\\1(_\\2)?_task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?' +
-            '(?:_recording-[a-zA-Z0-9]+)?'
-            + '(?:_physio.tsv.gz|_stim.tsv.gz|_physio.json|_stim.json)$')
+                             '\\/(?:(ses-[a-zA-Z0-9]+)' +
+                             '\\/)?(?:func|beh)' +
+                             '\\/\\1(_\\2)?_task-[a-zA-Z0-9]+(?:_acq-[a-zA-Z0-9]+)?(?:_rec-[a-zA-Z0-9]+)?(?:_run-[0-9]+)?' +
+                             '(?:_recording-[a-zA-Z0-9]+)?'
+                             + '(?:_physio.tsv.gz|_stim.tsv.gz|_physio.json|_stim.json)$')
         return self.conditional_match(cont_re, path)
 
-     # Get Path Values
-     #
-     # Takes a file path and returns and values
-     # found for the following path keys.
-     # sub-
-     # ses-
-     #/
     def get_path_values(self, path):
+        ''' Takes a file path and returns values found for the following path
+        keys:
+            sub-
+            ses-
+        '''
         values = {}
         # capture subject
         match = re.compile(r'/^\/sub-([a-zA-Z0-9]+)/)').findall(path)
         values['sub'] = match[1] if match & match[1] else None
 
         # capture session
-        match = re.compile(r'/^\/sub-[a-zA-Z0-9]+\/ses-([a-zA-Z0-9]+)/').findall(path)
+        match = re.compile(
+            r'/^\/sub-[a-zA-Z0-9]+\/ses-([a-zA-Z0-9]+)/').findall(path)
         values['ses'] = match[1] if match & match[1] else None
 
         return values
 
     def conditional_match(self, expression, path):
         match = expression.findall(path)
-        match = match[0] if len(match) >=1 else False
-        #print(match)
-            # adapted from JS code and JS does not support conditional groups
+        match = match[0] if len(match) >= 1 else False
+        # adapted from JS code and JS does not support conditional groups
         if (match):
             if ((match[1] == match[2][1:]) | (not match[1])):
                 return True


Test patch:
diff --git a/bids/grabbids/tests/test_grabbids.py b/bids/grabbids/tests/test_grabbids.py
--- a/bids/grabbids/tests/test_grabbids.py
+++ b/bids/grabbids/tests/test_grabbids.py
@@ -12,6 +12,7 @@ def testlayout1():
     data_dir = join(dirname(__file__), 'data', '7t_trt')
     return BIDSLayout(data_dir)
 
+
 @pytest.fixture
 def testlayout2():
     data_dir = join(dirname(__file__), 'data', 'ds005')
@@ -44,11 +45,13 @@ def test_get_metadata3(testlayout1):
     result = testlayout1.get_metadata(join(testlayout1.root, target))
     assert result['EchoTime'] == 0.017
 
+
 def test_get_metadata4(testlayout2):
     target = 'sub-03/anat/sub-03_T1w.nii.gz'
     result = testlayout2.get_metadata(join(testlayout2.root, target))
     assert result == None
 
+
 def test_get_events(testlayout2):
     target = 'sub-01/func/sub-01_task-' \
              'mixedgamblestask_run-03_bold.nii.gz'
@@ -56,11 +59,14 @@ def test_get_events(testlayout2):
     assert result == abspath(join(testlayout2.root,
                                   target.replace('_bold.nii.gz',
                                                  '_events.tsv')))
+
+
 def test_get_events2(testlayout2):
     target = 'sub-03/anat/sub-03_T1w.nii.gz'
     result = testlayout2.get_events(join(testlayout2.root, target))
     assert result == None
 
+
 def test_get_bvals_bvecs(testlayout2):
     dwifile = testlayout2.get(subject="01", modality="dwi")[0]
     result = testlayout2.get_bval(dwifile.filename)
@@ -69,6 +75,7 @@ def test_get_bvals_bvecs(testlayout2):
     result = testlayout2.get_bvec(dwifile.filename)
     assert result == abspath(join(testlayout2.root, 'dwi.bvec'))
 
+
 def test_get_subjects(testlayout1):
     result = testlayout1.get_subjects()
     predicted = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10']
@@ -95,6 +102,7 @@ def test_bids_json(testlayout1):
     assert testlayout1.get(return_type='id', target='run') == ['1', '2']
     assert testlayout1.get(return_type='id', target='session') == ['1', '2']
 
+
 def test_exclude(testlayout2):
     assert join(
         testlayout2.root, 'models/ds-005_type-russ_sub-all_model.json') \
diff --git a/bids/grabbids/tests/test_validator.py b/bids/grabbids/tests/test_validator.py
--- a/bids/grabbids/tests/test_validator.py
+++ b/bids/grabbids/tests/test_validator.py
@@ -24,8 +24,6 @@ def test_is_top_level_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_top_level(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -44,9 +42,7 @@ def test_is_top_level_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_top_level(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_associated_data() function true cases
@@ -60,8 +56,6 @@ def test_is_associated_data_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_associated_data(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -77,9 +71,7 @@ def test_is_associated_data_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_associated_data(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_session_level() function true cases
@@ -113,8 +105,6 @@ def test_is_session_level_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_session_level(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -149,9 +139,7 @@ def test_is_session_level_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_session_level(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_subject_level() function true cases
@@ -163,8 +151,6 @@ def test_is_subject_level_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_subject_level(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -180,9 +166,7 @@ def test_is_subject_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_subject_level(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_anat() function true cases
@@ -223,8 +207,6 @@ def test_is_anat_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_anat(item)
-        if not result:
-            print(item)
         assert result
 
 # checks is_anat() function false cases
@@ -255,9 +237,7 @@ def test_is_anat_false(testvalidator):
                    ]
     for item in target_list:
         result = testvalidator.is_anat(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_dwi() function true cases
@@ -298,8 +278,6 @@ def test_is_dwi_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_dwi(item)
-        if not result:
-            print(item)
         assert result
 
 # checks is_dwi() function false cases
@@ -332,9 +310,7 @@ def test_is_dwi_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_dwi(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_func() function true cases
@@ -447,8 +423,6 @@ def test_is_func_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_func(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -474,9 +448,7 @@ def test_is_func_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_func(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_func_bold() true cases
@@ -501,8 +473,6 @@ def test_is_func_bold_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_func_bold(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -543,9 +513,7 @@ def test_is_func_bold_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_func_bold(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_behavioral() function true cases
@@ -569,8 +537,6 @@ def test_is_behavioral_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_behavioral(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -595,9 +561,7 @@ def test_is_behavioral_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_behavioral(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_cont() function true cases
@@ -623,8 +587,6 @@ def test_is_cont_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_cont(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -651,9 +613,7 @@ def test_is_cont_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_cont(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_field_map() function true cases
@@ -854,8 +814,6 @@ def test_is_field_map_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_field_map(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -883,9 +841,7 @@ def test_is_field_map_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_field_map(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 # checks is_phenotypic() function true cases
@@ -897,8 +853,6 @@ def test_is_phenotypic_true(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_phenotypic(item)
-        if not result:
-            print(item)
         assert result
 
 
@@ -912,9 +866,7 @@ def test_is_phenotypic_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_phenotypic(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result
 
 
 def test_index_associated_false(testvalidator):
@@ -929,6 +881,4 @@ def test_index_associated_false(testvalidator):
 
     for item in target_list:
         result = testvalidator.is_associated_data(item)
-        if result:
-            print(item)
-        assert result == False
+        assert not result