diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index ca42cdd43f8..c1d750ba5fb 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -270,14 +270,39 @@ def _unwrapunits(self, obj, units=None): return obj return obj.getValue() - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods - such as getObjects("Project") - """ - return ("select obj from %s obj join fetch obj.details.owner " - "as owner join fetch obj.details.group " - "join fetch obj.details.creationEvent" % self.OMERO_CLASS) + such as getObjects("Project"). + Returns a tuple of (query, clauses, params). + Overridden by sub-classes to specify loading of different + portions of the graph. + Different sub-classes may allow some control over what's loaded + and filtering of the query using various opts arguments. + Opts: + 'child_count': boolean If true, this will produce a 'projection' + query that also selects child_count for + objects that have a LINK_CLASS + See different sub-classes for additional opts. + + :param opts: Dictionary of optional parameters. + :return: Tuple of string, list, ParametersI + """ + extra_select = "" + child_count = False + if opts is not None and 'child_count' in opts: + child_count = opts['child_count'] + if child_count and self.LINK_CLASS is not None: + extra_select = """, (select count(id) from %s chl + where chl.parent=obj.id)""" % self.LINK_CLASS + query = ("select obj %s from %s obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent" % + (extra_select, self.OMERO_CLASS)) + + params = omero.sys.ParametersI() + clauses = [] + return (query, clauses, params) def _getChildWrapper(self): """ @@ -2586,6 +2611,7 @@ def listOrphans(self, obj_type, eid=None, params=None, loadPixels=False): wrapper = KNOWN_WRAPPERS.get(obj_type.lower(), None) query = wrapper()._getQueryString() + query = wrapper()._getQueryString()[0] if loadPixels and obj_type == 'Image': # left outer join so we don't exclude @@ -3014,7 +3040,7 @@ def getObject(self, obj_type, oid=None, params=None, attributes=None): return wrapper(self, result) def getObjects(self, obj_type, ids=None, params=None, attributes=None, - respect_order=False): + respect_order=False, opts=None): """ Retrieve Objects by type E.g. "Image" Returns generator of appropriate :class:`BlitzObjectWrapper` type. @@ -3027,18 +3053,26 @@ def getObjects(self, obj_type, ids=None, params=None, attributes=None, :param ids: object IDs :type ids: List of Long :param params: omero.sys.Parameters, can be used for pagination, - filtering etc. - :param attributes: Map of key-value pairs to filter results by. + & filtering by owner. Takes precedence over opts. + :param attributes: Dict of key-value pairs to filter results by. Key must be attribute of obj_type. E.g. 'name', 'ns' :param respect_order: Returned items will be ordered according to the order of ids + :param opts: Dict of additional options for filtering or + defining extra data to load. + offset, limit and owner for all objects. + Additional opts handled by _getQueryString() + E.g. 'childCount', or filter Dataset by 'project' :return: Generator of :class:`BlitzObjectWrapper` subclasses """ query, params, wrapper = self.buildQuery( - obj_type, ids, params, attributes) - result = self.getQueryService().findAllByQuery( - query, params, self.SERVICE_OPTS) + obj_type, ids, params, attributes, opts) + qs = self.getQueryService() + # we do projection in case query has extra selects (E.g. child_count) + result = qs.projection(query, params, self.SERVICE_OPTS) + # unwrap projected objects + result = [unwrap(r[0]) for r in result] if respect_order and ids is not None: idMap = {} for r in result: @@ -3048,7 +3082,8 @@ def getObjects(self, obj_type, ids=None, params=None, attributes=None, for r in result: yield wrapper(self, r) - def buildQuery(self, obj_type, ids=None, params=None, attributes=None): + def buildQuery(self, obj_type, ids=None, params=None, attributes=None, + opts=None): """ Prepares a query for iQuery. Also prepares params and determines appropriate wrapper for result Returns (query, params, wrapper) which @@ -3060,10 +3095,15 @@ def buildQuery(self, obj_type, ids=None, params=None, attributes=None): :param ids: object IDs :type ids: List of Long :param params: omero.sys.Parameters, can be used for pagination, - filtering etc. - :param attributes: Map of key-value pairs to filter results by. + & filtering by owner. Takes precedence over opts. + :param attributes: Dict of key-value pairs to filter results by. Key must be attribute of obj_type. E.g. 'name', 'ns' + :param opts: Dict of additional options for filtering or + defining extra data to load. + offset, limit and owner for all objects. + Additional opts handled by _getQueryString() + E.g. 'childCount', or filter Dataset by 'project' :return: (query, params, wrapper) """ @@ -3078,38 +3118,65 @@ def buildQuery(self, obj_type, ids=None, params=None, attributes=None): "getObjects uses a string to define obj_type, E.g. " "'Image' not %r" % obj_type) - if params is None: - params = omero.sys.Parameters() - if params.map is None: - params.map = {} + owner = None + order_by = None + offset = None + limit = None # get the base query from the instantiated object itself. E.g "select # obj Project as obj" - query = wrapper()._getQueryString() + query, clauses, baseParams = wrapper()._getQueryString(opts) + + # Handle dict of parameters -> convert to ParametersI() + if opts is not None: + # Parse opts dict to build params + if 'offset' in opts and 'limit' in opts: + limit = opts['limit'] + offset = opts['offset'] + if 'owner' in opts: + owner = rlong(opts['owner']) + if 'order_by' in opts: + order_by = opts['order_by'] + # Handle additional Parameters - need to retrieve owner filter + if params is not None and params.theFilter is not None: + if params.theFilter.ownerId is not None: + owner = params.theFilter.ownerId + # pagination + ofs = params.theFilter.offset + lmt = params.theFilter.limit + if ofs is not None and lmt is not None: + offset = ofs.val + limit = lmt.val + # Other params args will be ignored unless we handle here + + if limit is not None and offset is not None: + baseParams.page(offset, limit) - clauses = [] # getting object by ids if ids is not None: clauses.append("obj.id in (:ids)") - params.map["ids"] = rlist([rlong(a) for a in ids]) + baseParams.map["ids"] = rlist([rlong(a) for a in ids]) # support filtering by owner (not for some object types) - if (params.theFilter and - params.theFilter.ownerId and + if (owner is not None and obj_type.lower() not in ["experimentergroup", "experimenter"]): clauses.append("owner.id = (:eid)") - params.map["eid"] = params.theFilter.ownerId + baseParams.map["eid"] = owner # finding by attributes if attributes is not None: for k, v in attributes.items(): clauses.append('obj.%s=:%s' % (k, k)) - params.map[k] = omero_type(v) + baseParams.map[k] = omero_type(v) if clauses: query += " where " + (" and ".join(clauses)) - return (query, params, wrapper) + # Order by... + if order_by is not None: + query += " order by lower(obj.%s), obj.id" % order_by + + return (query, baseParams, wrapper) def listFileAnnotations(self, eid=None, toInclude=[], toExclude=[]): """ @@ -4513,15 +4580,20 @@ def __eq__(self, a): self.getValue() == a.getValue() and self.getNs() == a.getNs()) - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as getObjects("Annotation") + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from Annotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent") + query = ("select obj from Annotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent") + return query, [], omero.sys.ParametersI() @classmethod def _register(klass, regklass): @@ -4673,15 +4745,20 @@ def __init__(self, *args, **kwargs): _attrs = ('file|OriginalFileWrapper',) - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("FileAnnotation") + getObjects("FileAnnotation"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from FileAnnotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent join fetch obj.file") + query = ("select obj from FileAnnotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent join fetch obj.file") + return query, [], omero.sys.ParametersI() def getValue(self): """ Not implemented """ @@ -4815,15 +4892,20 @@ class TimestampAnnotationWrapper (AnnotationWrapper): OMERO_TYPE = TimestampAnnotationI - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("TimestampAnnotation") + getObjects("TimestampAnnotation"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from TimestampAnnotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent") + query = ("select obj from TimestampAnnotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent") + return query, [], omero.sys.ParametersI() def getValue(self): """ @@ -4864,15 +4946,20 @@ class BooleanAnnotationWrapper (AnnotationWrapper): OMERO_TYPE = BooleanAnnotationI - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("BooleanAnnotation") + getObjects("BooleanAnnotation"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from BooleanAnnotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent") + query = ("select obj from BooleanAnnotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent") + return query, [], omero.sys.ParametersI() def getValue(self): """ @@ -4950,15 +5037,20 @@ def listParents(self, withlinks=True): self._conn, l.parent, l)) return rv - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("TagAnnotation") + getObjects("TagAnnotation"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from TagAnnotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent") + query = ("select obj from TagAnnotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent") + return query, [], omero.sys.ParametersI() def getValue(self): """ @@ -4992,15 +5084,20 @@ class CommentAnnotationWrapper (AnnotationWrapper): OMERO_TYPE = CommentAnnotationI - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("CommentAnnotation") + getObjects("CommentAnnotation"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from CommentAnnotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent") + query = ("select obj from CommentAnnotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent") + return query, [], omero.sys.ParametersI() def getValue(self): """ @@ -5032,15 +5129,20 @@ class LongAnnotationWrapper (AnnotationWrapper): """ OMERO_TYPE = LongAnnotationI - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("LongAnnotation") + getObjects("LongAnnotation"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from LongAnnotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent") + query = ("select obj from LongAnnotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent") + return query, [], omero.sys.ParametersI() def getValue(self): """ @@ -5073,15 +5175,20 @@ class DoubleAnnotationWrapper (AnnotationWrapper): """ OMERO_TYPE = DoubleAnnotationI - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("DoubleAnnotation") + getObjects("DoubleAnnotation"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from DoubleAnnotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent") + query = ("select obj from DoubleAnnotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent") + return query, [], omero.sys.ParametersI() def getValue(self): """ @@ -5115,15 +5222,20 @@ class TermAnnotationWrapper (AnnotationWrapper): """ OMERO_TYPE = TermAnnotationI - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("TermAnnotation") + getObjects("TermAnnotation"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select obj from TermAnnotation obj " - "join fetch obj.details.owner as owner " - "join fetch obj.details.group " - "join fetch obj.details.creationEvent") + query = ("select obj from TermAnnotation obj " + "join fetch obj.details.owner as owner " + "join fetch obj.details.creationEvent") + return query, [], omero.sys.ParametersI() def getValue(self): """ @@ -5234,13 +5346,20 @@ def simpleMarshal(self, xtra=None, parents=False): 'isAdmin': isAdmin, }) return rv - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Returns string for building queries, loading Experimenters only. + + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return ("select distinct obj from Experimenter as obj " - "left outer join fetch obj.groupExperimenterMap as map " - "left outer join fetch map.parent g") + query = ("select distinct obj from Experimenter as obj " + "left outer join fetch obj.groupExperimenterMap as map " + "left outer join fetch map.parent g") + return query, [], omero.sys.ParametersI() def getRawPreferences(self): """ @@ -5462,15 +5581,20 @@ def __bstrap__(self): self.CHILD_WRAPPER_CLASS = 'ExperimenterWrapper' self.PARENT_WRAPPER_CLASS = None - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Returns string for building queries, loading Experimenters for each group. + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ query = ("select distinct obj from ExperimenterGroup as obj " "left outer join fetch obj.groupExperimenterMap as map " "left outer join fetch map.child e") - return query + return query, [], omero.sys.ParametersI() def groupSummary(self, exclude_self=False): """ @@ -5555,6 +5679,23 @@ def __bstrap__(self): self.CHILD_WRAPPER_CLASS = 'ImageWrapper' self.PARENT_WRAPPER_CLASS = 'ProjectWrapper' + def _getQueryString(self, opts=None): + """ + Extend base query to handle filtering of Datasets by Projects. + Returns a tuple of (query, clauses, params). + Supported opts: 'project': to filter by Project + + :param opts: Dictionary of optional parameters. + :return: Tuple of string, list, ParametersI + """ + query, clauses, params = super( + _DatasetWrapper, self)._getQueryString(opts) + if opts is not None and 'project' in opts: + query += ' join obj.projectLinks plink' + clauses.append('plink.parent.id = :pid') + params.add('pid', rlong(opts['project'])) + return (query, clauses, params) + def __loadedHotSwap__(self): """ In addition to loading the Dataset, this method also loads the Images @@ -5807,18 +5948,22 @@ def exportOmeTiff(self): """ return None - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Returns a query string for constructing custom queries, loading the screen for each plate. + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ query = ("select obj from Plate as obj " "join fetch obj.details.owner as owner " - "join fetch obj.details.group " "join fetch obj.details.creationEvent " "left outer join fetch obj.screenLinks spl " "left outer join fetch spl.parent sc") - return query + return query, [], omero.sys.ParametersI() PlateWrapper = _PlateWrapper @@ -6576,15 +6721,21 @@ class _FilesetWrapper (BlitzObjectWrapper): def __bstrap__(self): self.OMERO_CLASS = 'Fileset' - def _getQueryString(self): + def _getQueryString(self, opts=None): """ Used for building queries in generic methods such as - getObjects("Fileset") + getObjects("Fileset"). + Returns a tuple of (query, clauses, params). + + :param opts: Dictionary of optional parameters. + NB: No options supported for this class. + :return: Tuple of string, list, ParametersI """ - return "select obj from Fileset obj "\ + query = "select obj from Fileset obj "\ "left outer join fetch obj.images as image "\ "left outer join fetch obj.usedFiles as usedFile " \ "join fetch usedFile.originalFile" + return query, [], omero.sys.ParametersI() def copyImages(self): """ Returns a list of :class:`ImageWrapper` linked to this Fileset """ diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index f15049756a4..1c3a141fe77 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -303,24 +303,63 @@ def testListProjects(self, gatewaywrapper): # params limit query by owner params = omero.sys.Parameters() params.theFilter = omero.sys.Filter() + conn = gatewaywrapper.gateway # should be no Projects owned by root (in the current group) params.theFilter.ownerId = omero.rtypes.rlong(0) # owned by 'root' - pros = gatewaywrapper.gateway.getObjects("Project", None, params) + pros = conn.getObjects("Project", None, params) + assert len(list(pros)) == 0, "Should be no Projects owned by root" + + # Also filter by owner using opts dict + pros = conn.getObjects("Project", None, opts={'owner': 0}) assert len(list(pros)) == 0, "Should be no Projects owned by root" # filter by current user should get same as above. # owned by 'author' params.theFilter.ownerId = omero.rtypes.rlong( - gatewaywrapper.gateway.getEventContext().userId) - pros = list(gatewaywrapper.gateway.getObjects( + conn.getEventContext().userId) + pros = list(conn.getObjects( "Project", None, params)) - projects = list(gatewaywrapper.gateway.listProjects()) + projects = list(conn.listProjects()) # check unordered lists are the same length & ids assert len(pros) == len(projects) projectIds = [p.getId() for p in projects] for p in pros: assert p.getId() in projectIds + def testPagination(self, gatewaywrapper): + gatewaywrapper.loginAsAuthor() + params = omero.sys.ParametersI() + # Only 3 images available + limit = 2 + params.page(0, limit) + pros = list(gatewaywrapper.gateway.getObjects( + "Project", None, params)) + assert len(pros) == limit + + # Also using opts dict + pros = list(gatewaywrapper.gateway.getObjects( + "Project", None, opts={'offset': 0, 'limit': 2})) + assert len(pros) == limit + + def testGetDatasetsByProject(self, gatewaywrapper): + gatewaywrapper.loginAsAuthor() + allDs = list(gatewaywrapper.gateway.getObjects("Dataset")) + + # Get Datasets by project.listChildren()... + project = gatewaywrapper.getTestProject() + dsIds = [d.id for d in project.listChildren()] + + # Get Datasets, filtering by project + p = {'project': project.id} + datasets = list(gatewaywrapper.gateway.getObjects("Dataset", opts=p)) + + # Check that not all Datasets are in Project (or test is invalid) + assert len(allDs) > len(dsIds) + # Should get same result both methods + assert len(datasets) == len(dsIds) + for d in datasets: + assert d.id in dsIds + def testListExperimentersAndGroups(self, gatewaywrapper): gatewaywrapper.loginAsAuthor() # experimenters @@ -558,30 +597,25 @@ def testListOrphans(self, gatewaywrapper): gatewaywrapper.loginAsUser() eid = gatewaywrapper.gateway.getUserId() - imageList = list() + # Create 5 images for i in range(0, 5): - imageList.append(gatewaywrapper.createTestImage( - imageName=(str(uuid.uuid1()))).getName()) - - findImages = list(gatewaywrapper.gateway.listOrphans("Image")) - assert len(findImages) == 5, "Did not find orphaned images" - - for p in findImages: - assert not p._obj.pixelsLoaded - assert p.getName() in imageList, \ - "All images should have queried name" + gatewaywrapper.createTestImage(imageName=str(uuid.uuid1())) + # Pagination, loading pixels params = omero.sys.ParametersI() params.page(1, 3) findImagesInPage = list(gatewaywrapper.gateway.listOrphans( "Image", eid=eid, params=params, loadPixels=True)) assert len(findImagesInPage) == 3, \ "Did not find orphaned images in page" - for p in findImagesInPage: assert p._obj.pixelsLoaded + # All orphans, no pixels + findImages = list(gatewaywrapper.gateway.listOrphans("Image")) + orphanedCount = len(findImages) for p in findImages: + assert not p._obj.pixelsLoaded client = p._conn handle = client.deleteObjects( 'Image', [p.getId()], deleteAnns=True) @@ -590,6 +624,15 @@ def testListOrphans(self, gatewaywrapper): finally: handle.close() + # Check this AFTER delete + # If test fails with previously undeleted images, + # it should pass when re-run since images are deleted above + assert orphanedCount == 5, "Did not find orphaned images" + + # Simply check this doesn't fail See https://github.com/ + # openmicroscopy/openmicroscopy/pull/4950#issuecomment-264142956 + list(gatewaywrapper.gateway.listOrphans("Dataset")) + def testOrderById(self, gatewaywrapper): gatewaywrapper.loginAsUser() imageIds = list() diff --git a/components/tools/OmeroPy/test/unit/gatewaytest/test_build_query.py b/components/tools/OmeroPy/test/unit/gatewaytest/test_build_query.py new file mode 100644 index 00000000000..29e7aee6bf5 --- /dev/null +++ b/components/tools/OmeroPy/test/unit/gatewaytest/test_build_query.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# +# Copyright (C) 2016 University of Dundee & Open Microscopy Environment. +# All rights reserved. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Gateway tests - buildQuery() as used by conn.getObjects().""" + +from omero.gateway import _BlitzGateway, BlitzObjectWrapper, KNOWN_WRAPPERS +from omero.sys import Parameters, ParametersI, Filter +import pytest +from omero.rtypes import wrap + + +@pytest.fixture(scope='function') +def gateway(): + """Create a BlitzGateway object.""" + return _BlitzGateway() + + +class TestBuildQuery(object): + """Test the conn.buildQuery() method for all Object Wrappers.""" + + @pytest.mark.parametrize("dtype", KNOWN_WRAPPERS.keys()) + def test_no_clauses(self, gateway, dtype): + """Expect a query with no 'where' clauses.""" + result = gateway.buildQuery(dtype) + query, params, wrapper = result + assert isinstance(query, str) + assert isinstance(params, Parameters) + assert isinstance(wrapper(), BlitzObjectWrapper) + assert query.startswith("select ") + assert "where" not in query + + @pytest.mark.parametrize("dtype", KNOWN_WRAPPERS.keys()) + def test_filter_by_owner(self, gateway, dtype): + """Query should filter by owner.""" + p = ParametersI() + p.theFilter = Filter() + p.theFilter.ownerId = wrap(2) + # Test using 'params' argument + with_params = gateway.buildQuery(dtype, params=p) + # Test using 'opts' dictionary + with_opts = gateway.buildQuery(dtype, opts={'owner': 1}) + for result in [with_params, with_opts]: + query, params, wrapper = result + assert isinstance(query, str) + assert isinstance(params, Parameters) + assert isinstance(wrapper(), BlitzObjectWrapper) + if dtype not in ('experimenter', 'experimentergroup'): + assert "where owner" in query + else: + assert "where owner" not in query + + @pytest.mark.parametrize("dtype", KNOWN_WRAPPERS.keys()) + def test_pagination(self, gateway, dtype): + """Query should paginate.""" + offset = 1 + limit = 100 + p = ParametersI() + p.page(offset, limit) + # Test using 'params' argument + with_params = gateway.buildQuery(dtype, params=p) + # Test using 'opts' dictionary + opts = {'offset': offset, 'limit': limit} + with_opts = gateway.buildQuery(dtype, opts=opts) + for result in [with_params, with_opts]: + query, params, wrapper = result + assert isinstance(query, str) + assert isinstance(params, Parameters) + assert isinstance(wrapper(), BlitzObjectWrapper) + assert params.theFilter.offset.val == offset + assert params.theFilter.limit.val == limit