Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/maint/0.14.x'
Browse files Browse the repository at this point in the history
  • Loading branch information
effigies committed Mar 29, 2022
2 parents c6f0597 + e3e7b7a commit e92d4e8
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 22 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,19 @@ be separately managed and follow a different development pace.
* MNT: Test on Python 3.10, various CI updates (#824)
* MNT: Avoid jinja2 v3 until nbconvert handles breakages (#823)

Version 0.14.1 (March 29, 2022)
-------------------------------
Bug-fix release in the 0.14.x series.

* RF/FIX: Decompose filter construction for special queries and lists (#826)

Includes the following back-ports from 0.15.0:

* FIX: Clarify exception message (#806)
* FIX: Catch UnicodeDecodeErrors along with JSONDecodeErrors for better reporting (#796)
* FIX: Accept paths/strings for layout configuration files (#799)
* ENH: Add __main__ to allow ``python -m bids`` to run CLI (#794)

Version 0.14.0 (November 09, 2021)
----------------------------------

Expand Down
64 changes: 42 additions & 22 deletions bids/layout/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,32 +752,52 @@ def _build_file_query(self, **kwargs):
for name, val in filters.items():
tag_alias = aliased(Tag)

if isinstance(val, (list, tuple)) and len(val) == 1:
val = val[0]
val_list = list(listify(val)) if val is not None else [None]
if not val_list:
continue

if Query.OPTIONAL in val_list:
continue

none = required = False
if None in val_list:
none = True
val_list.remove(None)
if Query.NONE in val_list:
none = True
val_list.remove(Query.NONE)
if Query.REQUIRED in val_list:
required = True
val_list.remove(Query.REQUIRED)

if none and required:
# Always true, apply no filter
continue

# Baseline, use join, start accumulating clauses
join_method = query.join
val_clauses = []

if val is None or val == Query.NONE:
# NONE and REQUIRED get special treatment
if none:
join_method = query.outerjoin
val_clause = tag_alias._value.is_(None)
elif val == Query.OPTIONAL:
continue
elif val == Query.ANY:
val_clause = tag_alias._value.isnot(None)
elif regex:
if isinstance(val, (list, tuple)):
val_clause = sa.or_(*[
tag_alias._value.op('REGEXP')(str(v))
for v in val
])
else:
val_clause = tag_alias._value.op('REGEXP')(str(val))
else:
vals = listify(val)
if isinstance(vals[0], int):
val_clause = cast(tag_alias._value, sa.Integer).in_(vals)
else:
val_clause = tag_alias._value.in_(vals)
val_clauses.append(tag_alias._value.is_(None))
if required:
val_clauses.append(tag_alias._value.isnot(None))

# Any remaining values
if regex:
val_clauses.extend([tag_alias._value.op('REGEXP')(str(v))
for v in val_list])
elif val_list:
_value = tag_alias._value
if isinstance(val_list[0], int):
_value = cast(tag_alias._value, sa.Integer)

val_clauses.append(_value.in_(val_list))

# Looking for intersection with list of vals, so use OR
val_clause = sa.or_(*val_clauses) if len(val_clauses) > 1 else val_clauses[0]

query = join_method(
tag_alias,
Expand Down
13 changes: 13 additions & 0 deletions bids/layout/tests/test_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,19 @@ def test_get_with_invalid_filters(layout_ds005):
l.get(**filters)


def test_get_with_query_constants_in_match_list(layout_ds005):
l = layout_ds005
get1 = l.get(subject='12', run=1, suffix='bold')
get_none = l.get(subject='12', run=None, suffix='bold')
get_any = l.get(subject='12', run=Query.ANY, suffix='bold')
get1_and_none = l.get(subject='12', run=[None, 1], suffix='bold')
get1_and_any = l.get(subject='12', run=[Query.ANY, 1], suffix='bold')
get_none_and_any = l.get(subject='12', run=[Query.ANY, Query.NONE], suffix='bold')
assert set(get1_and_none) == set(get1) | set(get_none)
assert set(get1_and_any) == set(get1) | set(get_any)
assert set(get_none_and_any) == set(get_none) | set(get_any)


def test_load_layout(layout_synthetic_nodb, db_dir):
db_path = str(db_dir / 'tmp_db')
layout_synthetic_nodb.save(db_path)
Expand Down

0 comments on commit e92d4e8

Please sign in to comment.