Repository: bids-standard/pybids

Issue:
BIDSLayout arguments: Argument is not JSON serializable
In PR #647 there were changes to how the BIDSLayout is saved.

If one passes an argument to `BIDSLayout` that is not serializable, it will throw an errorr:

```
/opt/miniconda-latest/envs/neuro/lib/python3.6/json/encoder.py in default(self, o)
    178         """
    179         raise TypeError("Object of type '%s' is not JSON serializable" %
--> 180                         o.__class__.__name__)
    181 
    182     def encode(self, o):

TypeError: Object of type 'PosixPath' is not JSON serializable
```

For example, if you pass a `Path` object to `derivatives`. 

Probably all arguments should be cast to string prior to this just in case. 


Discussion:
@adelavega It looks like `derivatives` is supposed to be `bool` or a `list` of paths, not a single path. We do check for `list` and coerce to `str`, but we do not coerce to `str` a single `Path`.

https://github.com/bids-standard/pybids/blob/9904dd54dce1a6f33799f78b80fc9f4da92d8da6/bids/layout/models.py#L57-L69

What do you think the best way forward is? a) `raise ValueError` if `derivatives` is neither `bool` nor `list`; b) `ensure_list` if not `bool`?
Oh, we are `listify`ing. So let's just say `if not isinstance(..., bool)`. I'll submit a PR.

Resolving patch:
diff --git a/bids/layout/models.py b/bids/layout/models.py
--- a/bids/layout/models.py
+++ b/bids/layout/models.py
@@ -60,7 +60,7 @@ def _sanitize_init_args(self, kwargs):
             kwargs['root'] = str(Path(kwargs['root']).absolute())
 
         # Get abspaths
-        if 'derivatives' in kwargs and isinstance(kwargs['derivatives'], list):
+        if kwargs.get('derivatives') not in (None, True, False):
             kwargs['derivatives'] = [
                 str(Path(der).absolute())
                 for der in listify(kwargs['derivatives'])
diff --git a/bids/layout/validation.py b/bids/layout/validation.py
--- a/bids/layout/validation.py
+++ b/bids/layout/validation.py
@@ -103,7 +103,7 @@ def check_for_description(bids_dir):
         return os.path.exists(dd)
 
     for p in paths:
-        p = os.path.abspath(p)
+        p = os.path.abspath(str(p))
         if os.path.exists(p):
             if check_for_description(p):
                 deriv_dirs.append(p)


Test patch:
diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py
--- a/bids/layout/tests/test_layout.py
+++ b/bids/layout/tests/test_layout.py
@@ -3,7 +3,8 @@
 
 import os
 import re
-from os.path import (join, abspath, basename)
+from os.path import join, abspath, basename
+from pathlib import Path
 
 import numpy as np
 import pytest
@@ -185,12 +186,12 @@ def test_get_metadata_error(layout_7t_trt):
     result = layout_7t_trt.files[path].get_metadata()
     with pytest.raises(KeyError) as err:
         result['Missing']
-    assert "Metadata term 'Missing' unavailable for file {}".format(path) in str(err)
+    assert "Metadata term 'Missing' unavailable for file {}".format(path) in str(err.value)
 
     result = layout_7t_trt.get_metadata(path)
     with pytest.raises(KeyError) as err:
         result['Missing']
-    assert "Metadata term 'Missing' unavailable for file {}".format(path) in str(err)
+    assert "Metadata term 'Missing' unavailable for file {}".format(path) in str(err.value)
 
 
 def test_get_with_bad_target(layout_7t_trt):
@@ -514,6 +515,21 @@ def test_deriv_indexing():
     assert not layout.get(scope='nonexistent')
 
 
+def test_path_arguments():
+    data_dir = join(get_test_data_path(), 'ds005')
+    deriv_dir = join(data_dir, 'derivatives', 'events')
+
+    layout = BIDSLayout(Path(data_dir), derivatives=Path(deriv_dir))
+    assert layout.get(scope='derivatives')
+    assert layout.get(scope='events')
+    assert not layout.get(scope='nonexistent')
+
+    layout = BIDSLayout(Path(data_dir), derivatives=[Path(deriv_dir)])
+    assert layout.get(scope='derivatives')
+    assert layout.get(scope='events')
+    assert not layout.get(scope='nonexistent')
+
+
 def test_layout_in_scope(layout_ds005, layout_ds005_derivs):
     assert layout_ds005._in_scope(['all'])
     assert layout_ds005._in_scope('raw')