Repository: bids-standard/pybids

Issue:
Improved reprs for SQLAlchemy model objects
This should help provide more useful exception messages for obscure SQLAlchemy error conditions. And just be generally more readable in a REPL.

Related to #465.

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index 54d01918..aefb43c5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,7 +12,6 @@ env:
   global:
     - EXTRA_WHEELS="https://5cf40426d9f06eb7461d-6fe47d9331aba7cd62fc36c7196769e4.ssl.cf2.rackcdn.com"
     - PRE_WHEELS="https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com"
-    - CHECK_TYPE="test"
   matrix:
     - EXTRA_PIP_FLAGS="--find-links=$EXTRA_WHEELS"
     - EXTRA_PIP_FLAGS="--pre --find-links=$EXTRA_WHEELS --find-links $PRE_WHEELS"
@@ -40,8 +39,6 @@ matrix:
       env:
         - PYTHON_VERSION=3.7
         - MINICONDA_URL="https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh"
-    - python: 3.6
-      env: CHECK_TYPE=tutorial
   allow_failures:
     - os: osx
       language: generic
@@ -72,20 +69,9 @@ before_install:
 
 install:
 - pip install $EXTRA_PIP_FLAGS ".[analysis]"
-- |
-  if [ "$CHECK_TYPE" = "tutorial" ]; then
-      pip install $EXTRA_PIP_FLAGS nbconvert jupyter_client ipykernel
-  fi
 
 script:
-- |
-  if [ "$CHECK_TYPE" = "test" ]; then
-      py.test -n 2 -v --cov bids --cov-config .coveragerc --cov-report xml:cov.xml bids
-  elif [ "$CHECK_TYPE" = "tutorial" ]; then
-      jupyter nbconvert --execute examples/pybids_tutorial.ipynb
-  else
-      false
-  fi
+- py.test -n 2 -v --cov bids --cov-config .coveragerc --cov-report xml:cov.xml bids
 
 after_script:
 - codecov --file cov.xml --flags unittests
@@ -97,6 +83,6 @@ deploy:
   user: $PYPI_USER
   password: $PYPI_PASS
   skip_cleanup: true
-  skip_existing: true
   on:
     tags: true
+    condition: $PYTHON_VERSION = "3.6"
diff --git a/.zenodo.json b/.zenodo.json
index 0d30ee1b..77fc77ef 100644
--- a/.zenodo.json
+++ b/.zenodo.json
@@ -83,11 +83,6 @@
       "name": "DuPre, Elizabeth",
       "orcid": "0000-0003-1358-196X"
     },
-    {
-      "affiliation": "Center for Adaptive Rationality, Max Planck Institute for Human Development, Berlin, Germany",
-      "name": "Appelhoff, Stefan",
-      "orcid": "0000-0001-8002-0877"
-    },
     {
       "name": "Ivanov, Alexander"
     },
@@ -100,11 +95,6 @@
       "name": "Tirrell, Lee S.",
       "orcid": "0000-0001-9393-8361"
     },
-    {
-      "affiliation": "Department of Psychology, Stanford University",
-      "name": "Esteban, Oscar",
-      "orcid": "0000-0001-8435-6191"
-    },
     {
       "affiliation": "T\u00e9l\u00e9com ParisTech, France",
       "name": "Jas, Mainak",
@@ -120,6 +110,16 @@
       "name": "Poldrack, Russell",
       "orcid": "0000-0001-6755-0259"
     },
+    {
+      "affiliation": "Department of Psychology, Stanford University",
+      "name": "Esteban, Oscar",
+      "orcid": "0000-0001-8435-6191"
+    },
+    {
+      "affiliation": "Center for Adaptive Rationality, Max Planck Institute for Human Development, Berlin, Germany",
+      "name": "Appelhoff, Stefan",
+      "orcid": "0000-0001-8002-0877"
+    },
     {
       "affiliation": "Berkeley Institute for Data Science; University of California at Berkeley",
       "name": "Holdgraf, Chris",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2e0f616..4ae794df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,24 +1,5 @@
 # Changelog
 
-## Version 0.9.2 (July 12, 2019)
-
-This version includes a number of minor fixes and improvements.
-EEG files are better handled, and `BIDSLayout` and `BIDSFile` play more
-nicely with `Path`-like objects.
-
-With thanks to new contributor Cecile Madjar.
-
-* FIX: Instantiate `ignore`/`force_index` after root validation (#457)
-* FIX: Restore `<entity>=None` query returning files lacking the entity (#458)
-* ENH: Add `BIDSJSONFile` (#444)
-* ENH: Add `BIDSFile.__fspath__` to work with pathlib (#449)
-* ENH: Add `eeg` datatype to layout config (#455)
-* RF: Remove unused kwargs to BIDSFile (#443)
-* DOC: Improve docstring consistency, style (#443)
-* DOC: Address final JOSS review (#453)
-* STY: PEP8 Fixes (#456)
-* MAINT: Set name explicitly in setup.py (#450)
-
 ## Version 0.9.1 (May 24, 2019)
 Hotfix release:
 
diff --git a/bids/analysis/hrf.py b/bids/analysis/hrf.py
index 30521708..749e8b01 100644
--- a/bids/analysis/hrf.py
+++ b/bids/analysis/hrf.py
@@ -346,7 +346,7 @@ def _orthogonalize(X):
 
 
 def _regressor_names(con_name, hrf_model, fir_delays=None):
-    """ Return a list of regressor names, computed from con-name and hrf type
+    """ Returns a list of regressor names, computed from con-name and hrf type
 
     Parameters
     ----------
@@ -434,7 +434,7 @@ def _hrf_kernel(hrf_model, tr, oversampling=50, fir_delays=None):
 
 def compute_regressor(exp_condition, hrf_model, frame_times, con_id='cond',
                       oversampling=50, fir_delays=None, min_onset=-24):
-    """ Convolve regressors with hrf model (the main function of the module)
+    """ This is the main function to convolve regressors with hrf model
 
     Parameters
     ----------
diff --git a/bids/layout/__init__.py b/bids/layout/__init__.py
index 88c9a55e..3082589f 100644
--- a/bids/layout/__init__.py
+++ b/bids/layout/__init__.py
@@ -1,15 +1,7 @@
 from .layout import BIDSLayout, add_config_paths, parse_file_entities
-from .models import BIDSFile, BIDSImageFile, BIDSDataFile, BIDSJSONFile
+from .models import BIDSFile
 # Backwards compatibility
 from bids_validator import BIDSValidator
 
-__all__ = [
-    "BIDSLayout",
-    "BIDSValidator",
-    "add_config_paths",
-    "parse_file_entities",
-    "BIDSFile",
-    "BIDSImageFIle",
-    "BIDSDataFile",
-    "BIDSJSONFile",
-]
+__all__ = ["BIDSLayout", "BIDSValidator", "add_config_paths",
+           "parse_file_entities", "BIDSFile"]
diff --git a/bids/layout/config/bids.json b/bids/layout/config/bids.json
index bc880762..39f126bb 100644
--- a/bids/layout/config/bids.json
+++ b/bids/layout/config/bids.json
@@ -21,7 +21,7 @@
             "pattern": "[_/\\\\]+acq-([a-zA-Z0-9]+)"
         },
         {
-            "name": "contrast",
+            "name": "ce",
             "pattern": "[_/\\\\]+ce-([a-zA-Z0-9]+)"
         },
         {
@@ -29,7 +29,7 @@
             "pattern": "[_/\\\\]+rec-([a-zA-Z0-9]+)"
         },
         {
-            "name": "direction",
+            "name": "dir",
             "pattern": "[_/\\\\]+dir-([a-zA-Z0-9]+)"
         },
         {
@@ -67,7 +67,7 @@
         },
         {
             "name": "datatype",
-            "pattern": "[/\\\\]+(func|anat|fmap|dwi|meg|eeg)[/\\\\]+"
+            "pattern": "[/\\\\]+(func|anat|fmap|dwi|meg)[/\\\\]+s"
         },
         {
             "name": "extension",
@@ -78,7 +78,7 @@
     "default_path_patterns": [
         "sub-{subject}[/ses-{session}]/anat/sub-{subject}[_ses-{session}][_acq-{acquisition}][_ce-{contrast}][_rec-{reconstruction}]_{suffix<T1w|T2w|T1rho|T1map|T2map|T2star|FLAIR|FLASH|PDmap|PD|PDT2|inplaneT[12]|angio>}.nii.gz",
         "sub-{subject}[/ses-{session}]/anat/sub-{subject}[_ses-{session}][_acq-{acquisition}][_ce-{contrast}][_rec-{reconstruction}][_mod-{modality}]_{suffix<defacemask>}.nii.gz",
-        "sub-{subject}[/ses-{session}]/func/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_ce-{contrast}][_dir-{direction}][_rec-{reconstruction}][_run-{run}][_echo-{echo}]_{suffix<bold>}.nii.gz",
+        "sub-{subject}[/ses-{session}]/func/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_rec-{reconstruction}][_run-{run}][_echo-{echo}]_{suffix<bold>}.nii.gz",
         "sub-{subject}[/ses-{session}]/dwi/sub-{subject}[_ses-{session}][_acq-{acquisition}]_{suffix<dwi>}.{extension<bval|bvec|json|nii\\.gz|nii>|nii\\.gz}",
         "sub-{subject}[/ses-{session}]/fmap/sub-{subject}[_ses-{session}][_acq-{acquisition}][_dir-{direction}][_run-{run}]_{fmap<phasediff|magnitude[1-2]|phase[1-2]|fieldmap|epi>}.nii.gz",
         "sub-{subject}[/ses-{session}]/[{datatype<func|meg>|func}/]sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}][_rec-{reconstruction}][_run-{run}][_echo-{echo}][_recording-{recording}]_{suffix<events>}.{extension<tsv>|tsv}",
@@ -88,4 +88,4 @@
         "sub-{subject}[/ses-{session}]/meg/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}]_{suffix<coordsystem>}.json",
         "sub-{subject}[/ses-{session}]/meg/sub-{subject}[_ses-{session}]_task-{task}[_acq-{acquisition}]_{suffix<photo>}.jpg"
     ]
-}
+}
\ No newline at end of file
diff --git a/bids/layout/layout.py b/bids/layout/layout.py
index f4c8b902..94f65f15 100644
--- a/bids/layout/layout.py
+++ b/bids/layout/layout.py
@@ -1,10 +1,9 @@
-"""BIDSLayout class."""
 import os
 import json
 import re
 from collections import defaultdict
 from io import open
-from functools import partial
+from functools import reduce, partial
 from itertools import chain
 import copy
 import warnings
@@ -16,7 +15,8 @@ from sqlalchemy.orm import joinedload
 from ..utils import listify, natural_sort, make_bidsfile
 from ..external import inflect, six
 from .writing import build_path, write_contents_to_file
-from .models import (Base, Config, BIDSFile, Entity, Tag)
+from .models import (Base, Config, BIDSFile, Entity, Tag, BIDSDataFile,
+                     BIDSImageFile)
 from .index import BIDSLayoutIndexer
 from .. import config as cf
 
@@ -35,7 +35,7 @@ __all__ = ['BIDSLayout']
 
 def parse_file_entities(filename, entities=None, config=None,
                         include_unmatched=False):
-    """Parse the passed filename for entity/value pairs.
+    """ Parse the passed filename for entity/value pairs.
 
     Args:
         filename (str): The filename to parse for entity values
@@ -52,6 +52,7 @@ def parse_file_entities(filename, entities=None, config=None,
     Returns: A dict, where keys are Entity names and values are the
         values extracted from the filename.
     """
+
     # Load Configs if needed
     if entities is None:
 
@@ -79,7 +80,7 @@ def parse_file_entities(filename, entities=None, config=None,
 
 
 def add_config_paths(**kwargs):
-    """Add to the pool of available configuration files for BIDSLayout.
+    """ Add to the pool of available configuration files for BIDSLayout.
 
     Args:
         kwargs: dictionary specifying where to find additional config files.
@@ -89,6 +90,7 @@ def add_config_paths(**kwargs):
         > add_config_paths(my_config='/path/to/config')
         > layout = BIDSLayout('/path/to/bids', config=['bids', 'my_config'])
     """
+
     for k, path in kwargs.items():
         if not os.path.exists(path):
             raise ValueError(
@@ -101,7 +103,7 @@ def add_config_paths(**kwargs):
 
 
 class BIDSLayout(object):
-    """Layout class representing an entire BIDS dataset.
+    """ Layout class representing an entire BIDS dataset.
 
     Args:
         root (str): The root directory of the BIDS dataset.
@@ -112,8 +114,7 @@ class BIDSLayout(object):
             will lead files in supplementary folders like derivatives/, code/,
             etc. to be ignored.
         absolute_paths (bool): If True, queries always return absolute paths.
-            If False, queries return relative paths (for files and
-            directories).
+            If False, queries return relative paths (for files and directories).
         derivatives (bool, str, list): Specifies whether and/or which
             derivatives to to index. If True, all pipelines found in the
             derivatives/ subdirectory will be indexed. If a str or list, gives
@@ -169,7 +170,7 @@ class BIDSLayout(object):
                  force_index=None, config_filename='layout_config.json',
                  regex_search=False, database_file=None, reset_database=False,
                  index_metadata=True):
-        """Initialize BIDSLayout."""
+
         self.root = root
         self.validate = validate
         self.absolute_paths = absolute_paths
@@ -177,14 +178,6 @@ class BIDSLayout(object):
         self.sources = sources
         self.regex_search = regex_search
         self.config_filename = config_filename
-
-        self.database_file = database_file
-        self.session = None
-
-        # Do basic BIDS validation on root directory
-        self._validate_root()
-
-        # Instantiate after root validation to ensure os.path.join works
         self.ignore = [os.path.abspath(os.path.join(self.root, patt))
                        if isinstance(patt, six.string_types) else patt
                        for patt in listify(ignore or [])]
@@ -192,6 +185,12 @@ class BIDSLayout(object):
                             if isinstance(patt, six.string_types) else patt
                             for patt in listify(force_index or [])]
 
+        self.database_file = database_file
+        self.session = None
+
+        # Do basic BIDS validation on root directory
+        self._validate_root()
+
         # Initialize the BIDS validator and examine ignore/force_index args
         self._validate_force_index()
 
@@ -202,7 +201,7 @@ class BIDSLayout(object):
             if config is None:
                 config = 'bids'
             config = [Config.load(c, session=self.session)
-                      for c in listify(config)]
+                    for c in listify(config)]
             self.config = {c.name: c for c in config}
 
             # Index files and (optionally) metadata
@@ -227,8 +226,8 @@ class BIDSLayout(object):
                 index_metadata=index_metadata)
 
     def __getattr__(self, key):
-        """Dynamically inspect missing methods for get_<entity>() calls
-        and return a partial function of get() if a match is found."""
+        ''' Dynamically inspect missing methods for get_<entity>() calls
+        and return a partial function of get() if a match is found. '''
         if key.startswith('get_'):
             ent_name = key.replace('get_', '')
             entities = self.get_entities()
@@ -247,7 +246,7 @@ class BIDSLayout(object):
                              (self.__class__.__name__, key))
 
     def __repr__(self):
-        """Provide a tidy summary of key properties."""
+        # A tidy summary of key properties
         # TODO: Replace each nested list comprehension with a single DB query
         n_sessions = len([session for isub in self.get_subjects()
                           for session in self.get_sessions(subject=isub)])
@@ -263,7 +262,7 @@ class BIDSLayout(object):
         engine = sa.create_engine('sqlite:///{}'.format(database_file))
 
         def regexp(expr, item):
-            """Regex function for SQLite's REGEXP."""
+            ''' Regex function for SQLite's REGEXP. '''
             reg = re.compile(expr, re.I)
             return reg.search(item) is not None
 
@@ -290,10 +289,8 @@ class BIDSLayout(object):
         self._set_session(database_file)
 
         # Reset database if needed and return whether or not it was reset
-        condi = (reset_database or
-                 not database_file or
-                 not os.path.exists(database_file))
-        if condi:
+        if (reset_database or not database_file or
+                           not os.path.exists(database_file)):
             engine = self.session.get_bind()
             Base.metadata.drop_all(engine)
             Base.metadata.create_all(engine)
@@ -308,9 +305,8 @@ class BIDSLayout(object):
             self.root = str(self.root)
         except:
             raise TypeError("root argument must be a string (or a type that "
-                            "supports casting to string, such as "
-                            "pathlib.Path) specifying the directory "
-                            "containing the BIDS dataset.")
+                    "supports casting to string, such as pathlib.Path)"
+                    " specifying the directory containing the BIDS dataset.")
 
         self.root = os.path.abspath(self.root)
 
@@ -338,21 +334,20 @@ class BIDSLayout(object):
         # Derivatives get special handling; they shouldn't be indexed normally
         if self.force_index is not None:
             for entry in self.force_index:
-                condi = (isinstance(entry, six.string_types) and
-                         os.path.normpath(entry).startswith('derivatives'))
-                if condi:
-                    msg = ("Do not pass 'derivatives' in the force_index "
-                           "list. To index derivatives, either set "
-                           "derivatives=True, or use add_derivatives().")
-                    raise ValueError(msg)
+                if (isinstance(entry, six.string_types) and
+                    os.path.normpath(entry).startswith('derivatives')):
+                        msg = ("Do not pass 'derivatives' in the force_index "
+                                "list. To index derivatives, either set "
+                                "derivatives=True, or use add_derivatives().")
+                        raise ValueError(msg)
 
     def _in_scope(self, scope):
-        """Determine whether current BIDSLayout is in the passed scope.
+        ''' Determine whether current BIDSLayout is in the passed scope.
 
         Args:
             scope (str, list): The intended scope(s). Each value must be one of
                 'all', 'raw', 'derivatives', or a pipeline name.
-        """
+        '''
         scope = listify(scope)
 
         if 'all' in scope:
@@ -363,17 +358,14 @@ class BIDSLayout(object):
         pl_name = self.description.get("PipelineDescription", {}).get("Name")
         is_deriv = bool(pl_name or ('derivatives' in self.config))
 
-        return ((not is_deriv and 'raw' in scope) or
-                (is_deriv and ('derivatives' in scope or pl_name in scope)))
+        return (not is_deriv and 'raw' in scope) or (is_deriv and \
+                ('derivatives' in scope or pl_name in scope))
 
     def _get_layouts_in_scope(self, scope):
-        """Return all layouts in the passed scope."""
-
-        if scope == 'self':
-            return [self]
+        ''' Return all layouts in the passed scope. '''
 
         def collect_layouts(layout):
-            """Recursively build a list of layouts."""
+            ''' Recursively build a list of layouts '''
             children = list(layout.derivatives.values())
             layouts = [collect_layouts(d) for d in children]
             return [layout] + list(chain(*layouts))
@@ -383,16 +375,14 @@ class BIDSLayout(object):
 
     @property
     def entities(self):
-        """Get the entities."""
         return self.get_entities()
 
     @property
     def files(self):
-        """Get the files."""
         return self.get_files()
 
     def save(self, filename='.index.db', replace_connection=True):
-        """Save the current index as a SQLite3 DB at the specified location.
+        """ Saves the current index as a SQLite3 DB at the specified location.
 
         Args:
             filename (str): The path to the desired database file. By default,
@@ -420,12 +410,12 @@ class BIDSLayout(object):
             self._set_session(filename)
 
     def get_entities(self, scope='all', metadata=None):
-        """Get entities for all layouts in the specified scope.
+        ''' Get entities for all layouts in the specified scope.
 
         Args:
             scope (str): The scope of the search space. Indicates which
-                BIDSLayouts' entities to extract. See BIDSLayout.get()
-                docstring for valid values.
+                BIDSLayouts' entities to extract. See BIDSLayout docstring
+                for valid values.
             metadata (bool, None): By default (None), all available entities
                 are returned. If True, only entities found in metadata files
                 (and not defined for filenames) are returned. If False, only
@@ -434,7 +424,7 @@ class BIDSLayout(object):
 
         Returns: A dict, where keys are entity names and values are Entity
             instances.
-        """
+        '''
         # TODO: memoize results
         layouts = self._get_layouts_in_scope(scope)
         entities = {}
@@ -447,16 +437,16 @@ class BIDSLayout(object):
         return entities
 
     def get_files(self, scope='all'):
-        """Get BIDSFiles for all layouts in the specified scope.
+        ''' Get BIDSFiles for all layouts in the specified scope.
 
         Args:
             scope (str): The scope of the search space. Indicates which
-                BIDSLayouts' entities to extract. See BIDSLayout.get()
-                docstring for valid values.
+                BIDSLayouts' entities to extract. See BIDSLayout docstring
+                for valid values.
 
         Returns: A dict, where keys are file paths and values are BIDSFile
             instances.
-        """
+        '''
         # TODO: memoize results
         layouts = self._get_layouts_in_scope(scope)
         files = {}
@@ -466,18 +456,18 @@ class BIDSLayout(object):
         return files
 
     def clone(self):
-        """Return a deep copy of the current BIDSLayout."""
+        """ Return a deep copy of the current BIDSLayout. """
         return copy.deepcopy(self)
 
     def parse_file_entities(self, filename, scope='all', entities=None,
                             config=None, include_unmatched=False):
-        """Parse the passed filename for entity/value pairs.
+        ''' Parse the passed filename for entity/value pairs.
 
         Args:
             filename (str): The filename to parse for entity values
             scope (str, list): The scope of the search space. Indicates which
-                BIDSLayouts' entities to extract. See BIDSLayout.get()
-                docstring for valid values. By default, extracts all entities.
+                BIDSLayouts' entities to extract. See BIDSLayout docstring
+                for valid values. By default, extracts all entities.
             entities (list): An optional list of Entity instances to use in
                 extraction. If passed, the scope and config arguments are
                 ignored, and only the Entities in this list are used.
@@ -490,7 +480,8 @@ class BIDSLayout(object):
 
         Returns: A dict, where keys are Entity names and values are the
             values extracted from the filename.
-        """
+        '''
+
         # If either entities or config is specified, just pass through
         if entities is None and config is None:
             layouts = self._get_layouts_in_scope(scope)
@@ -501,7 +492,7 @@ class BIDSLayout(object):
                                    include_unmatched)
 
     def add_derivatives(self, path, **kwargs):
-        """Add BIDS-Derivatives datasets to tracking.
+        ''' Add BIDS-Derivatives datasets to tracking.
 
         Args:
             path (str, list): One or more paths to BIDS-Derivatives datasets.
@@ -514,7 +505,7 @@ class BIDSLayout(object):
         Note: Every derivatives directory intended for indexing MUST contain a
             valid dataset_description.json file. See the BIDS-Derivatives
             specification for details.
-        """
+        '''
         paths = listify(path)
         deriv_dirs = []
 
@@ -565,7 +556,9 @@ class BIDSLayout(object):
             self.derivatives[pipeline_name] = BIDSLayout(deriv, **kwargs)
 
     def to_df(self, metadata=False, **filters):
-        """Return information for BIDSFiles tracked in Layout as pd.DataFrame.
+        """
+        Return information for all BIDSFiles tracked in the Layout as a pandas
+        DataFrame.
 
         Args:
             metadata (bool): If True, includes columns for all metadata fields.
@@ -577,12 +570,13 @@ class BIDSLayout(object):
             A pandas DataFrame, where each row is a file, and each column is
                 a tracked entity. NaNs are injected whenever a file has no
                 value for a given attribute.
-
         """
         try:
             import pandas as pd
         except ImportError:
-            raise ImportError('Missing dependency: "pandas"')
+            raise ImportError("What are you doing trying to export a BIDSLayout"
+                              " as a pandas DataFrame when you don't have "
+                              "pandas installed? Eh? Eh?")
 
         # TODO: efficiency could probably be improved further by joining the
         # BIDSFile and Tag tables and running a single query. But this would
@@ -593,7 +587,7 @@ class BIDSLayout(object):
         query = self.session.query(Tag).filter(Tag.file_path.in_(file_paths))
 
         if not metadata:
-            query = query.join(Entity).filter(Entity.is_metadata == False)
+            query = query.join(Entity).filter(Entity.is_metadata==False)
 
         tags = query.all()
 
@@ -611,7 +605,8 @@ class BIDSLayout(object):
     def get(self, return_type='object', target=None, scope='all',
             regex_search=False, absolute_paths=None, drop_invalid_filters=True,
             **filters):
-        """Retrieve files and/or metadata from the current Layout.
+        """
+        Retrieve files and/or metadata from the current Layout.
 
         Args:
             return_type (str): Type of result to return. Valid values:
@@ -628,7 +623,6 @@ class BIDSLayout(object):
                     'all' (default): search all available directories.
                     'derivatives': search all derivatives directories
                     'raw': search only BIDS-Raw directories
-                    'self': search only the directly called BIDSLayout
                     <PipelineName>: the name of a BIDS-Derivatives pipeline
             regex_search (bool or None): Whether to require exact matching
                 (False) or regex search (True) when comparing the query string
@@ -639,7 +633,7 @@ class BIDSLayout(object):
                 at BIDSLayout initialization.
            filters (dict): Any optional key/values to filter the entities on.
                 Keys are entity names, values are regexes to filter on. For
-                example, passing filters={'subject': 'sub-[12]'} would return
+                example, passing filter={'subject': 'sub-[12]'} would return
                 only files that match the first two subjects.
 
         Returns:
@@ -647,14 +641,14 @@ class BIDSLayout(object):
 
         Notes:
             * In pybids 0.7.0, some keywords were changed. Namely: 'type'
-            becomes 'suffix', 'modality' becomes 'datatype', 'acq' becomes
-            'acquisition' and 'mod' becomes 'modality'. Using the wrong version
-            could result in get() silently returning wrong or no results. See
+            becomes 'suffix', 'modality' becomes 'datatype', 'acq' becomes 
+            'acquisition' and 'mod' becomes 'modality'. Using the wrong version 
+            could result in get() silently returning wrong or no results. See 
             the changelog for more details.
             * In pybids 0.9.0, the 'extensions' argument has been removed in
             favor of the 'extension' entity.
-
         """
+
         # Warn users still expecting 0.6 behavior
         if 'type' in filters:
             raise ValueError("As of pybids 0.7.0, the 'type' argument has been"
@@ -701,7 +695,7 @@ class BIDSLayout(object):
             # Eager load associations, because mixing queries from different
             # DB sessions causes objects to detach
             query = query.options(joinedload(BIDSFile.tags)
-                                  .joinedload(Tag.entity))
+                                 .joinedload(Tag.entity))
             results.extend(query.all())
 
         # Convert to relative paths if needed
@@ -709,10 +703,10 @@ class BIDSLayout(object):
             absolute_paths = self.absolute_paths
 
         if not absolute_paths:
-            for i, fi in enumerate(results):
-                fi = copy.copy(fi)
-                fi.path = os.path.relpath(fi.path, self.root)
-                results[i] = fi
+            for i, f in enumerate(results):
+                f = copy.copy(f)
+                f.path = os.path.relpath(f.path, self.root)
+                results[i] = f
 
         if return_type.startswith('file'):
             results = natural_sort([f.path for f in results])
@@ -742,7 +736,7 @@ class BIDSLayout(object):
                     template = template.replace('{%s}' % ent, patt)
                 template += r'[^\%s]*$' % os.path.sep
                 matches = [
-                    f.dirname if absolute_paths else os.path.relpath(f.dirname, self.root)  # noqa: E501
+                    f.dirname if absolute_paths else os.path.relpath(f.dirname, self.root)
                     for f in results
                     if re.search(template, f.dirname)
                 ]
@@ -758,20 +752,20 @@ class BIDSLayout(object):
         return results
 
     def get_file(self, filename, scope='all'):
-        """Return the BIDSFile object with the specified path.
+        ''' Returns the BIDSFile object with the specified path.
 
         Args:
             filename (str): The path of the file to retrieve. Must be either
                 an absolute path, or relative to the root of this BIDSLayout.
             scope (str, list): Scope of the search space. If passed, only
                 BIDSLayouts that match the specified scope will be
-                searched. See BIDSLayout.get() docstring for valid values.
+                searched. See BIDSLayout docstring for valid values.
 
         Returns: A BIDSFile, or None if no match was found.
-        """
+        '''
         filename = os.path.abspath(os.path.join(self.root, filename))
         for layout in self._get_layouts_in_scope(scope):
-            result = layout.session.query(BIDSFile).filter_by(path=filename).first()  # noqa: E501
+            result = layout.session.query(BIDSFile).filter_by(path=filename).first()
             if result:
                 return result
         return None
@@ -787,30 +781,22 @@ class BIDSLayout(object):
             query = query.join(BIDSFile.tags)
             regex = kwargs.get('regex_search', False)
             for name, val in filters.items():
-                if isinstance(val, (list, tuple)) and len(val) == 1:
-                    val = val[0]
-
-                if val is None:
-                    drop = query.filter(
-                        BIDSFile.tags.any(entity_name=name))
-                    query = query.except_(drop)
-                    continue
-
                 if regex:
                     if isinstance(val, (list, tuple)):
                         val_clause = sa.or_(*[Tag._value.op('REGEXP')(str(v))
-                                              for v in val])
+                                             for v in val])
                     else:
                         val_clause = Tag._value.op('REGEXP')(val)
+                    subq = sa.and_(Tag.entity_name==name, val_clause)
+                    query = query.filter(BIDSFile.tags.any(subq))
                 else:
                     if isinstance(val, (list, tuple)):
-                        val_clause = Tag._value.in_(val)
+                        subq = sa.and_(Tag.entity_name==name,
+                                       Tag._value.in_(val))
+                        query = query.filter(BIDSFile.tags.any(subq))
                     else:
-                        val_clause = Tag._value == val
-
-                subq = sa.and_(Tag.entity_name == name, val_clause)
-                query = query.filter(BIDSFile.tags.any(subq))
-
+                        query = query.filter(
+                            BIDSFile.tags.any(entity_name=name, _value=val))
         return query
 
     def get_collections(self, level, types=None, variables=None, merge=False,
@@ -858,9 +844,8 @@ class BIDSLayout(object):
                 from the filename (rather than JSON sidecars) are included in
                 the returned metadata dictionary.
             scope (str, list): The scope of the search space. Each element must
-                be one of 'all', 'raw', 'self', 'derivatives', or a
-                BIDS-Derivatives pipeline name. Defaults to searching all
-                available datasets.
+                be one of 'all', 'raw', 'derivatives', or a BIDS-Derivatives
+                pipeline name. Defaults to searching all available datasets.
 
         Returns: A dictionary of key/value pairs extracted from all of the
             target file's associated JSON sidecars.
@@ -870,16 +855,16 @@ class BIDSLayout(object):
             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.
-
         """
+
         for layout in self._get_layouts_in_scope(scope):
 
             query = (layout.session.query(Tag)
                                    .join(BIDSFile)
-                                   .filter(BIDSFile.path == path))
+                                   .filter(BIDSFile.path==path))
 
             if not include_entities:
-                query = query.join(Entity).filter(Entity.is_metadata == True)
+                query = query.join(Entity).filter(Entity.is_metadata==True)
 
             results = query.all()
             if results:
@@ -887,31 +872,12 @@ class BIDSLayout(object):
 
         return {}
 
-    def get_dataset_description(self, scope='self', all_=False):
-        """Return contents of dataset_description.json.
-
-        Args:
-            scope (str): The scope of the search space. Only descriptions of
-                BIDSLayouts that match the specified scope will be returned.
-                See BIDSLayout.get() docstring for valid values. Defaults to
-                'self'--i.e., returns the dataset_description.json file for
-                only the directly-called BIDSLayout.
-            all_ (bool): If True, returns a list containing descriptions for
-                all matching layouts. If False (default), returns for only the
-                first matching layout.
-
-        Returns: a dictionary or list of dictionaries (depending on all_).
-        """
-        layouts = self._get_layouts_in_scope(scope)
-        if not all_:
-            return layouts[0].get_file('dataset_description.json').get_dict()
-        return [l.get_file('dataset_description.json').get_dict()
-              for l in layouts]
 
     def get_nearest(self, path, return_type='filename', strict=True,
                     all_=False, ignore_strict_entities='extension',
                     full_search=False, **filters):
-        """Walk up file tree from specified path and return nearest matching file(s).
+        ''' Walk up the file tree from the specified path and return the
+        nearest matching file(s).
 
         Args:
             path (str): The file to search from.
@@ -932,7 +898,8 @@ class BIDSLayout(object):
                 they don't share a common root with the provided path. If
                 False, only files that share a common root will be scanned.
             filters: Optional keywords to pass on to .get().
-        """
+        '''
+
         path = os.path.abspath(path)
 
         # Make sure we have a valid suffix
@@ -1005,24 +972,24 @@ class BIDSLayout(object):
             if not all_:
                 break
 
-        matches = [match.path if return_type.startswith('file')
-                   else match for match in matches]
+        matches = [m.path if return_type.startswith('file')
+                   else m for m in matches]
         return matches if all_ else matches[0] if matches else None
 
     def get_bvec(self, path, **kwargs):
-        """Get bvec file for passed path."""
+        """ Get bvec file for passed path. """
         result = self.get_nearest(path, extension='bvec', suffix='dwi',
                                   all_=True, **kwargs)
         return listify(result)[0]
 
     def get_bval(self, path, **kwargs):
-        """Get bval file for passed path."""
+        """ Get bval file for passed path. """
         result = self.get_nearest(path, suffix='dwi', extension='bval',
                                   all_=True, **kwargs)
         return listify(result)[0]
 
     def get_fieldmap(self, path, return_list=False):
-        """Get fieldmap(s) for specified path."""
+        """ Get fieldmap(s) for specified path. """
         fieldmaps = self._get_fieldmaps(path)
 
         if return_list:
@@ -1082,15 +1049,14 @@ class BIDSLayout(object):
         return fieldmap_set
 
     def get_tr(self, derivatives=False, **filters):
-
-        """Return the scanning repetition time (TR) for one or more runs.
+        """ Returns the scanning repetition time (TR) for one or more runs.
 
         Args:
             derivatives (bool): If True, also checks derivatives images.
             filters: Optional keywords used to constrain the selected runs.
                 Can be any arguments valid for a .get call (e.g., BIDS entities
                 or JSON sidecar keys).
-
+        
         Returns: A single float.
 
         Notes: Raises an exception if more than one unique TR is found.
@@ -1107,15 +1073,14 @@ class BIDSLayout(object):
         for img in images:
             md = self.get_metadata(img.path)
             all_trs.add(round(float(md['RepetitionTime']), 5))
-
+ 
         if len(all_trs) > 1:
             raise ValueError("Unique TR cannot be found given filters {!r}"
                              .format(filters))
         return all_trs.pop()
 
-    def build_path(self, source, path_patterns=None, strict=False,
-                   scope='all'):
-        """Construct a target filename for a file or dictionary of entities.
+    def build_path(self, source, path_patterns=None, strict=False, scope='all'):
+        ''' Constructs a target filename for a file or dictionary of entities.
 
         Args:
             source (str, BIDSFile, dict): The source data to use to construct
@@ -1147,7 +1112,8 @@ class BIDSLayout(object):
                 two or more values are provided, the order determines the
                 precedence of path patterns (i.e., earlier layouts will have
                 higher precedence).
-        """
+        '''
+
         # 'is_file' is a crude check for Path objects
         if isinstance(source, six.string_types) or hasattr(source, 'is_file'):
             source = str(source)
@@ -1174,10 +1140,9 @@ class BIDSLayout(object):
 
     def copy_files(self, files=None, path_patterns=None, symbolic_links=True,
                    root=None, conflicts='fail', **kwargs):
-        """Copy BIDSFile(s) to new locations.
-
-        The new locations are defined by each BIDSFile's entities and the
-        specified `path_patterns`.
+        """
+        Copies one or more BIDSFiles to new locations defined by each
+        BIDSFile's entities and the specified path_patterns.
 
         Args:
             files (list): Optional list of BIDSFile objects to write out. If
@@ -1210,7 +1175,8 @@ class BIDSLayout(object):
                                contents=None, link_to=None,
                                content_mode='text', conflicts='fail',
                                strict=False):
-        """Write arbitrary data to a file defined by the passed entities and
+        """
+        Write arbitrary data to a file defined by the passed entities and
         path patterns.
 
         Args:
diff --git a/bids/layout/models.py b/bids/layout/models.py
index 0cbde2c3..997e7a63 100644
--- a/bids/layout/models.py
+++ b/bids/layout/models.py
@@ -127,7 +127,7 @@ class BIDSFile(Base):
         'polymorphic_identity': 'file'
     }
 
-    def __init__(self, filename):
+    def __init__(self, filename, derivatives=False, is_dir=False):
         self.path = filename
         self.filename = os.path.basename(self.path)
         self.dirname = os.path.dirname(self.path)
@@ -152,9 +152,6 @@ class BIDSFile(Base):
     def __repr__(self):
         return "<{} filename='{}'>".format(self.__class__.__name__, self.path)
 
-    def __fspath__(self):
-        return self.path
-
     @reconstructor
     def _init_on_load(self):
         self._data = None
@@ -194,11 +191,11 @@ class BIDSFile(Base):
         return chain(*[collect_associations([], bf) for bf in associations])
 
     def get_metadata(self):
-        """ Return all metadata associated with the current file. """
+        """ Returns all metadata associated with the current file. """
         return self.get_entities(metadata=True)
 
     def get_entities(self, metadata=False, values='tags'):
-        """ Return entity information for the current file.
+        """ Returns entity information for the current file.
 
         Args:
             metadata (bool, None): If False (default), only entities defined
@@ -274,18 +271,13 @@ class BIDSFile(Base):
 
 
 class BIDSDataFile(BIDSFile):
-    """ Represents a single data file in a BIDS dataset.
-
-    Derived from `BIDSFile` and provides additional functionality such as
-    obtaining pandas DataFrame data representation (via `get_df`).
-    """
 
     __mapper_args__ = {
         'polymorphic_identity': 'data_file'
     }
 
     def get_df(self, include_timing=True, adjust_onset=False):
-        """ Return the contents of a tsv file as a pandas DataFrame.
+        """ Returns the contents of a tsv file as a pandas DataFrame.
 
         Args:
             include_timing (bool): If True, adds an "onset" column to dense
@@ -321,11 +313,6 @@ class BIDSDataFile(BIDSFile):
 
 
 class BIDSImageFile(BIDSFile):
-    """ Represents a single neuroimaging data file in a BIDS dataset.
-
-    Derived from `BIDSFile` and provides additional functionality such as
-    obtaining nibabel's image file representation (via `get_image`).
-    """
 
     __mapper_args__ = {
         'polymorphic_identity': 'image_file'
@@ -342,25 +329,6 @@ class BIDSImageFile(BIDSFile):
                              "NiBabel can read.".format(self.path))
 
 
-class BIDSJSONFile(BIDSFile):
-
-    __mapper_args__ = {
-        'polymorphic_identity': 'json_file'
-    }
-
-    def get_dict(self):
-        ''' Return the contents of the current file as a dictionary. '''
-        d = json.loads(self.get_json())
-        if not isinstance(d, dict):
-            raise ValueError("File %s is a json containing %s, not a dict which was expected" % (self.path, type(d)))
-        return d
-
-    def get_json(self):
-        ''' Return the contents of the current file as a JSON string. '''
-        with open(self.path, 'r') as f:
-            return f.read()
-
-
 class Entity(Base):
     """
     Represents a single entity defined in the JSON config.
@@ -452,11 +420,11 @@ class Entity(Base):
         return self._astype(val)
 
     def unique(self):
-        """ Return all unique values/levels for the current entity. """
+        """ Returns all unique values/levels for the current entity. """
         return list(set(self.files.values()))
 
     def count(self, files=False):
-        """ Return a count of unique values or files.
+        """ Returns a count of unique values or files.
 
         Args:
             files (bool): When True, counts all files mapped to the Entity.
@@ -472,7 +440,8 @@ class Entity(Base):
 
 
 class Tag(Base):
-    """ Represents an association between a File and and Entity.
+    """
+    Represents an association between a File and and Entity.
 
     Args:
         file (BIDSFile): The associated BIDSFile.
@@ -522,6 +491,10 @@ class Tag(Base):
         self._dtype = dtype
 
         self._init_on_load()
+    
+    def __repr__(self):
+        msg = "<Tag file:{!r} entity:{!r} value:{!r}>"
+        return msg.format(self.file_path, self.entity_name, self.value)
 
     @reconstructor
     def _init_on_load(self):
diff --git a/bids/reports/parsing.py b/bids/reports/parsing.py
index b1d92504..efc429fe 100644
--- a/bids/reports/parsing.py
+++ b/bids/reports/parsing.py
@@ -315,7 +315,7 @@ def fmap_info(metadata, img, config, layout):
         run_dict = {}
         for scan in scans:
             fn = basename(scan)
-            iff_file = [f for f in layout.get(extension=[".nii", ".nii.gz"]) if fn in f.path][0]
+            iff_file = [f for f in layout.get(extension='nii.gz') if fn in f.path][0]
             run_num = int(iff_file.run)
             ty = iff_file.entities['suffix'].upper()
             if ty == 'BOLD':
@@ -439,7 +439,7 @@ def parse_niftis(layout, niftis, subj, config, **kwargs):
 
             if nifti_struct.entities['datatype'] == 'func':
                 if not skip_task.get(nifti_struct.entities['task'], False):
-                    echos = layout.get_echoes(subject=subj, extension=[".nii", ".nii.gz"],
+                    echos = layout.get_echoes(subject=subj, extension='nii.gz',
                                               task=nifti_struct.entities['task'],
                                               **kwargs)
                     n_echos = len(echos)
@@ -447,7 +447,7 @@ def parse_niftis(layout, niftis, subj, config, **kwargs):
                         metadata['EchoTime'] = []
                         for echo in sorted(echos):
                             echo_struct = layout.get(subject=subj, echo=echo,
-                                                     extension=[".nii", ".nii.gz"],
+                                                     extension='nii.gz',
                                                      task=nifti_struct.entities['task'],
                                                      **kwargs)[0]
                             echo_file = echo_struct.path
@@ -469,7 +469,7 @@ def parse_niftis(layout, niftis, subj, config, **kwargs):
                 description_list.append(anat_info(suffix, metadata, img,
                                                   config))
             elif nifti_struct.entities['datatype'] == 'dwi':
-                bval_file = nii_file.replace('.nii.gz', '.bval').replace('.nii', '.bval')
+                bval_file = nii_file.replace('.nii.gz', '.bval')
                 description_list.append(dwi_info(bval_file, metadata, img,
                                                  config))
             elif nifti_struct.entities['datatype'] == 'fmap':
diff --git a/bids/reports/report.py b/bids/reports/report.py
index 304b0628..45b66514 100644
--- a/bids/reports/report.py
+++ b/bids/reports/report.py
@@ -123,7 +123,7 @@ class BIDSReport(object):
             sessions = [sessions]
 
         for ses in sessions:
-            niftis = self.layout.get(subject=subject, extension= [".nii", ".nii.gz"],
+            niftis = self.layout.get(subject=subject, extension='nii.gz',
                                      **kwargs)
 
             if niftis:
diff --git a/bids/reports/utils.py b/bids/reports/utils.py
index d4807d20..30bc01cc 100644
--- a/bids/reports/utils.py
+++ b/bids/reports/utils.py
@@ -5,7 +5,6 @@ methods section from a BIDS dataset.
 """
 from __future__ import print_function
 import logging
-import os
 from .. import __version__
 
 logging.basicConfig()
diff --git a/bids/utils.py b/bids/utils.py
index fe078847..02acf9c6 100644
--- a/bids/utils.py
+++ b/bids/utils.py
@@ -101,8 +101,6 @@ def make_bidsfile(filename):
         cls = 'BIDSImageFile'
     elif ext in ['tsv', 'tsv.gz']:
         cls = 'BIDSDataFile'
-    elif ext == 'json':
-        cls = 'BIDSJSONFile'
     else:
         cls = 'BIDSFile'
 
diff --git a/paper/paper.bib b/paper/paper.bib
index d43715bb..ea5cdaef 100644
--- a/paper/paper.bib
+++ b/paper/paper.bib
@@ -1,8 +1,49 @@
+@misc{zenodo,
+  author       = {Yarkoni, Tal and
+                  Markiewicz, Christopher J. and
+                  de la Vega, Alejandro and
+                  Gorgolewski, Krzysztof J. and
+                  Salo, Taylor and
+                  Halchenko, Yaroslav O. and
+                  McNamara, Quinten and
+                  DeStasio, Krista and
+                  Poline, Jean-Baptiste and
+                  Petrov, Dmitry and
+                  Hayot-Sasson, Valérie and
+                  Nielson, Dylan M. and
+                  Carlin, Johan and
+                  Kiar, Gregory and
+                  Whitaker, Kirstie and
+                  DuPre, Elizabeth and
+                  Wagner, Adina and
+                  Ivanov, Alexander and
+                  Tirrell, Lee S. and
+                  Jas, Mainak and
+                  Hanke, Michael and
+                  Poldrack, Russell and
+                  Esteban, Oscar and
+                  Appelhoff, Stefan and
+                  Holdgraf, Chris and
+                  Staden, Isla and
+                  Rokem, Ariel and
+                  Thirion, Bertrand and
+                  Kleinschmidt, Dave F. and
+                  Lee, John A. and
+                  Visconti di Oleggio Castello, Matteo and
+                  Notter, Michael Philipp and
+                  Roca, Pauline and
+                  Blair, Ross},
+  title        = {bids-standard/pybids: 0.8.0},
+  month        = feb,
+  year         = 2019,
+  doi          = {10.5281/zenodo.2566511},
+  url          = {https://doi.org/10.5281/zenodo.2566511}
+}
+
 @article{Gorgolewski2016-sk,
   title    = "A Practical Guide for Improving Transparency and Reproducibility
               in Neuroimaging Research",
-  author   = {Gorgolewski, Krzysztof J and
-              Poldrack, Russell A},
+  author   = "Gorgolewski, Krzysztof J and Poldrack, Russell A",
   abstract = "Recent years have seen an increase in alarming signals regarding
               the lack of replicability in neuroscience, psychology, and other
               related fields. To avoid a widespread crisis in neuroimaging
@@ -16,7 +57,7 @@
               advice as well as highlighting advantages of adopting more open
               research practices that go beyond improved transparency and
               reproducibility.",
-  journal  = "PLOS Biology",
+  journal  = "PLoS Biol.",
   volume   =  14,
   number   =  7,
   pages    = "e1002506",
@@ -29,33 +70,15 @@
 @article{Gorgolewski2016-nw,
   title    = "The brain imaging data structure, a format for organizing and
               describing outputs of neuroimaging experiments",
-  author   = {Gorgolewski, Krzysztof J and
-              Auer, Tibor and
-              Calhoun, Vince D and
-              Craddock, R Cameron and
-              Das, Samir and
-              Duff, Eugene P and
-              Flandin, Guillaume and
-              Ghosh, Satrajit S and
-              Glatard, Tristan and
-              Halchenko, Yaroslav O and
-              Handwerker, Daniel A and
-              Hanke, Michael and
-              Keator, David and
-              Li, Xiangrui and
-              Michael, Zachary and
-              Maumet, Camille and
-              Nichols, B Nolan and
-              Nichols, Thomas E and
-              Pellman, John and
-              Poline, Jean-Baptiste and
-              Rokem, Ariel and
-              Schaefer, Gunnar and
-              Sochat, Vanessa and
-              Triplett, William and
-              Turner, Jessica A and
-              Varoquaux, Ga{\"e}l and
-              Poldrack, Russell A},
+  author   = "Gorgolewski, Krzysztof J and Auer, Tibor and Calhoun, Vince D and
+              Craddock, R Cameron and Das, Samir and Duff, Eugene P and
+              Flandin, Guillaume and Ghosh, Satrajit S and Glatard, Tristan and
+              Halchenko, Yaroslav O and Handwerker, Daniel A and Hanke, Michael
+              and Keator, David and Li, Xiangrui and Michael, Zachary and
+              Maumet, Camille and Nichols, B Nolan and Nichols, Thomas E and
+              Pellman, John and Poline, Jean-Baptiste and Rokem, Ariel and
+              Schaefer, Gunnar and Sochat, Vanessa and Triplett, William and
+              Turner, Jessica A and Varoquaux, Ga{\"e}l and Poldrack, Russell A",
   abstract = "The development of magnetic resonance imaging (MRI) techniques
               has defined modern neuroimaging. Since its inception, tens of
               thousands of studies using techniques such as functional MRI and
@@ -73,7 +96,7 @@
               unifies the majority of practices already common in the field,
               and captures the metadata necessary for most common data
               processing operations.",
-  journal  = "Scientific Data",
+  journal  = "Sci Data",
   volume   =  3,
   pages    = "160044",
   month    =  jun,
@@ -85,35 +108,17 @@
 @article{Gorgolewski2017-cz,
   title    = "{BIDS} apps: Improving ease of use, accessibility, and
               reproducibility of neuroimaging data analysis methods",
-  author   = {Gorgolewski, Krzysztof J and
-              Alfaro-Almagro, Fidel and
-              Auer, Tibor and
-              Bellec, Pierre and
-              Capot{\u a}, Mihai and
-              Chakravarty, M Mallar and
-              Churchill, Nathan W and
-              Cohen, Alexander Li and
-              Craddock, R Cameron and
-              Devenyi, Gabriel A and
-              Eklund, Anders and
-              Esteban, Oscar and
-              Flandin, Guillaume and
-              Ghosh, Satrajit S and
-              Guntupalli, J Swaroop and
-              Jenkinson, Mark and
-              Keshavan, Anisha and
-              Kiar, Gregory and
-              Liem, Franziskus and
-              Raamana, Pradeep Reddy and
-              Raffelt, David and
-              Steele, Christopher J and
-              Quirion, Pierre-Olivier and
-              Smith, Robert E and
-              Strother, Stephen C and
-              Varoquaux, Ga{\"e}l and
-              Wang, Yida and
-              Yarkoni, Tal and
-              Poldrack, Russell A},
+  author   = "Gorgolewski, Krzysztof J and Alfaro-Almagro, Fidel and Auer,
+              Tibor and Bellec, Pierre and Capot{\u a}, Mihai and Chakravarty,
+              M Mallar and Churchill, Nathan W and Cohen, Alexander Li and
+              Craddock, R Cameron and Devenyi, Gabriel A and Eklund, Anders and
+              Esteban, Oscar and Flandin, Guillaume and Ghosh, Satrajit S and
+              Guntupalli, J Swaroop and Jenkinson, Mark and Keshavan, Anisha
+              and Kiar, Gregory and Liem, Franziskus and Raamana, Pradeep Reddy
+              and Raffelt, David and Steele, Christopher J and Quirion,
+              Pierre-Olivier and Smith, Robert E and Strother, Stephen C and
+              Varoquaux, Ga{\"e}l and Wang, Yida and Yarkoni, Tal and Poldrack,
+              Russell A",
   abstract = "The rate of progress in human neurosciences is limited by the
               inability to easily apply a wide range of analysis methods to the
               plethora of different datasets acquired in labs around the world.
@@ -134,7 +139,7 @@
               proof of concept, this work is accompanied by 22 ready to use
               BIDS Apps, packaging a diverse set of commonly used neuroimaging
               algorithms.",
-  journal  = "PLOS Computational Biology",
+  journal  = "PLoS Comput. Biol.",
   volume   =  13,
   number   =  3,
   pages    = "e1005209",
@@ -147,16 +152,10 @@
 @article{Poldrack2017-gz,
   title    = "Scanning the horizon: towards transparent and reproducible
               neuroimaging research",
-  author   = {Poldrack, Russell A and
-              Baker, Chris I and
-              Durnez, Joke and
-              Gorgolewski, Krzysztof J and
-              Matthews, Paul M and
-              Munaf{\`o}, Marcus R and
-              Nichols, Thomas E and
-              Poline, Jean-Baptiste and
-              Vul, Edward and
-              Yarkoni, Tal},
+  author   = "Poldrack, Russell A and Baker, Chris I and Durnez, Joke and
+              Gorgolewski, Krzysztof J and Matthews, Paul M and Munaf{\`o},
+              Marcus R and Nichols, Thomas E and Poline, Jean-Baptiste and Vul,
+              Edward and Yarkoni, Tal",
   abstract = "Functional neuroimaging techniques have transformed our ability
               to probe the neurobiological basis of behaviour and are
               increasingly being applied by the wider neuroscience community.
@@ -169,7 +168,7 @@
               outline current and suggested best practices, and describe how we
               think the field should evolve to produce the most meaningful and
               reliable answers to neuroscientific questions.",
-  journal  = "Nature Reviews Neuroscience",
+  journal  = "Nat. Rev. Neurosci.",
   volume   =  18,
   number   =  2,
   pages    = "115--126",
@@ -180,14 +179,9 @@
 }
 
 @article{mriqc,
-  title={MRIQC: Advancing the automatic prediction of image quality in {MRI} from unseen sites},
-  author={Esteban, Oscar and
-          Birman, Daniel and
-          Schaer, Marie and
-          Koyejo, Oluwasanmi O and
-          Poldrack, Russell A and
-          Gorgolewski, Krzysztof J},
-  journal={PLOS One},
+  title={MRIQC: Advancing the automatic prediction of image quality in MRI from unseen sites},
+  author={Esteban, Oscar and Birman, Daniel and Schaer, Marie and Koyejo, Oluwasanmi O and Poldrack, Russell A and Gorgolewski, Krzysztof J},
+  journal={PloS one},
   volume={12},
   number={9},
   pages={e0184661},
@@ -197,28 +191,13 @@
 }
 
 @article{fmriprep,
-  title={FMRIPrep: a robust preprocessing pipeline for functional {MRI}},
-  author={Esteban, Oscar and
-          Markiewicz, Christopher J and
-          Blair, Ross W and
-          Moodie, Craig A and
-          Isik, A Ilkay and
-          Erramuzpe, Asier and
-          Kent, James D and
-          Goncalves, Mathias and
-          DuPre, Elizabeth and
-          Snyder, Madeleine and
-          Oya, Hiroyuki and
-          Ghosh, Satrajit S and
-          Wright, Jessey and
-          Durnez, Joke and
-          Poldrack, Russell A and
-          Gorgolewski, Krzysztof J},
-  journal={Nature Methods},
+  title={FMRIPrep: a robust preprocessing pipeline for functional MRI},
+  author={Esteban, Oscar and Markiewicz, Christopher J and Blair, Ross W and Moodie, Craig A and Isik, A Ilkay and Erramuzpe, Asier and Kent, James D and Goncalves, Mathias and DuPre, Elizabeth and Snyder, Madeleine and others},
+  journal={Nature methods},
   volume={16},
   number={1},
   pages={111},
   year={2019},
   doi={10.1038/s41592-018-0235-4},
   publisher={Nature Publishing Group}
-}
+}
\ No newline at end of file
diff --git a/paper/paper.md b/paper/paper.md
index c1ab0725..9f3b9a82 100644
--- a/paper/paper.md
+++ b/paper/paper.md
@@ -152,7 +152,7 @@ own preprocessing and analysis procedures. The resulting lack of field-wide
 standards has severely limited reproducibility and data sharing and reuse.
 
 To address this problem, we and others recently introduced the Brain Imaging
-Data Standard (``BIDS``; [@Gorgolewski2016-nw]), a specification meant to
+Data Standard (``BIDS``; [@Gorgolewski2016-sk]), a specification meant to
 standardize the process of representing brain imaging data. BIDS is
 deliberately designed with adoption in mind; it adheres to a user-focused
 philosophy that prioritizes common use cases and discourages complexity. By
@@ -168,9 +168,9 @@ almost every tool designed to work with BIDS datasets involves regular
 file-filtering operations—there is a strong incentive to develop utility
 libraries that provide common functionality via a standardized, simple API.
 
-``PyBIDS`` is a Python package that makes it easier to work with BIDS
+``PyBIDS`` [@zenodo] is a Python package that makes it easier to work with BIDS
 datasets. In principle, its scope includes virtually any functionality that is
-likely to be of general use when working with BIDS datasets (*i.e.*, that is not
+likely to be of general use when working with BIDS datasets (i.e., that is not
 specific to one narrow context). At present, its core and most widely used
 module supports simple and flexible querying and manipulation of BIDS datasets.
 PyBIDS makes it easy for researchers and developers working in Python to search
@@ -183,7 +183,7 @@ In addition to this core functionality, PyBIDS also contains an ever-growing
 set of modules that support additional capabilities meant to keep up with the
 evolution and expansion of the BIDS specification itself. Currently, PyBIDS
 includes tools for (1) reading and manipulating data contained in various
-BIDS-defined files (*e.g.*, physiological recordings, event files, or
+BIDS-defined files (e.g., physiological recordings, event files, or
 participant-level variables); (2) constructing design matrices and contrasts
 that support the new ``BIDS-StatsModel`` specification (for machine-readable
 representation of fMRI statistical models); and (3) automated generation of
@@ -192,17 +192,17 @@ partial Methods sections for inclusion in publications.
 PyBIDS can be easily installed on all platforms via pip (``pip install
 pybids``), though currently it is not officially supported on Windows. The
 package has few dependencies outside of standard Python numerical and image
-analysis libraries (*i.e.*, numpy, scipy, pandas, and NiBabel). The core API
+analysis libraries (i.e., numpy, scipy, pandas, and NiBabel). The core API
 is deliberately kept minimalistic: nearly all interactions with PyBIDS
 functionality occur through a core ``BIDSLayout`` object initialized by passing
 in a path to a BIDS dataset. For most applications, no custom configuration
 should be required.
 
 Although technically still in alpha release, PyBIDS is already being used both
-as a dependency in dozens of other open-source brain imaging packages --
-*e.g.*, fMRIPrep [@fmriprep], MRIQC [@mriqc], datalad-neuroimaging
+as a dependency in dozens of other open-source brain imaging packages--e.g.,
+fMRIPrep [@fmriprep], MRIQC [@mriqc], datalad-neuroimaging
 (https://github.com/datalad/datalad-neuroimaging), and fitlins
-(https://github.com/poldracklab/fitlins) -- and directly in many researchers'
+(https://github.com/poldracklab/fitlins)--and directly in many researchers'
 custom Python workflows. Development is extremely active, with bug fixes and
 new features continually being added (https://github.com/bids-standard/pybids),
 and major releases occurring approximately every 6 months. As of this writing,
diff --git a/setup.py b/setup.py
index 7a6e2efa..3c681fe2 100755
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ ver_file = os.path.join('bids', 'version.py')
 with open(ver_file) as f:
     exec(f.read())
 
-opts = dict(name="pybids",
+opts = dict(name=NAME,
             maintainer=MAINTAINER,
             maintainer_email=MAINTAINER_EMAIL,
             description=DESCRIPTION,


Test patch:
diff --git a/bids/layout/tests/test_layout.py b/bids/layout/tests/test_layout.py
index ca263989..1b80c0f3 100644
--- a/bids/layout/tests/test_layout.py
+++ b/bids/layout/tests/test_layout.py
@@ -222,17 +222,6 @@ def test_get_return_type_dir(layout_7t_trt, layout_7t_trt_relpath):
     assert target == res2
 
 
-def test_get_val_none(layout_7t_trt):
-    t1w_files = layout_7t_trt.get(subject='01', ses='1', suffix='T1w')
-    assert len(t1w_files) == 1
-    assert 'acq' not in t1w_files[0].path
-    t1w_files = layout_7t_trt.get(subject='01', ses='1', suffix='T1w', acquisition=None)
-    assert len(t1w_files) == 1
-    t1w_files = layout_7t_trt.get(subject='01', ses='1', suffix='T1w', acquisition=[None])
-    assert len(t1w_files) == 1
-    bold_files = layout_7t_trt.get(subject='01', ses='1', suffix='bold', acquisition=None)
-    assert len(bold_files) == 0
-
 
 def test_get_return_sorted(layout_7t_trt):
     bids_files = layout_7t_trt.get(target='subject')
@@ -501,21 +490,6 @@ def test_get_layouts_in_scope(layout_ds005_multi_derivs):
     assert len(l._get_layouts_in_scope(['events', 'dummy'])) == 2
     assert len(l._get_layouts_in_scope(['derivatives'])) == 2
     assert len(l._get_layouts_in_scope('raw')) == 1
-    self_scope = l._get_layouts_in_scope('self')
-    assert len(self_scope) == 1
-    assert self_scope == [l]
-
-
-def test_get_dataset_description(layout_ds005_multi_derivs):
-    l = layout_ds005_multi_derivs
-    dd = l.get_dataset_description()
-    assert isinstance(dd, dict)
-    assert dd['Name'] == 'Mixed-gambles task'
-    dd = l.get_dataset_description('all', True)
-    assert isinstance(dd, list)
-    assert len(dd) == 3
-    names = {'Mixed-gambles task', 'Mixed-gambles task -- dummy derivative'}
-    assert set([d['Name'] for d in dd]) == names
 
 
 def test_indexed_file_associations(layout_7t_trt):
diff --git a/bids/layout/tests/test_models.py b/bids/layout/tests/test_models.py
index 8b36b059..fce08864 100644
--- a/bids/layout/tests/test_models.py
+++ b/bids/layout/tests/test_models.py
@@ -1,10 +1,7 @@
-import sys
 import os
 import pytest
 import bids
 import copy
-import json
-from pathlib import Path
 
 from sqlalchemy import create_engine
 from sqlalchemy.orm import sessionmaker
@@ -14,7 +11,6 @@ from bids.layout.models import (BIDSFile, Entity, Tag, Base, Config,
                                 FileAssociation, BIDSImageFile)
 from bids.layout import BIDSLayout
 from bids.tests import get_test_data_path
-from bids.external import six
 
 
 def create_session():
@@ -101,6 +97,13 @@ def test_file_associations():
     assert set(results) == {md1, md2}
 
 
+def test_tag_init(sample_bidsfile, subject_entity):
+    f, e = sample_bidsfile, subject_entity
+    tag = Tag(f, e, 'zzz')
+    rep = str(tag)
+    assert rep.startswith("<Tag file:") and f.path in rep and 'zzz' in rep
+
+
 def test_tag_dtype(sample_bidsfile, subject_entity):
     f, e = sample_bidsfile, subject_entity
     # Various ways of initializing--should all give same result
@@ -184,22 +187,11 @@ def test_bidsimagefile_get_image():
     path = "synthetic/sub-01/ses-01/func/sub-01_ses-01_task-nback_run-01_bold.nii.gz"
     path = path.split('/')
     path = os.path.join(get_test_data_path(), *path)
-    bf = BIDSImageFile(path)
+    bf = BIDSImageFile(path, None)
     assert bf.get_image() is not None
     assert bf.get_image().shape == (64, 64, 64, 64)
 
 
-def test_bidsjsonfile(layout_synthetic):
-    jf = layout_synthetic.get(suffix='bold', extension='json')[0]
-    d = jf.get_dict()
-    assert isinstance(d, dict)
-    assert d['RepetitionTime'] == 2.5
-    j = jf.get_json()
-    assert isinstance(j, six.string_types)
-    assert 'RepetitionTime' in j
-    assert json.loads(j) == d
-
-
 def test_bidsfile_get_metadata(layout_synthetic):
     bf = layout_synthetic.get(suffix='physio', extension='tsv.gz')[0]
     md = bf.get_metadata()
@@ -234,11 +226,3 @@ def test_bidsfile_get_entities(layout_synthetic):
     md2 = bf.get_entities(metadata=None)
     assert md == md2
     assert set(md.keys()) == md_ents | file_ents
-
-
-@pytest.mark.xfail(sys.version_info < (3, 6), reason="os.PathLike introduced in Python 3.6")
-def test_bidsfile_fspath(sample_bidsfile):
-    bf = sample_bidsfile
-    bf_path = Path(bf)
-    assert bf_path == Path(bf.path)
-    assert bf_path.read_text() == '###'
diff --git a/bids/reports/tests/test_parsing.py b/bids/reports/tests/test_parsing.py
index 12ef4b14..780be5e1 100644
--- a/bids/reports/tests/test_parsing.py
+++ b/bids/reports/tests/test_parsing.py
@@ -98,7 +98,7 @@ def test_parsing_parse(testlayout, testconfig):
     which is combined within task, across runs)
     """
     subj = '01'
-    niftis = testlayout.get(subject=subj, extension=[".nii", ".nii.gz"])
+    niftis = testlayout.get(subject=subj, extension='nii.gz')
     desc = parsing.parse_niftis(testlayout, niftis, subj, testconfig)
     assert isinstance(desc, list)
     assert isinstance(desc[0], str)
diff --git a/bids/variables/tests/test_variables.py b/bids/variables/tests/test_variables.py
index 9a07a40a..84d220bf 100644
--- a/bids/variables/tests/test_variables.py
+++ b/bids/variables/tests/test_variables.py
@@ -58,8 +58,9 @@ def test_merge_wrapper():
     data = pd.DataFrame({'amplitude': [4, 3, 2, 5]})
     sev = SimpleVariable('simple', data, 'dummy')
     # Should break if asked to merge different classes
-    with pytest.raises(ValueError, match='Variables of different classes'):
+    with pytest.raises(ValueError) as e:
         merge_variables([dev, sev])
+    assert "Variables of different classes" in str(e)
 
 
 def test_sparse_run_variable_to_dense(layout1):