diff --git a/lib/galaxy/managers/base.py b/lib/galaxy/managers/base.py index a2e15ab79dd..e06ca8de029 100644 --- a/lib/galaxy/managers/base.py +++ b/lib/galaxy/managers/base.py @@ -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') diff --git a/lib/galaxy/managers/history_contents.py b/lib/galaxy/managers/history_contents.py index 1a10146b7a1..70fb896de19 100644 --- a/lib/galaxy/managers/history_contents.py +++ b/lib/galaxy/managers/history_contents.py @@ -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): diff --git a/lib/galaxy/managers/taggable.py b/lib/galaxy/managers/taggable.py index 3e3c598f78c..da7de761428 100644 --- a/lib/galaxy/managers/taggable.py +++ b/lib/galaxy/managers/taggable.py @@ -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({ diff --git a/lib/galaxy/managers/tools.py b/lib/galaxy/managers/tools.py index a324c136017..03a849fbca6 100644 --- a/lib/galaxy/managers/tools.py +++ b/lib/galaxy/managers/tools.py @@ -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({ diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py index cfc9657be0e..77ecb7d9606 100644 --- a/lib/galaxy/util/__init__.py +++ b/lib/galaxy/util/__init__.py @@ -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 diff --git a/lib/galaxy/webapps/galaxy/api/datasets.py b/lib/galaxy/webapps/galaxy/api/datasets.py index c3c92f34c3f..28a70e76928 100644 --- a/lib/galaxy/webapps/galaxy/api/datasets.py +++ b/lib/galaxy/webapps/galaxy/api/datasets.py @@ -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] diff --git a/test/api/test_datasets.py b/test/api/test_datasets.py index 2e64c5fbce4..58f3fcd3103 100644 --- a/test/api/test_datasets.py +++ b/test/api/test_datasets.py @@ -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() diff --git a/test/unit/managers/base.py b/test/unit/managers/base.py index dcb7d0b7a6e..8377d48d6b1 100644 --- a/test/unit/managers/base.py +++ b/test/unit/managers/base.py @@ -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 diff --git a/test/unit/managers/test_HDAManager.py b/test/unit/managers/test_HDAManager.py index 8e372fa005a..7d4e80c0d8c 100644 --- a/test/unit/managers/test_HDAManager.py +++ b/test/unit/managers/test_HDAManager.py @@ -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')) diff --git a/test/unit/managers/test_HistoryContentsManager.py b/test/unit/managers/test_HistoryContentsManager.py index 255c4d17e88..17ee7ec3c19 100644 --- a/test/unit/managers/test_HistoryContentsManager.py +++ b/test/unit/managers/test_HistoryContentsManager.py @@ -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]]) diff --git a/test/unit/managers/test_HistoryManager.py b/test/unit/managers/test_HistoryManager.py index 21e363d6b76..eb5f05658c3 100644 --- a/test/unit/managers/test_HistoryManager.py +++ b/test/unit/managers/test_HistoryManager.py @@ -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')