Make sure orm filters operate on correct model

In practice we use ParsedFiler.filter_type to discriminate
the different filter types. Filters that require the correct
model class for a specific query are now functions that take
a model_class and generate the correct filter.
This commit is contained in:
mvdbeek
2019-04-18 18:55:14 +02:00
parent 2b95d6911d
commit e7b0867602
11 changed files with 171 additions and 123 deletions
+16 -13
View File
@@ -36,9 +36,12 @@ from six import string_types
from galaxy import exceptions
from galaxy import model
from galaxy.model import tool_shed_install
from galaxy.util import namedtuple
log = logging.getLogger(__name__)
parsed_filter = namedtuple("ParsedFilter", "filter_type filter")
# ==== accessors from base/controller.py
def security_check(trans, item, check_ownership=False, check_accessible=False):
@@ -323,18 +326,16 @@ class ModelManager(object):
if not isinstance(filters, list):
filters = [filters]
for filter_ in filters:
if self._is_fn_filter(filter_):
fn_filters.append(filter_)
else:
if not hasattr(filter_, 'filter_type'):
orm_filters.append(filter_)
elif filter_.filter_type == 'function':
fn_filters.append(filter_.filter)
elif filter_.filter_type == 'orm_function':
orm_filters.append(filter_.filter(self.model_class))
else:
orm_filters.append(filter_.filter)
return (orm_filters, fn_filters)
def _is_fn_filter(self, filter_):
"""
Returns True if `filter_` is a functional filter to be applied after the SQL query.
"""
return callable(filter_)
def _orm_list(self, query=None, **kwargs):
"""
Sends kwargs to build the query return all models found.
@@ -380,7 +381,7 @@ class ModelManager(object):
"""
if not ids:
return []
ids_filter = self.model_class.id.in_(ids)
ids_filter = parsed_filter("orm", self.model_class.id.in_(ids))
found = self.list(filters=self._munge_filters(ids_filter, filters), **kwargs)
# TODO: this does not order by the original 'ids' array
@@ -899,6 +900,8 @@ class ModelFilterParser(HasAModelManager):
#: model class
model_class = None
subcontainer_model_class = None
parsed_filter = parsed_filter
def __init__(self, app, **kwargs):
"""
@@ -998,7 +1001,7 @@ class ModelFilterParser(HasAModelManager):
val = val_parser(val)
# curry/partial and fold the val in there now
return lambda i: filter_fn(i, val)
return self.parsed_filter(filter_type="function", filter=lambda i: filter_fn(i, val))
# ---- ORM filters
def _parse_orm_filter(self, attr, op, val):
@@ -1013,7 +1016,7 @@ class ModelFilterParser(HasAModelManager):
# no column mapping (not whitelisted)
return None
if callable(column_map):
return column_map(attr, op, val)
return self.parsed_filter(filter_type="orm_function", filter=column_map(attr, op, val))
# attr must be a whitelisted column by attr name or by key passed in column_map
# note: column_map[ 'column' ] takes precedence
if 'column' in column_map:
@@ -1040,7 +1043,7 @@ class ModelFilterParser(HasAModelManager):
val = val_parser(val)
orm_filter = op(val)
return orm_filter
return self.parsed_filter(filter_type="orm", filter=orm_filter)
#: these are the easier/shorter string equivalents to the python operator fn names that need '__' around them
UNDERSCORED_OPS = ('lt', 'le', 'eq', 'ne', 'ge', 'gt')
+50 -41
View File
@@ -153,8 +153,8 @@ class HistoryContentsManager(containers.ContainerManagerMixin):
Note: does not include deleted/hidden contents.
"""
filters = [
sql.column('deleted') == false(),
sql.column('visible') == true()
base.ModelFilterParser.parsed_filter("orm", sql.column('deleted') == false()),
base.ModelFilterParser.parsed_filter("orm", sql.column('visible') == true())
]
contents_subquery = self._union_of_contents_query(history, filters=filters).subquery()
statement = (sql.select([sql.column('state'), func.count('*')])
@@ -250,7 +250,6 @@ class HistoryContentsManager(containers.ContainerManagerMixin):
# cycle back over the union query to create an ordered list of the objects returned in queries 2 & 3 above
contents = []
filters = kwargs.get('filters') or []
filters = [f for f in filters if callable(f)]
# TODO: or as generator?
for result in contents_results:
result_type = self._get_union_type(result)
@@ -263,8 +262,9 @@ class HistoryContentsManager(containers.ContainerManagerMixin):
@staticmethod
def passes_filters(content, filters):
for filter_fn in filters:
if not filter_fn(content):
return False
if filter_fn.filter_type == 'function':
if not filter_fn.filter(content):
return False
return True
def _union_of_contents_query(self, container, filters=None, limit=None, offset=None, order_by=None, **kwargs):
@@ -287,12 +287,19 @@ class HistoryContentsManager(containers.ContainerManagerMixin):
# query 1: create a union of common columns for which the component_classes can be filtered/limited
contained_query = self._contents_common_query_for_contained(container.id if container else None)
subcontainer_query = self._contents_common_query_for_subcontainer(container.id if container else None)
filters = filters or []
# Apply filters that are specific to a model
for orm_filter in filters:
if orm_filter.filter_type == "orm_function":
contained_query = contained_query.filter(orm_filter.filter(self.contained_class))
subcontainer_query = subcontainer_query.filter(orm_filter.filter(self.subcontainer_class))
contents_query = contained_query.union(subcontainer_query)
for orm_filter in (filters or []):
if not callable(orm_filter):
# skip python filters that need to be applied after the qeury
contents_query = contents_query.filter(orm_filter)
for orm_filter in filters:
if orm_filter.filter_type == "orm":
contents_query = contents_query.filter(orm_filter.filter)
contents_query = contents_query.order_by(*order_by)
if limit is not None:
@@ -440,8 +447,8 @@ class HistoryContentsFilters(base.ModelFilterParser,
tools.ToolFilterMixin):
# surprisingly (but ominously), this works for both content classes in the union that's filtered
model_class = model.HistoryDatasetAssociation
subcontainer_model_class = model.HistoryDatasetCollectionAssociation
# TODO: history_content_type filter doesn't work with psycopg2: column does not exist (even with hybrid props)
def _parse_orm_filter(self, attr, op, val):
# we need to use some manual/text/column fu here since some where clauses on the union don't work
@@ -449,41 +456,43 @@ class HistoryContentsFilters(base.ModelFilterParser,
# (and some of these are *not* a normal columns (especially 'state') anyway)
# TODO: genericize these - can probably extract a _get_column( attr, ... ) or something
# special cases...special cases everywhere
if attr == 'history_content_type' and op == 'eq':
if val == 'dataset':
return sql.column('history_content_type') == 'dataset'
if val == 'dataset_collection':
return sql.column('history_content_type') == 'dataset_collection'
self.raise_filter_err(attr, op, val, 'bad op in filter')
def get_filter(attr, op, val):
if attr == 'history_content_type' and op == 'eq':
if val in ('dataset', 'dataset_collection'):
return sql.column('history_content_type') == val
self.raise_filter_err(attr, op, val, 'bad op in filter')
if attr == 'type_id':
if op == 'eq':
return sql.column('type_id') == val
if op == 'in':
return sql.column('type_id').in_(self.parse_type_id_list(val))
self.raise_filter_err(attr, op, val, 'bad op in filter')
if attr == 'type_id':
if op == 'eq':
return sql.column('type_id') == val
if op == 'in':
return sql.column('type_id').in_(self.parse_type_id_list(val))
self.raise_filter_err(attr, op, val, 'bad op in filter')
if attr in ('update_time', 'create_time'):
if op == 'ge':
return sql.column(attr) >= self.parse_date(val)
if op == 'le':
return sql.column(attr) <= self.parse_date(val)
self.raise_filter_err(attr, op, val, 'bad op in filter')
if attr in ('update_time', 'create_time'):
if op == 'ge':
return sql.column(attr) >= self.parse_date(val)
if op == 'le':
return sql.column(attr) <= self.parse_date(val)
self.raise_filter_err(attr, op, val, 'bad op in filter')
if attr == 'state':
valid_states = model.Dataset.states.values()
if op == 'eq':
if val not in valid_states:
self.raise_filter_err(attr, op, val, 'invalid state in filter')
return sql.column('state') == val
if op == 'in':
states = [s for s in val.split(',') if s]
for state in states:
if state not in valid_states:
self.raise_filter_err(attr, op, state, 'invalid state in filter')
return sql.column('state').in_(states)
self.raise_filter_err(attr, op, val, 'bad op in filter')
if attr == 'state':
valid_states = model.Dataset.states.values()
if op == 'eq':
if val not in valid_states:
self.raise_filter_err(attr, op, val, 'invalid state in filter')
return sql.column('state') == val
if op == 'in':
states = [s for s in val.split(',') if s]
for state in states:
if state not in valid_states:
self.raise_filter_err(attr, op, state, 'invalid state in filter')
return sql.column('state').in_(states)
self.raise_filter_err(attr, op, val, 'bad op in filter')
column_filter = get_filter(attr, op, val)
if column_filter is not None:
return self.parsed_filter(filter_type='orm', filter=column_filter)
return super(HistoryContentsFilters, self)._parse_orm_filter(attr, op, val)
def decode_type_id(self, type_id):
+28 -18
View File
@@ -96,26 +96,36 @@ class TaggableDeserializerMixin(object):
class TaggableFilterMixin(object):
valid_ops = ('eq', 'contains', 'has')
def create_tag_filter(self, attr, op, val):
target_model = getattr(model, "%sTagAssociation" % self.model_class.__name__)
id_column = "%s_id" % target_model.table.name.rsplit('_tag_association')[0]
if ':' not in val and op == 'eq':
# We require an exact match and the tag to look for has no user_value,
# so we can't just concatenate user_tname, ':' and user_vale
column = target_model.table.c.user_tname
else:
def _create_tag_filter(model_class=None):
if op not in TaggableFilterMixin.valid_ops:
self.raise_filter_err(attr, op, val, 'bad op in filter')
if model_class is None:
return True
class_name = model_class.__name__
if class_name == 'HistoryDatasetCollectionAssociation':
# Unfortunately we were a little inconsistent with out naming scheme
class_name = 'HistoryDatasetCollection'
target_model = getattr(model, "%sTagAssociation" % class_name)
id_column = "%s_id" % target_model.table.name.rsplit('_tag_association')[0]
column = target_model.table.c.user_tname + ":" + target_model.table.c.user_value
if op == 'eq':
cond = column == val
elif op in ('contains', 'has'):
cond = column.contains(val, autoescape=True)
else:
self.raise_filter_err(attr, op, val, 'bad op in filter')
return sql.expression.and_(
# TODO: generalize to all sorts of tag associations
self.model_class.table.c.id == getattr(target_model.table.c, id_column),
cond
)
if op == 'eq':
if ':' not in val:
# We require an exact match and the tag to look for has no user_value,
# so we can't just concatenate user_tname, ':' and user_vale
cond = target_model.table.c.user_tname == val
else:
cond = column == val
else:
cond = column.contains(val, autoescape=True)
return sql.expression.and_(
model_class.table.c.id == getattr(target_model.table.c, id_column),
cond
)
return _create_tag_filter
def _add_parsers(self):
self.orm_filter_parsers.update({
+24 -11
View File
@@ -96,17 +96,30 @@ class DynamicToolManager(ModelManager):
class ToolFilterMixin(object):
def create_tool_filter(self, attr, op, val):
if op == 'eq':
cond = model.Job.table.c.tool_id == val
elif op == 'contains':
cond = model.Job.table.c.tool_id.contains(val, autoescape=True)
else:
self.raise_filter_err(attr, op, val, 'bad op in filter')
return sql.expression.and_(
model.Job.table.c.id == model.JobToOutputDatasetAssociation.table.c.job_id,
model.HistoryDatasetAssociation.table.c.id == model.JobToOutputDatasetAssociation.table.c.dataset_id,
cond
)
def _create_tool_filter(model_class=None):
if op == 'eq':
cond = model.Job.table.c.tool_id == val
elif op == 'contains':
cond = model.Job.table.c.tool_id.contains(val, autoescape=True)
else:
self.raise_filter_err(attr, op, val, 'bad op in filter')
if model_class is model.HistoryDatasetAssociation:
return sql.expression.and_(
model.Job.table.c.id == model.JobToOutputDatasetAssociation.table.c.job_id,
model.HistoryDatasetAssociation.table.c.id == model.JobToOutputDatasetAssociation.table.c.dataset_id,
cond
)
elif model_class is model.HistoryDatasetCollectionAssociation:
return sql.expression.and_(
model.Job.id == model.JobToOutputDatasetAssociation.job_id,
model.JobToOutputDatasetAssociation.dataset_id == model.DatasetCollectionElement.hda_id,
model.DatasetCollectionElement.dataset_collection_id == model.HistoryDatasetCollectionAssociation.collection_id,
cond,
)
else:
return True
return _create_tool_filter
def _add_parsers(self):
self.orm_filter_parsers.update({
+2
View File
@@ -63,6 +63,8 @@ inflector = Inflector(English)
log = get_logger(__name__)
_lock = threading.RLock()
namedtuple = collections.namedtuple
CHUNK_SIZE = 65536 # 64k
DATABASE_MAX_STRING_SIZE = 32768
+6 -1
View File
@@ -35,6 +35,7 @@ class DatasetsController(BaseAPIController, UsesVisualizationMixin):
def __init__(self, app):
super(DatasetsController, self).__init__(app)
self.history_manager = managers.histories.HistoryManager(app)
self.hda_manager = managers.hdas.HDAManager(app)
self.hda_serializer = managers.hdas.HDASerializer(app)
self.hdca_serializer = managers.hdcas.HDCASerializer(app)
@@ -55,6 +56,7 @@ class DatasetsController(BaseAPIController, UsesVisualizationMixin):
trans,
limit=500,
offset=0,
history_id=None,
**kwd):
"""
GET /api/datasets/
@@ -109,8 +111,11 @@ class DatasetsController(BaseAPIController, UsesVisualizationMixin):
filter_params = self.parse_filter_params(kwd)
filters = self.history_contents_filters.parse_filters(filter_params)
order_by = self._parse_order_by(manager=self.history_contents_manager, order_by_string=kwd.get('order', 'create_time-dsc'))
container = None
if history_id:
container = self.history_manager.get_accessible(self.decode_id(history_id), trans.user)
contents = self.history_contents_manager.contents(
container=None, filters=filters, limit=limit, offset=offset, order_by=order_by
container=container, filters=filters, limit=limit, offset=offset, order_by=order_by
)
return [self.serializer_by_type[content.history_content_type].serialize_to_view(content, user=trans.user, trans=trans, view='summary') for content in contents]
+5 -5
View File
@@ -71,12 +71,12 @@ class DatasetsApiTestCase(api.ApiTestCase):
assert len(self._get("datasets", payload).json()) == 1
self.dataset_collection_populator.create_list_in_history(self.history_id,
name="search by tool id",
contents=["1\n2\n3"])
payload = {'limit': 1, 'offset': 0, 'q': ['history_content_type', 'tool_id'],
'qv': ['dataset_collection', 'upload1']}
contents=["1\n2\n3"]).json()
self.dataset_populator.wait_for_history(self.history_id)
payload = {'limit': 10, 'offset': 0, 'history_id': self.history_id, 'q': ['name', 'tool_id'],
'qv': ['search by tool id', 'upload1']}
result = self._get("datasets", payload).json()
assert len(result) == 1
assert result[0]['name'] == 'search by tool id'
assert result[0]['name'] == 'search by tool id', result
payload = {'limit': 1, 'offset': 0, 'q': ['history_content_type', 'tool_id'],
'qv': ['dataset_collection', 'uploadX']}
result = self._get("datasets", payload).json()
+10 -6
View File
@@ -100,14 +100,18 @@ class BaseTestCase(unittest.TestCase):
self.assertTrue(True, 'is uuid: ' + item)
def assertORMFilter(self, item, msg=None):
if not isinstance(item, (sqlalchemy.sql.elements.BinaryExpression, sqlalchemy.sql.elements.BooleanClauseList)):
self.fail('Not an orm filter: ' + str(type(item)))
self.assertTrue(True, msg or ('is an orm filter: ' + str(item)))
if not isinstance(item.filter, (sqlalchemy.sql.elements.BinaryExpression, sqlalchemy.sql.elements.BooleanClauseList)):
self.fail('Not an orm filter: ' + str(type(item.filter)))
self.assertTrue(True, msg or ('is an orm filter: ' + str(item.filter)))
def assertORMFunctionFilter(self, item, msg=None):
assert item.filter_type == 'orm_function'
assert callable(item.filter)
def assertFnFilter(self, item, msg=None):
if not item or not callable(item):
self.fail('Not a fn filter: ' + str(type(item)))
self.assertTrue(True, msg or ('is a fn filter: ' + str(item)))
if not item.filter or not callable(item.filter):
self.fail('Not a fn filter: ' + str(type(item.filter)))
self.assertTrue(True, msg or ('is a fn filter: ' + str(item.filter)))
def assertIsJsonifyable(self, item):
# TODO: use galaxy's override
+2 -2
View File
@@ -655,8 +655,8 @@ class HDAFilterParserTestCase(HDATestCase):
self.assertORMFilter(self.filter_parser.parse_filter('state', 'in', ['queued', 'running']))
self.assertORMFilter(self.filter_parser.parse_filter('visible', 'eq', True))
# taggable
self.assertORMFilter(self.filter_parser.parse_filter('tag', 'eq', 'wot'))
self.assertORMFilter(self.filter_parser.parse_filter('tag', 'has', 'wot'))
self.assertORMFunctionFilter(self.filter_parser.parse_filter('tag', 'eq', 'wot'))
self.assertORMFunctionFilter(self.filter_parser.parse_filter('tag', 'has', 'wot'))
# genomebuild
self.assertFnFilter(self.filter_parser.parse_filter('genome_build', 'eq', 'wot'))
self.assertFnFilter(self.filter_parser.parse_filter('genome_build', 'contains', 'wot'))
@@ -8,7 +8,7 @@ import unittest
from sqlalchemy import column, desc, false, true
from sqlalchemy.sql import text
from galaxy.managers import collections, hdas, history_contents
from galaxy.managers import base, collections, hdas, history_contents
from galaxy.managers.histories import HistoryManager
from .base import BaseTestCase
from .base import CreatesCollectionsMixin
@@ -17,6 +17,7 @@ default_password = '123456'
user2_data = dict(email='user2@user2.user2', username='user2', password=default_password)
user3_data = dict(email='user3@user3.user3', username='user3', password=default_password)
user4_data = dict(email='user4@user4.user4', username='user4', password=default_password)
parsed_filter = base.ModelFilterParser.parsed_filter
# =============================================================================
@@ -135,15 +136,15 @@ class HistoryAsContainerTestCase(HistoryAsContainerBaseTestCase):
self.app.model.context.flush()
# TODO: cross db compat?
filters = [text('deleted = 1')]
filters = [parsed_filter(filter_type="orm", filter=text('deleted = 1'))]
self.assertEqual(self.contents_manager.contents(history, filters=filters), deleted)
# even stranger that sqlalx can use the first model in the union (HDA) for columns across the union
HDA = self.hda_manager.model_class
self.assertEqual(self.contents_manager.contents(history,
filters=[HDA.deleted == true()]), deleted)
filters=[parsed_filter("orm", HDA.deleted == true())]), deleted)
filter_limited_contents = self.contents_manager.contents(history,
filters=[HDA.deleted == true()], limit=2, offset=1)
filters=[parsed_filter("orm", HDA.deleted == true())], limit=2, offset=1)
self.assertEqual(filter_limited_contents, deleted[1:])
self.log("should allow filter on visible")
@@ -153,43 +154,43 @@ class HistoryAsContainerTestCase(HistoryAsContainerBaseTestCase):
invisible = [contents[2], contents[5], contents[6]]
self.app.model.context.flush()
filters = [text('visible = 0')]
filters = [parsed_filter("orm", text('visible = 0'))]
self.assertEqual(self.contents_manager.contents(history, filters=filters), invisible)
self.assertEqual(self.contents_manager.contents(history,
filters=[HDA.visible == false()]), invisible)
filters=[parsed_filter("orm", HDA.visible == false())]), invisible)
filter_limited_contents = self.contents_manager.contents(history,
filters=[HDA.visible == false()], limit=2, offset=1)
filters=[parsed_filter("orm", HDA.visible == false())], limit=2, offset=1)
self.assertEqual(filter_limited_contents, invisible[1:])
self.log("should allow filtering more than one attribute")
deleted_and_invisible = [contents[6]]
filters = [text('deleted = 1'), text('visible = 0')]
filters = [parsed_filter("orm", text('deleted = 1')), parsed_filter("orm", text('visible = 0'))]
self.assertEqual(self.contents_manager.contents(history, filters=filters), deleted_and_invisible)
self.assertEqual(self.contents_manager.contents(history,
filters=[HDA.deleted == true(), HDA.visible == false()]), deleted_and_invisible)
filters=[parsed_filter("orm", HDA.deleted == true()), parsed_filter("orm", HDA.visible == false())]), deleted_and_invisible)
offset_too_far = self.contents_manager.contents(history,
filters=[HDA.deleted == true(), HDA.visible == false()], limit=2, offset=1)
filters=[parsed_filter("orm", HDA.deleted == true()), parsed_filter("orm", HDA.visible == false())], limit=2, offset=1)
self.assertEqual(offset_too_far, [])
self.log("should allow filtering more than one attribute")
deleted_and_invisible = [contents[6]]
# note the two syntaxes both work
self.assertEqual(self.contents_manager.contents(history,
filters=[text('deleted = 1'), text('visible = 0')]), deleted_and_invisible)
filters=[parsed_filter("orm", text('deleted = 1')), parsed_filter("orm", text('visible = 0'))]), deleted_and_invisible)
self.assertEqual(self.contents_manager.contents(history,
filters=[HDA.deleted == true(), HDA.visible == false()]), deleted_and_invisible)
filters=[parsed_filter("orm", HDA.deleted == true()), parsed_filter("orm", HDA.visible == false())]), deleted_and_invisible)
offset_too_far = self.contents_manager.contents(history,
filters=[HDA.deleted == true(), HDA.visible == false()], limit=2, offset=1)
filters=[parsed_filter("orm", HDA.deleted == true()), parsed_filter("orm", HDA.visible == false())], limit=2, offset=1)
self.assertEqual(offset_too_far, [])
self.log("should allow filtering using like")
# find 'hda-4'
self.assertEqual([contents[4]],
self.contents_manager.contents(history, filters=[HDA.name.like('%-4')]))
self.contents_manager.contents(history, filters=[parsed_filter("orm", HDA.name.like('%-4'))]))
# the collections added above have the default name 'test collection'
self.assertEqual(self.contents_manager.subcontainers(history),
self.contents_manager.contents(history, filters=[HDA.name.like('%collect%')]))
self.contents_manager.contents(history, filters=[parsed_filter("orm", HDA.name.like('%collect%'))]))
def test_order_by(self):
user2 = self.user_manager.create(**user2_data)
@@ -245,7 +246,7 @@ class HistoryAsContainerTestCase(HistoryAsContainerBaseTestCase):
update_time = item.collection.update_time
return update_time
results = self.contents_manager.contents(history, filters=[column('update_time') >= update_time])
results = self.contents_manager.contents(history, filters=[parsed_filter("orm", column('update_time') >= update_time)])
self.assertEqual(results, [contents[3]])
def test_filtered_counting(self):
@@ -269,11 +270,11 @@ class HistoryAsContainerTestCase(HistoryAsContainerBaseTestCase):
self.app.model.context.flush()
HDA = self.hda_manager.model_class
self.assertEqual(self.contents_manager.contents_count(history, filters=[HDA.deleted == true()]), 3)
filters = [text('visible = 0')]
self.assertEqual(self.contents_manager.contents_count(history, filters=[parsed_filter("orm", HDA.deleted == true())]), 3)
filters = [parsed_filter("orm", text('visible = 0'))]
self.assertEqual(self.contents_manager.contents_count(history, filters=filters), 3)
filters = [text('deleted = 1'), text('visible = 0')]
filters = [parsed_filter("orm", text('deleted = 1')), parsed_filter("orm", text('visible = 0'))]
self.assertEqual(self.contents_manager.contents_count(history, filters=filters), 1)
def test_type_id(self):
@@ -286,13 +287,13 @@ class HistoryAsContainerTestCase(HistoryAsContainerBaseTestCase):
contents.append(self.add_list_collection_to_history(history, contents[4:6]))
self.log("should be able to use eq and in with hybrid type_id")
filters = [column('type_id') == u'dataset-2']
filters = [parsed_filter("orm", column('type_id') == u'dataset-2')]
self.assertEqual(self.contents_manager.contents(history, filters=filters), [contents[1]])
filters = [column('type_id').in_([u'dataset-1', u'dataset-3'])]
filters = [parsed_filter("orm", column('type_id').in_([u'dataset-1', u'dataset-3']))]
self.assertEqual(self.contents_manager.contents(history, filters=filters), [contents[0], contents[2]])
filters = [column('type_id') == u'dataset_collection-1']
filters = [parsed_filter("orm", column('type_id') == u'dataset_collection-1')]
self.assertEqual(self.contents_manager.contents(history, filters=filters), [contents[3]])
filters = [column('type_id').in_([u'dataset-2', u'dataset_collection-2'])]
filters = [parsed_filter("orm", column('type_id').in_([u'dataset-2', u'dataset_collection-2']))]
self.assertEqual(self.contents_manager.contents(history, filters=filters), [contents[1], contents[6]])
+4 -3
View File
@@ -29,6 +29,7 @@ default_password = '123456'
user2_data = dict(email='user2@user2.user2', username='user2', password=default_password)
user3_data = dict(email='user3@user3.user3', username='user3', password=default_password)
user4_data = dict(email='user4@user4.user4', username='user4', password=default_password)
parsed_filter = base.ModelFilterParser.parsed_filter
class HistoryManagerTestCase(BaseTestCase):
@@ -697,7 +698,7 @@ class HistoryFiltersTestCase(BaseTestCase):
self.assertEqual(len(filters), 3)
self.log('values should be parsed')
self.assertIsInstance(filters[1].right, sqlalchemy.sql.elements.True_)
self.assertIsInstance(filters[1].filter.right, sqlalchemy.sql.elements.True_)
def test_parse_filters_invalid_filters(self):
self.log('should error on non-column attr')
@@ -763,7 +764,7 @@ class HistoryFiltersTestCase(BaseTestCase):
history3 = self.history_manager.create(name='history3', user=user2)
filters = self.filter_parser.parse_filters([('annotation', 'has', 'no play'), ])
anno_filter = filters[0]
anno_filter = filters[0].filter
history3.add_item_annotation(self.trans.sa_session, user2, history3, "All work and no play")
self.trans.sa_session.flush()
@@ -797,7 +798,7 @@ class HistoryFiltersTestCase(BaseTestCase):
self.log('should have parsed out a single filter')
self.assertEqual(len(filters), 1)
filter_ = filters[0]
filter_ = filters[0].filter
fake = galaxy_mock.OpenObject()
fake.name = '123'
self.log('123 should return true through the filter')