Repository: bids-standard/pybids
Pull request #769 (2021-08-20 17:43:32)

MNT: Disable bids-nodot mode
Follow up to #629, in preparation for a 0.14 release.


Patch:
diff --git a/bids/config.py b/bids/config.py
index 6c5feda3..bd143999 100644
--- a/bids/config.py
+++ b/bids/config.py
@@ -14,11 +14,10 @@ _config_name = 'pybids_config.json'
 
 conf_path = str(Path(__file__).absolute().parent.joinpath('layout', 'config', '{}.json'))
 _default_settings = {
-    # XXX 0.14: Remove bids-nodot option (and file)
     'config_paths': {
-        name: conf_path.format(name) for name in ['bids', 'derivatives', 'bids-nodot']},
-    # XXX 0.14: Set to True
-    'extension_initial_dot': None,
+        name: conf_path.format(name) for name in ['bids', 'derivatives']},
+    # XXX 0.16: Remove
+    'extension_initial_dot': True,
 }
 
 
@@ -31,10 +30,14 @@ def set_option(key, value):
     """
     if key not in _settings:
         raise ValueError("Invalid pybids setting: '%s'" % key)
-    # XXX 0.14: Raise error
-    if (key, value) == ("extension_initial_dot", False):
-        warnings.warn("Setting 'extension_initial_dot' to False will be disabled in "
-                      "pybids 0.14", FutureWarning)
+    # XXX 0.16: Remove
+    elif key == "extension_initial_dot":
+        if value is not True:
+            raise ValueError(f"Cannot set {key!r} to {value!r} as of pybids 0.14. "
+                             "This setting is always True, and will be removed "
+                             "entirely in 0.16.")
+        warnings.warn("Setting 'extension_initial_dot' will be removed in pybids 0.16.",
+                      FutureWarning)
     _settings[key] = value
 
 
diff --git a/bids/layout/index.py b/bids/layout/index.py
index c11eab6b..189feeae 100644
--- a/bids/layout/index.py
+++ b/bids/layout/index.py
@@ -225,8 +225,6 @@ class BIDSLayoutIndexer:
     def _index_metadata(self):
         """Index metadata for all files in the BIDS dataset.
         """
-        dot = '.' if bids.config.get_option('extension_initial_dot') else ''
-
         filters = self.filters
 
         if filters:
@@ -237,10 +235,8 @@ class BIDSLayoutIndexer:
             if filters.get(ext_key):
                 filters[ext_key] = listify(filters[ext_key])
                 # ensure json files are being indexed
-                # XXX 0.14: dot always == '.'
-                json_ext = dot + 'json'
-                if json_ext not in filters[ext_key]:
-                    filters[ext_key].append(json_ext)
+                if '.json' not in filters[ext_key]:
+                    filters[ext_key].append('.json')
 
         # Process JSON files first if we're indexing metadata
         all_files = self._layout.get(absolute_paths=True, **filters)
@@ -287,7 +283,7 @@ class BIDSLayoutIndexer:
                     file_data[key] = defaultdict(list)
 
                 payload = None
-                if ext == dot + 'json':
+                if ext == '.json':
                     payload = partial(load_json, bf.path)
 
                 to_store = (file_ents, payload, bf.path)
@@ -328,7 +324,7 @@ class BIDSLayoutIndexer:
             # add the payload to the stack. Finally, we invert the
             # stack and merge the payloads in order.
             ext_key = "{}/{}".format(ext, suffix)
-            json_key = dot + "json/{}".format(suffix)
+            json_key = ".json/{}".format(suffix)
             dirname = bf._dirname
 
             payloads = []
diff --git a/bids/layout/layout.py b/bids/layout/layout.py
index 80e7a828..167b6a77 100644
--- a/bids/layout/layout.py
+++ b/bids/layout/layout.py
@@ -610,18 +610,10 @@ class BIDSLayout(object):
                                'keywords to the `get()` call. For example: '
                                '`layout.get(**filters)`.')
 
-        # Strip leading periods if extensions were passed
+        # Ensure leading periods if extensions were passed
         if 'extension' in filters and 'bids' in self.config:
-            # XXX 0.14: Disable drop_dot option
-            drop_dot = (self.config['bids'].entities['extension'].pattern ==
-                        '[._]*[a-zA-Z0-9]*?\\.([^/\\\\]+)$')
-            exts = listify(filters['extension'])
-            if drop_dot:
-                filters['extension'] = [x.lstrip('.') if isinstance(x, str) else x
-                                        for x in exts]
-            else:
-                filters['extension'] = ['.' + x.lstrip('.') if isinstance(x, str) else x
-                                        for x in exts]
+            filters['extension'] = ['.' + x.lstrip('.') if isinstance(x, str) else x
+                                    for x in listify(filters['extension'])]
 
         if invalid_filters != 'allow':
             bad_filters = set(filters.keys()) - set(entities.keys())
diff --git a/bids/layout/models.py b/bids/layout/models.py
index 62ffd3b8..2508ef2a 100644
--- a/bids/layout/models.py
+++ b/bids/layout/models.py
@@ -141,17 +141,6 @@ class Config(Base):
         A Config instance.
         """
 
-        # XXX 0.14: Disable extension_initial_dot branching
-        extension_initial_dot = get_option('extension_initial_dot')
-        if config == "bids" and not extension_initial_dot:
-            if extension_initial_dot is None:
-                warnings.warn("The 'extension' entity currently excludes the leading dot ('.'). "
-                              "As of version 0.14.0, it will include the leading dot. To suppress "
-                              "this warning and include the leading dot, use "
-                              "`bids.config.set_option('extension_initial_dot', True)`.",
-                              FutureWarning)
-            config = "bids-nodot"
-
         if isinstance(config, str):
             config_paths = get_option('config_paths')
             if config in config_paths:
@@ -166,15 +155,6 @@ class Config(Base):
         if session is not None:
             result = session.query(Config).filter_by(name=config['name']).first()
             if result:
-                # XXX 0.14: Remove check
-                if config["name"] == "bids":
-                    old_pattern = result.entities["extension"].pattern
-                    new_pattern = next(ent['pattern'] for ent in config['entities']
-                                       if ent['name'] == 'extension')
-                    if new_pattern != old_pattern:
-                        warnings.warn(
-                            "Cannot modify extension_initial_dot option after initialization. "
-                            "Set option immediately after ``import bids``.")
                 return result
 
         return Config(session=session, **config)
@@ -442,8 +422,7 @@ class BIDSDataFile(BIDSFile):
 
         data = self.data.copy()
 
-        # XXX 0.14: ".tsv.gz" only
-        if self.entities['extension'] in ('tsv.gz', '.tsv.gz'):
+        if self.entities['extension'] == '.tsv.gz':
             md = self.get_metadata()
             # We could potentially include some validation here, but that seems
             # like a job for the BIDS Validator.


Test patch:
diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py
index baf380c1..f00828db 100644
--- a/bids/layout/tests/test_layout.py
+++ b/bids/layout/tests/test_layout.py
@@ -28,7 +28,6 @@ def test_layout_init(layout_7t_trt):
     assert isinstance(layout_7t_trt.files, dict)
 
 
-@pytest.mark.parametrize("extension_initial_dot", (True, False))
 @pytest.mark.parametrize(
     'index_metadata,query,result',
     [
@@ -501,7 +500,7 @@ def test_parse_file_entities_from_layout(layout_synthetic):
 
     # Test with entities taken from bids config
     target = {'subject': '03', 'session': '07', 'run': 4, 'suffix': 'sekret',
-              'extension': 'nii.gz'}
+              'extension': '.nii.gz'}
     assert target == layout.parse_file_entities(filename, config='bids')
     config = Config.load('bids')
     assert target == layout.parse_file_entities(filename, config=[config])
@@ -509,7 +508,7 @@ def test_parse_file_entities_from_layout(layout_synthetic):
 
     # Test with default scope--i.e., everything
     target = {'subject': '03', 'session': '07', 'run': 4, 'suffix': 'sekret',
-              'desc': 'bleargh', 'extension': 'nii.gz'}
+              'desc': 'bleargh', 'extension': '.nii.gz'}
     assert target == layout.parse_file_entities(filename)
     # Test with only the fmriprep pipeline (which includes both configs)
     assert target == layout.parse_file_entities(filename, scope='fmriprep')
diff --git a/bids/layout/tests/test_utils.py b/bids/layout/tests/test_utils.py
index 857984fd..1023fb76 100644
--- a/bids/layout/tests/test_utils.py
+++ b/bids/layout/tests/test_utils.py
@@ -19,22 +19,19 @@ def test_bidsmetadata_class():
     assert md["Missing"] == 1
 
 
-@pytest.mark.parametrize("extension_initial_dot", (True, False))
-def test_parse_file_entities(mock_config, extension_initial_dot):
+def test_parse_file_entities(mock_config):
     filename = '/sub-03_ses-07_run-4_desc-bleargh_sekret.nii.gz'
 
-    dot = '.' if extension_initial_dot else ''
-
     # Test with entities taken from bids config
     target = {'subject': '03', 'session': '07', 'run': 4, 'suffix': 'sekret',
-              'extension': dot + 'nii.gz'}
+              'extension': '.nii.gz'}
     assert target == parse_file_entities(filename, config='bids')
     config = Config.load('bids')
     assert target == parse_file_entities(filename, config=[config])
 
     # Test with entities taken from bids and derivatives config
     target = {'subject': '03', 'session': '07', 'run': 4, 'suffix': 'sekret',
-              'desc': 'bleargh', 'extension': dot + 'nii.gz'}
+              'desc': 'bleargh', 'extension': '.nii.gz'}
     assert target == parse_file_entities(filename)
     assert target == parse_file_entities(
         filename, config=['bids', 'derivatives'])
diff --git a/bids/tests/test_config.py b/bids/tests/test_config.py
index 3bcb3406..2209526a 100644
--- a/bids/tests/test_config.py
+++ b/bids/tests/test_config.py
@@ -55,23 +55,18 @@ def test_set_option():
 def test_extension_initial_dot(mock_config):
     ds117 = os.path.join(get_test_data_path(), 'ds000117')
 
-    # Warn if creating a layout without declaring a mode
-    bids.config.set_option('extension_initial_dot', None)
-    with pytest.warns(FutureWarning, match='To suppress this warning'):
-        layout = bids.BIDSLayout(ds117)
-    assert layout.get(extension='nii.gz')[0].entities['extension'] == 'nii.gz'
-
-    # Warn if setting the mode to False... this isn't sticking around
-    with pytest.warns(FutureWarning, match='will be disabled'):
+    # Setting False is disabled
+    with pytest.raises(ValueError, match='Cannot set'):
         bids.config.set_option('extension_initial_dot', False)
-    with pytest.warns(None) as record:
-        layout = bids.BIDSLayout(ds117)
-    assert len(record) == 0
-    assert layout.get(extension='nii.gz')[0].entities['extension'] == 'nii.gz'
+    with pytest.raises(ValueError, match='Cannot set'):
+        bids.config.set_option('extension_initial_dot', None)
 
-    # No warnings to move to dot mode
-    with pytest.warns(None) as record:
+    # Setting True warns
+    with pytest.warns(FutureWarning, match='will be removed'):
         bids.config.set_option('extension_initial_dot', True)
+
+    # No warnings on layout construction
+    with pytest.warns(None) as record:
         layout = bids.BIDSLayout(ds117)
     assert len(record) == 0
     assert layout.get(extension='nii.gz')[0].entities['extension'] == '.nii.gz'
diff --git a/bids/variables/tests/test_io.py b/bids/variables/tests/test_io.py
index c4690ce8..eb69a775 100644
--- a/bids/variables/tests/test_io.py
+++ b/bids/variables/tests/test_io.py
@@ -19,18 +19,15 @@ def layout1():
 @pytest.fixture(scope="module", params=["events", "preproc"])
 def synthetic(request):
     import bids.config
-    # Not testing with/without here
-    with patch.dict('bids.config._settings'):
-        bids.config.set_option('extension_initial_dot', True)
-        root = join(get_test_data_path(), 'synthetic')
-        if request.param == 'preproc':
-            layout = BIDSLayout(root, derivatives=True)
-            dataset = load_variables(layout, skip_empty=True, desc='preproc',
-                                     space='T1w')
-        else:
-            layout = BIDSLayout(root)
-            dataset = load_variables(layout, skip_empty=True)
-        yield request.param, dataset
+    root = join(get_test_data_path(), 'synthetic')
+    if request.param == 'preproc':
+        layout = BIDSLayout(root, derivatives=True)
+        dataset = load_variables(layout, skip_empty=True, desc='preproc',
+                                 space='T1w')
+    else:
+        layout = BIDSLayout(root)
+        dataset = load_variables(layout, skip_empty=True)
+    yield request.param, dataset
 
 
 def test_load_events(layout1):