Repository: bids-standard/pybids

Issue:
Stochastic bug: sqlalchemy.orm.exc.DetachedInstanceError
Example: https://travis-ci.org/github/bids-standard/pybids/jobs/694363424#L828-L907

```
layout_7t_trt = BIDS Layout: .../pybids/bids/tests/data/7t_trt | Subjects: 10 | Sessions: 20 | Runs: 20
layout_7t_trt_relpath = BIDS Layout: .../pybids/bids/tests/data/7t_trt | Subjects: 10 | Sessions: 20 | Runs: 20
    def test_get_return_type_dir(layout_7t_trt, layout_7t_trt_relpath):
        query = dict(target='subject', return_type='dir')
        # In case of relative paths
>       res_relpath = layout_7t_trt_relpath.get(**query)
bids/layout/tests/test_layout.py:257:  
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
bids/layout/layout.py:1008: in get
    results = [x for x in results if target in x.entities]
bids/layout/layout.py:1008: in <listcomp>
    results = [x for x in results if target in x.entities]
venv/lib/python3.6/site-packages/sqlalchemy/ext/associationproxy.py:191: in __get__
    inst = self._as_instance(class_, obj)
venv/lib/python3.6/site-packages/sqlalchemy/ext/associationproxy.py:264: in _as_instance
    return inst._non_canonical_get_for_object(obj)
venv/lib/python3.6/site-packages/sqlalchemy/ext/associationproxy.py:792: in _non_canonical_get_for_object
    actual_obj = getattr(parent_instance, self.target_collection)
venv/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py:287: in __get__
    return self.impl.get(instance_state(instance), dict_)
venv/lib/python3.6/site-packages/sqlalchemy/orm/attributes.py:723: in get
    value = self.callable_(state, passive)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <sqlalchemy.orm.strategies.LazyLoader object at 0x7f5e76ae1990>
state = <sqlalchemy.orm.state.InstanceState object at 0x7f5e5a795ac8>
passive = symbol('PASSIVE_OFF')
    def _load_for_state(self, state, passive):

        if not state.key and (
            (
                not self.parent_property.load_on_pending
                and not state._load_pending
            )
            or not state.session_id
        ):
            return attributes.ATTR_EMPTY
    
        pending = not state.key
        primary_key_identity = None

        if (not passive & attributes.SQL_OK and not self.use_get) or (
            not passive & attributes.NON_PERSISTENT_OK and pending
        ):
            return attributes.PASSIVE_NO_RESULT
    
        if (
            # we were given lazy="raise"
            self._raise_always
            # the no_raise history-related flag was not passed
            and not passive & attributes.NO_RAISE
            and (
                # if we are use_get and related_object_ok is disabled,
                # which means we are at most looking in the identity map
                # for history purposes or otherwise returning
                # PASSIVE_NO_RESULT, don't raise.  This is also a
                # history-related flag
                not self.use_get
                or passive & attributes.RELATED_OBJECT_OK
            )
        ):

            self._invoke_raise_load(state, passive, "raise")

        session = _state_session(state)
        if not session:
            if passive & attributes.NO_RAISE:
                return attributes.PASSIVE_NO_RESULT

            raise orm_exc.DetachedInstanceError(
                "Parent instance %s is not bound to a Session; "
                "lazy load operation of attribute '%s' cannot proceed"
>               % (orm_util.state_str(state), self.key)
            )
E           sqlalchemy.orm.exc.DetachedInstanceError: Parent instance <BIDSImageFile at 0x7f5e833df150> is not bound to a Session; lazy load operation of attribute 'tags' cannot proceed (Background on this error at: http://sqlalche.me/e/bhk3)
venv/lib/python3.6/site-packages/sqlalchemy/orm/strategies.py:710: DetachedInstanceError
```


Discussion:
Oh goddammit. This is almost certainly caused by the "fix" in #616. I couldn't reproduce the original problem, so I thought it was safe to remove the eager loading (see [this comment](https://github.com/bids-standard/pybids/issues/533#issuecomment-627994884)). Apparently I was wrong.

The fix for this is trivial (revert #616), but the downside is that this will re-introduce the slowness described in #533. Fixing both of these things at once will probably require delving into how SQLAlchemy manages sessions, which I don't have time to do at the moment. But I think the sensible thing to do is revert to eager loading, as random failure is worse than reliable slowness...

Resolving patch:
diff --git a/bids/layout/layout.py b/bids/layout/layout.py
--- a/bids/layout/layout.py
+++ b/bids/layout/layout.py
@@ -992,6 +992,10 @@ def get(self, return_type='object', target=None, scope='all',
         for l in layouts:
             query = l._build_file_query(filters=filters,
                                         regex_search=regex_search)
+            # Eager load associations, because mixing queries from different
+            # DB sessions causes objects to detach
+            query = query.options(joinedload(BIDSFile.tags)
+                                  .joinedload(Tag.entity))
             results.extend(query.all())
 
         # Convert to relative paths if needed


Test patch: