From e3eba8c5fafc8649fc331203e91d08f8f263e562 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 27 Jan 2017 16:06:14 +0000 Subject: [PATCH 01/28] Use ?offset=200 parameter instead of page=2 --- components/tools/OmeroWeb/omeroweb/api/views.py | 4 ++-- .../tools/OmeroWeb/test/integration/test_api_containers.py | 2 +- .../tools/OmeroWeb/test/integration/test_api_images.py | 2 +- .../tools/OmeroWeb/test/integration/test_api_projects.py | 7 +++---- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 8d434f4e8b6..359e0bf3e51 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -251,7 +251,7 @@ class ObjectsView(ApiView): def get_opts(self, request, **kwargs): """Return an options dict based on request parameters.""" try: - page = getIntOrDefault(request, 'page', 1) + offset = getIntOrDefault(request, 'offset', 0) limit = getIntOrDefault(request, 'limit', settings.PAGE) owner = getIntOrDefault(request, 'owner', None) child_count = request.GET.get('childCount', False) == 'true' @@ -260,7 +260,7 @@ def get_opts(self, request, **kwargs): raise BadRequestError(str(ex)) # orphaned and child_count not used by every subclass - opts = {'offset': (page - 1) * limit, + opts = {'offset': offset, 'limit': limit, 'owner': owner, 'orphaned': orphaned, diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index eb4a19d73a9..57ca9e60c18 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -269,7 +269,7 @@ def test_datasets_plates(self, user1, dtype, project_datasets, payload = {ptype: parent.id.val, 'limit': limit} rsp = _get_response_json(django_client, request_url, payload) assert_objects(conn, rsp['data'], children[0:limit], dtype=dtype) - payload['page'] = 2 + payload['offset'] = limit # page 2 rsp = _get_response_json(django_client, request_url, payload) assert_objects(conn, rsp['data'], children[limit:limit * 2], dtype=dtype) diff --git a/components/tools/OmeroWeb/test/integration/test_api_images.py b/components/tools/OmeroWeb/test/integration/test_api_images.py index 7c1884eb814..1f322c318d7 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_images.py +++ b/components/tools/OmeroWeb/test/integration/test_api_images.py @@ -171,7 +171,7 @@ def test_dataset_images(self, user1, dataset_images): rsp = _get_response_json(django_client, dataset_images_url, payload) assert_objects(conn, rsp['data'], images[0:limit], dtype='Image', opts={'load_pixels': True}) - payload['page'] = 2 + payload['offset'] = limit # page 2 rsp = _get_response_json(django_client, images_url, payload) assert_objects(conn, rsp['data'], images[limit:limit * 2], dtype='Image', opts={'load_pixels': True}) diff --git a/components/tools/OmeroWeb/test/integration/test_api_projects.py b/components/tools/OmeroWeb/test/integration/test_api_projects.py index 83a867dfc51..2448300e9cf 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_projects.py +++ b/components/tools/OmeroWeb/test/integration/test_api_projects.py @@ -395,17 +395,16 @@ def test_marshal_projects_pagination(self, user1, user2, version = settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) - # First page, just 2 projects. Page = 1 by default + # First page, just 2 projects. offset = 0 by default limit = 2 rsp = _get_response_json(django_client, request_url, {'limit': limit}) assert len(rsp['data']) == limit assert_objects(conn, rsp['data'], projects[0:limit]) # Check that page 2 gives next 2 projects - page = 2 - payload = {'limit': limit, 'page': page} + payload = {'limit': limit, 'offset': limit} rsp = _get_response_json(django_client, request_url, payload) - assert_objects(conn, rsp['data'], projects[limit:limit * page]) + assert_objects(conn, rsp['data'], projects[limit:limit * 2]) def test_marshal_projects_params(self, user1, user2, projects_user1_group1, From 4778aa8b656c0c81c764ea6c79222933ed09006c Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 30 Jan 2017 14:57:31 +0000 Subject: [PATCH 02/28] Blitz conn.buildCountQuery() based on conn.buildQuery() --- .../OmeroPy/src/omero/gateway/__init__.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index a6fe2213217..1d018d94fc5 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -3177,6 +3177,38 @@ def buildQuery(self, obj_type, ids=None, params=None, attributes=None, return (query, baseParams, wrapper) + def buildCountQuery(self, obj_type, opts=None): + """ + Prepares a 'projection' query to count objects. + + Based on buildQuery(), we modify the query to only return a count. + Modified query does not 'fetch' any data or add any other + unnecessary objects to query. + We return just the query and omero.sys.ParametersI for the query. + + :param obj_type: Object type. E.g. "Project" see above + :param opts: Dict of options for filtering by + offset, limit and owner for all objects. + Additional opts handled by _getQueryString() + E.g. filter Dataset by 'project' + :return: (query, params) + """ + # We disable pagination & loading of child_counts + opts_copy = opts.copy() + if 'limit' in opts_copy: + del opts_copy['limit'] + opts_copy['child_count'] = False + + # Get query with other options + query, params, wrapper = self.buildQuery(obj_type, opts=opts_copy) + + # Modify query to only select count() + query = query.replace("select obj ", "select count(obj) ") + query = query.replace("fetch", "") + query = query.split("order by")[0] + return query, params + + def listFileAnnotations(self, eid=None, toInclude=[], toExclude=[]): """ Lists FileAnnotations created by users, filtering by namespaces if From 6bbdfc6a088803e383111550f76282016f055e2b Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 30 Jan 2017 15:16:12 +0000 Subject: [PATCH 03/28] api_query.py uses conn.buildCountQuery() to add 'totalCount' to 'meta':{} --- .../tools/OmeroWeb/omeroweb/api/api_query.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_query.py b/components/tools/OmeroWeb/omeroweb/api/api_query.py index db15cc70147..fc0250fb303 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_query.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_query.py @@ -69,4 +69,17 @@ def query_objects(conn, object_type, for obj in result: objects.append(obj) - return marshal_objects(objects, extras=extras, normalize=normalize) + # Query the count() of objects & add to 'meta' dict + count_query, params = conn.buildCountQuery(object_type, opts=opts) + result = qs.projection(count_query, params, ctx) + + meta = {} + if 'offset' in opts: + meta['offset'] = opts['offset'] + if 'limit' in opts: + meta['limit'] = opts['limit'] + meta['totalCount'] = result[0][0].val + + marshalled = marshal_objects(objects, extras=extras, normalize=normalize) + marshalled['meta'] = meta + return marshalled From 0e862fffee611872d92747dbac9857637750658e Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 31 Jan 2017 15:54:44 +0000 Subject: [PATCH 04/28] Test 'meta' dict for listing Datasets & Plates --- .../OmeroWeb/test/integration/test_api_containers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 57ca9e60c18..c43dcac5d75 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -263,16 +263,25 @@ def test_datasets_plates(self, user1, dtype, project_datasets, assert len(rsp['data']) == 5 assert_objects(conn, rsp['data'], children, dtype=dtype, extra=child_counts) + assert rsp['meta'] == {'totalCount': 5, + 'limit': settings.PAGE, + 'offset': 0} # Pagination limit = 3 payload = {ptype: parent.id.val, 'limit': limit} rsp = _get_response_json(django_client, request_url, payload) assert_objects(conn, rsp['data'], children[0:limit], dtype=dtype) + assert rsp['meta'] == {'totalCount': 5, + 'limit': limit, + 'offset': 0} payload['offset'] = limit # page 2 rsp = _get_response_json(django_client, request_url, payload) assert_objects(conn, rsp['data'], children[limit:limit * 2], dtype=dtype) + assert rsp['meta'] == {'totalCount': 5, + 'limit': limit, + 'offset': limit} def test_screens(self, user1, user_screens): """Test listing of Screens.""" From bed8a339c7f75f083c36903fd498fe524e5b916f Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 31 Jan 2017 16:01:40 +0000 Subject: [PATCH 05/28] Test 'meta' dict for listing Images, orphans --- .../test/integration/test_api_containers.py | 6 ++++++ .../OmeroWeb/test/integration/test_api_images.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index c43dcac5d75..efba6b8614a 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -249,11 +249,17 @@ def test_datasets_plates(self, user1, dtype, project_datasets, # List ALL Datasets or Plates rsp = _get_response_json(django_client, request_url, {}) assert len(rsp['data']) == 6 + assert rsp['meta'] == {'totalCount': 6, + 'limit': settings.PAGE, + 'offset': 0} # Filter Datasets or Plates by Orphaned payload = {'orphaned': 'true'} rsp = _get_response_json(django_client, request_url, payload) assert_objects(conn, rsp['data'], [orphaned], dtype=dtype) + assert rsp['meta'] == {'totalCount': 1, + 'limit': settings.PAGE, + 'offset': 0} # Filter Datasets by Project or Plates by Screen children.sort(cmp_name_insensitive) diff --git a/components/tools/OmeroWeb/test/integration/test_api_images.py b/components/tools/OmeroWeb/test/integration/test_api_images.py index 1f322c318d7..3bfaf651721 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_images.py +++ b/components/tools/OmeroWeb/test/integration/test_api_images.py @@ -147,12 +147,18 @@ def test_dataset_images(self, user1, dataset_images): # List ALL Images rsp = _get_response_json(django_client, images_url, {}) assert len(rsp['data']) == 6 + assert rsp['meta'] == {'totalCount': 6, + 'limit': settings.PAGE, + 'offset': 0} # Filter Images by Orphaned payload = {'orphaned': 'true'} rsp = _get_response_json(django_client, images_url, payload) assert_objects(conn, rsp['data'], [orphaned], dtype='Image', opts={'load_pixels': True}) + assert rsp['meta'] == {'totalCount': 1, + 'limit': settings.PAGE, + 'offset': 0} # Filter Images by Dataset images.sort(cmp_name_insensitive) @@ -163,6 +169,9 @@ def test_dataset_images(self, user1, dataset_images): assert 'Channels' not in rsp['data'][0]['Pixels'] assert_objects(conn, rsp['data'], images, dtype='Image', opts={'load_pixels': True}) + assert rsp['meta'] == {'totalCount': 5, + 'limit': settings.PAGE, + 'offset': 0} # Pagination, listing images via /datasets/:id/images/ limit = 3 @@ -171,10 +180,16 @@ def test_dataset_images(self, user1, dataset_images): rsp = _get_response_json(django_client, dataset_images_url, payload) assert_objects(conn, rsp['data'], images[0:limit], dtype='Image', opts={'load_pixels': True}) + assert rsp['meta'] == {'totalCount': 5, + 'limit': limit, + 'offset': 0} payload['offset'] = limit # page 2 rsp = _get_response_json(django_client, images_url, payload) assert_objects(conn, rsp['data'], images[limit:limit * 2], dtype='Image', opts={'load_pixels': True}) + assert rsp['meta'] == {'totalCount': 5, + 'limit': limit, + 'offset': limit} # Show ONLY the orphaned image (channels are loaded by default) img_url = images_url + '%s/' % orphaned.id.val From f99f34a7d77a9756354024a61d7a8fd31ef409cc Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 12:11:49 +0000 Subject: [PATCH 06/28] Added omero.web.max_page_size 500 to settings.py --- components/tools/OmeroWeb/omeroweb/settings.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/settings.py b/components/tools/OmeroWeb/omeroweb/settings.py index 497a838a693..2f9be19c9ed 100644 --- a/components/tools/OmeroWeb/omeroweb/settings.py +++ b/components/tools/OmeroWeb/omeroweb/settings.py @@ -639,7 +639,12 @@ def leave_none_unset_int(s): 200, int, ("Number of images displayed within a dataset or 'orphaned'" - " container to prevent from loading them all at once.")], + " container or default number of items returned from json api.")], + "omero.web.max_page_size": + ["MAX_PAGE", + 500, + int, + ("Maximum number of items returned from json api.")], "omero.web.ui.top_links": ["TOP_LINKS", ('[' From c558aeddfc0661dc5892527083882ba9ef006e2a Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 12:12:29 +0000 Subject: [PATCH 07/28] Limit page size to MAX_PAGE --- components/tools/OmeroWeb/omeroweb/api/api_query.py | 2 ++ components/tools/OmeroWeb/omeroweb/api/views.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_query.py b/components/tools/OmeroWeb/omeroweb/api/api_query.py index fc0250fb303..b69ef4a50b0 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_query.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_query.py @@ -24,6 +24,7 @@ from api_marshal import marshal_objects from copy import deepcopy +from django.conf import settings def query_objects(conn, object_type, @@ -78,6 +79,7 @@ def query_objects(conn, object_type, meta['offset'] = opts['offset'] if 'limit' in opts: meta['limit'] = opts['limit'] + meta['maxLimit'] = settings.MAX_PAGE meta['totalCount'] = result[0][0].val marshalled = marshal_objects(objects, extras=extras, normalize=normalize) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 359e0bf3e51..bd0d4a8b787 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -261,7 +261,7 @@ def get_opts(self, request, **kwargs): # orphaned and child_count not used by every subclass opts = {'offset': offset, - 'limit': limit, + 'limit': min(limit, settings.MAX_PAGE), 'owner': owner, 'orphaned': orphaned, 'child_count': child_count, From 3e9b8eea54f894dcc2c891d39ad46b20d9310041 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 15:02:47 +0000 Subject: [PATCH 08/28] flake8 fix --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 1d018d94fc5..38f0d7c3f77 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -3208,7 +3208,6 @@ def buildCountQuery(self, obj_type, opts=None): query = query.split("order by")[0] return query, params - def listFileAnnotations(self, eid=None, toInclude=[], toExclude=[]): """ Lists FileAnnotations created by users, filtering by namespaces if From cfa25305feaab0f679bada3d9e359b5a3e34db7a Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 8 Feb 2017 15:27:02 +0000 Subject: [PATCH 09/28] Moving api settings to api_settings.py --- .../tools/OmeroWeb/omeroweb/api/api_query.py | 4 +- .../OmeroWeb/omeroweb/api/api_settings.py | 57 +++++++++++++++++++ .../tools/OmeroWeb/omeroweb/api/urls.py | 4 +- .../tools/OmeroWeb/omeroweb/api/views.py | 12 ++-- .../tools/OmeroWeb/omeroweb/settings.py | 23 +------- 5 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 components/tools/OmeroWeb/omeroweb/api/api_settings.py diff --git a/components/tools/OmeroWeb/omeroweb/api/api_query.py b/components/tools/OmeroWeb/omeroweb/api/api_query.py index b69ef4a50b0..322570fd920 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_query.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_query.py @@ -24,7 +24,7 @@ from api_marshal import marshal_objects from copy import deepcopy -from django.conf import settings +from . import api_settings def query_objects(conn, object_type, @@ -79,7 +79,7 @@ def query_objects(conn, object_type, meta['offset'] = opts['offset'] if 'limit' in opts: meta['limit'] = opts['limit'] - meta['maxLimit'] = settings.MAX_PAGE + meta['maxLimit'] = api_settings.API_MAX_LIMIT meta['totalCount'] = result[0][0].val marshalled = marshal_objects(objects, extras=extras, normalize=normalize) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_settings.py b/components/tools/OmeroWeb/omeroweb/api/api_settings.py new file mode 100644 index 00000000000..b15e381105d --- /dev/null +++ b/components/tools/OmeroWeb/omeroweb/api/api_settings.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (C) 2017 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 Affero General Public License as +# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +"""Settings for the OMERO JSON api app.""" + +import sys +from omeroweb.settings import process_custom_settings, report_settings, \ + str_slash + +# load settings +API_SETTINGS_MAPPING = { + + "omero.web.api.limit": + ["API_LIMIT", + 200, + int, + "Default number of items returned from json api."], + "omero.web.api.max_limit": + ["API_MAX_LIMIT", + 500, + int, + "Maximum number of items returned from json api."], + "omero.web.api.absolute_url": + ["API_ABSOLUTE_URL", + None, + str_slash, + ("URL to use for generating urls within API json responses. " + "By default this is None, and we use Django's " + "request.build_absolute_uri() to generate absolute urls " + "based on each request. If set to a string or empty string, " + "this will be used as prefix to relative urls.")], +} + +process_custom_settings(sys.modules[__name__], 'API_SETTINGS_MAPPING') +report_settings(sys.modules[__name__]) + +# For any given release of api, we may support +# one or more versions of the api. +# E.g. /api/v1.0/ +# TODO - need to decide how this is configured, strategy for extending etc. +API_VERSIONS = ('0.1',) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index f2a77063fd3..311d2058029 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -22,11 +22,11 @@ from django.conf.urls import url, patterns from omeroweb.api import views from omeroweb.webgateway.views import LoginView -from django.conf import settings +from . import api_settings import re versions = '|'.join([re.escape(v) - for v in settings.API_VERSIONS]) + for v in api_settings.API_VERSIONS]) api_versions = url(r'^$', views.api_versions, name='api_versions') diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index bd0d4a8b787..e6c6a24ae74 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -23,7 +23,7 @@ from django.middleware import csrf from django.utils.decorators import method_decorator from django.core.urlresolvers import reverse -from django.conf import settings +from . import api_settings import traceback import json @@ -52,11 +52,11 @@ def build_url(request, name, api_version, **kwargs): """ kwargs['api_version'] = api_version url = reverse(name, kwargs=kwargs) - if settings.API_ABSOLUTE_URL is None: + if api_settings.API_ABSOLUTE_URL is None: return request.build_absolute_uri(url) else: # remove trailing slash - prefix = settings.API_ABSOLUTE_URL.rstrip('/') + prefix = api_settings.API_ABSOLUTE_URL.rstrip('/') return "%s%s" % (prefix, url) @@ -64,7 +64,7 @@ def build_url(request, name, api_version, **kwargs): def api_versions(request, **kwargs): """Base url of the webgateway json api.""" versions = [] - for v in settings.API_VERSIONS: + for v in api_settings.API_VERSIONS: versions.append({ 'version': v, 'url:base': build_url(request, 'api_base', v) @@ -252,7 +252,7 @@ def get_opts(self, request, **kwargs): """Return an options dict based on request parameters.""" try: offset = getIntOrDefault(request, 'offset', 0) - limit = getIntOrDefault(request, 'limit', settings.PAGE) + limit = getIntOrDefault(request, 'limit', api_settings.API_LIMIT) owner = getIntOrDefault(request, 'owner', None) child_count = request.GET.get('childCount', False) == 'true' orphaned = request.GET.get('orphaned', False) == 'true' @@ -261,7 +261,7 @@ def get_opts(self, request, **kwargs): # orphaned and child_count not used by every subclass opts = {'offset': offset, - 'limit': min(limit, settings.MAX_PAGE), + 'limit': min(limit, api_settings.API_MAX_LIMIT), 'owner': owner, 'orphaned': orphaned, 'child_count': child_count, diff --git a/components/tools/OmeroWeb/omeroweb/settings.py b/components/tools/OmeroWeb/omeroweb/settings.py index 2f9be19c9ed..50ad5e035f1 100644 --- a/components/tools/OmeroWeb/omeroweb/settings.py +++ b/components/tools/OmeroWeb/omeroweb/settings.py @@ -472,15 +472,6 @@ def leave_none_unset_int(s): ("Workers silent for more than this many seconds are killed " "and restarted. Check Gunicorn Documentation " "http://docs.gunicorn.org/en/stable/settings.html#timeout")], - "omero.web.api.absolute_url": - ["API_ABSOLUTE_URL", - None, - str_slash, - ("URL to use for generating urls within API json responses. " - "By default this is None, and we use Django's " - "request.build_absolute_uri() to generate absolute urls " - "based on each request. If set to a string or empty string, " - "this will be used as prefix to relative urls.")], # Public user "omero.web.public.enabled": @@ -639,12 +630,7 @@ def leave_none_unset_int(s): 200, int, ("Number of images displayed within a dataset or 'orphaned'" - " container or default number of items returned from json api.")], - "omero.web.max_page_size": - ["MAX_PAGE", - 500, - int, - ("Maximum number of items returned from json api.")], + " container to prevent from loading them all at once.")], "omero.web.ui.top_links": ["TOP_LINKS", ('[' @@ -1100,6 +1086,7 @@ def report_settings(module): 'omeroweb.webclient', 'omeroweb.webgateway', 'omeroweb.webredirect', + 'omeroweb.api', 'pipeline', ) @@ -1180,12 +1167,6 @@ def report_settings(module): # FEEDBACK_APP: 6 = OMERO.web FEEDBACK_APP = 6 -# For any given release of api, we may support -# one or more versions of the api. -# E.g. /api/v1.0/ -# TODO - need to decide how this is configured, strategy for extending etc. -API_VERSIONS = ('0.1',) - # IGNORABLE_404_STARTS: # Default: ('/cgi-bin/', '/_vti_bin', '/_vti_inf') # IGNORABLE_404_ENDS: From 7fbb04d2dd5b7b7e4f810fffd2d683d473b4a9a3 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 17 Feb 2017 12:48:18 +0000 Subject: [PATCH 10/28] Fix merge conflict --- components/tools/OmeroWeb/test/integration/test_api_images.py | 1 - 1 file changed, 1 deletion(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_images.py b/components/tools/OmeroWeb/test/integration/test_api_images.py index d1db0484861..9389be5d105 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_images.py +++ b/components/tools/OmeroWeb/test/integration/test_api_images.py @@ -24,7 +24,6 @@ from django.core.urlresolvers import reverse from django.conf import settings import pytest -from omero_marshal import get_encoder from test_api_projects import cmp_name_insensitive, get_update_service, \ get_connection, marshal_objects from omero.model import DatasetI, ImageI From 5eb47b9a147ff0111b2d5799ecfd2fb3e520673a Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 17 Feb 2017 12:56:23 +0000 Subject: [PATCH 11/28] Don't need to remove opts['child_count'] in buildCountQuery() --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 6192966599d..fa10f9f12d5 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -3197,11 +3197,10 @@ def buildCountQuery(self, obj_type, opts=None): E.g. filter Dataset by 'project' :return: (query, params) """ - # We disable pagination & loading of child_counts + # We disable pagination since we want to count ALL results opts_copy = opts.copy() if 'limit' in opts_copy: del opts_copy['limit'] - opts_copy['child_count'] = False # Get query with other options query, params, wrapper = self.buildQuery(obj_type, opts=opts_copy) From 1057dceb7efa6329bcbe181cc39cdbbc6aa4d409 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 17 Feb 2017 13:04:56 +0000 Subject: [PATCH 12/28] Fix test_api_images.py to use api_settings and MAX_LIMIT --- .../test/integration/test_api_images.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_images.py b/components/tools/OmeroWeb/test/integration/test_api_images.py index 9389be5d105..1d088ff4bb1 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_images.py +++ b/components/tools/OmeroWeb/test/integration/test_api_images.py @@ -22,13 +22,14 @@ from omeroweb.testlib import IWebTest, _get_response_json, \ _csrf_post_json, _csrf_put_json, _csrf_delete_response_json from django.core.urlresolvers import reverse -from django.conf import settings +from omeroweb.api import api_settings import pytest from test_api_projects import cmp_name_insensitive, get_update_service, \ get_connection, marshal_objects from omero.model import DatasetI, ImageI from omero.rtypes import rstring import json +from omero_marshal import OME_SCHEMA_URL def get_query_service(user): @@ -106,7 +107,7 @@ def test_dataset_images(self, user1, dataset_images): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] dataset = dataset_images[0] images = dataset.linkedImageList() @@ -119,7 +120,8 @@ def test_dataset_images(self, user1, dataset_images): rsp = _get_response_json(django_client, images_url, {}) assert len(rsp['data']) == 6 assert rsp['meta'] == {'totalCount': 6, - 'limit': settings.PAGE, + 'limit': api_settings.API_LIMIT, + 'maxLimit': api_settings.API_MAX_LIMIT, 'offset': 0} # Filter Images by Orphaned @@ -128,7 +130,8 @@ def test_dataset_images(self, user1, dataset_images): assert_objects(conn, rsp['data'], [orphaned], dtype='Image', opts={'load_pixels': True}) assert rsp['meta'] == {'totalCount': 1, - 'limit': settings.PAGE, + 'limit': api_settings.API_LIMIT, + 'maxLimit': api_settings.API_MAX_LIMIT, 'offset': 0} # Filter Images by Dataset @@ -141,7 +144,8 @@ def test_dataset_images(self, user1, dataset_images): assert_objects(conn, rsp['data'], images, dtype='Image', opts={'load_pixels': True}) assert rsp['meta'] == {'totalCount': 5, - 'limit': settings.PAGE, + 'limit': api_settings.API_LIMIT, + 'maxLimit': api_settings.API_MAX_LIMIT, 'offset': 0} # Pagination, listing images via /datasets/:id/images/ @@ -153,6 +157,7 @@ def test_dataset_images(self, user1, dataset_images): opts={'load_pixels': True}) assert rsp['meta'] == {'totalCount': 5, 'limit': limit, + 'maxLimit': api_settings.API_MAX_LIMIT, 'offset': 0} payload['offset'] = limit # page 2 rsp = _get_response_json(django_client, images_url, payload) @@ -160,6 +165,7 @@ def test_dataset_images(self, user1, dataset_images): dtype='Image', opts={'load_pixels': True}) assert rsp['meta'] == {'totalCount': 5, 'limit': limit, + 'maxLimit': api_settings.API_MAX_LIMIT, 'offset': limit} # Show ONLY the orphaned image (channels are loaded by default) @@ -176,7 +182,7 @@ def test_image_create_update_delete(self, user1): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) payload = {'Name': 'Image test', '@type': OME_SCHEMA_URL + '#Image'} From 88ee449729f54b34373992754843476dc02c1ce2 Mon Sep 17 00:00:00 2001 From: William Moore Date: Sun, 19 Feb 2017 22:52:27 +0000 Subject: [PATCH 13/28] Fix API_VERSIONS to '0' from merge conflict --- components/tools/OmeroWeb/omeroweb/api/api_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_settings.py b/components/tools/OmeroWeb/omeroweb/api/api_settings.py index b15e381105d..0515a0ebcbf 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_settings.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_settings.py @@ -54,4 +54,4 @@ # one or more versions of the api. # E.g. /api/v1.0/ # TODO - need to decide how this is configured, strategy for extending etc. -API_VERSIONS = ('0.1',) +API_VERSIONS = ('0',) From 418eb3b9effb0a6c080a72e41afb6fa9c94a0fe2 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 20 Feb 2017 23:13:37 +0000 Subject: [PATCH 14/28] flake8 fixes from merge conflict --- .../tools/OmeroWeb/test/integration/test_api_containers.py | 4 ++-- components/tools/OmeroWeb/test/integration/test_api_images.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 3fbf98c2c82..4b5c6fef5d1 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -188,7 +188,7 @@ def test_create_update_unsupported(self, user1, dtype, method): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) payload = {'Name': 'test', '@type': OME_SCHEMA_URL + '#%s' % dtype} @@ -203,7 +203,7 @@ def test_delete_unsupported(self, user1, dtype): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] # Delete (fake url - image doesn't need to exist for test) url_name = 'api_%s' % dtype.lower() delete_url = reverse(url_name, kwargs={'api_version': version, diff --git a/components/tools/OmeroWeb/test/integration/test_api_images.py b/components/tools/OmeroWeb/test/integration/test_api_images.py index f1291a35d6e..52886269157 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_images.py +++ b/components/tools/OmeroWeb/test/integration/test_api_images.py @@ -28,7 +28,6 @@ from omero.model import DatasetI, ImageI from omero.rtypes import rstring import json -from omero_marshal import OME_SCHEMA_URL def get_query_service(user): From faddc5d730dbd28d80cdc9e81ca99c20e3734ae4 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 20 Feb 2017 23:20:24 +0000 Subject: [PATCH 15/28] Tiny fix to avoid merge conflict --- components/tools/OmeroWeb/omeroweb/api/api_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_query.py b/components/tools/OmeroWeb/omeroweb/api/api_query.py index 90c43af6a3a..f681b5b0165 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_query.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_query.py @@ -22,10 +22,10 @@ from omero.rtypes import unwrap, wrap from omero.sys import ParametersI +from . import api_settings from api_marshal import marshal_objects from copy import deepcopy -from . import api_settings def get_child_counts(conn, link_class, parent_ids): From 400ae402ffeb6f904d34fbe8843a4e7e893e43db Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 21 Feb 2017 11:41:59 +0000 Subject: [PATCH 16/28] Fix 'settings' -> 'api_settings' in tests --- .../test/integration/test_api_errors.py | 14 +++++----- .../test/integration/test_api_login.py | 16 +++++------ .../test/integration/test_api_projects.py | 28 +++++++++---------- .../test/integration/test_api_wells.py | 6 ++-- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_errors.py b/components/tools/OmeroWeb/test/integration/test_api_errors.py index d48d1680c5a..3bab8ed0267 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_errors.py +++ b/components/tools/OmeroWeb/test/integration/test_api_errors.py @@ -21,7 +21,7 @@ from omeroweb.testlib import IWebTest, _csrf_post_json, _csrf_put_json from django.core.urlresolvers import reverse -from django.conf import settings +from omeroweb.api import api_settings import pytest from test_api_projects import get_connection from omero.model import ProjectI, TagAnnotationI @@ -56,7 +56,7 @@ def user_A(self, group_A, group_B): def test_save_post_no_id(self): """If POST to /save/ data shouldn't contain @id.""" django_client = self.django_root_client - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) payload = {'Name': 'test_save_post_no_id', '@type': '%s#Project' % OME_SCHEMA_URL, @@ -69,7 +69,7 @@ def test_save_post_no_id(self): def test_save_put_id(self): """If PUT to /save/ to update, data must contain @id.""" django_client = self.django_root_client - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) payload = {'Name': 'test_save_put_id', '@type': '%s#Project' % OME_SCHEMA_URL} @@ -81,7 +81,7 @@ def test_save_put_id(self): def test_marshal_type(self): """If no decoder found for @type, get suitable message.""" django_client = self.django_root_client - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) objType = 'SomeInvalidSchema#Project' payload = {'Name': 'test_marshal_type', @@ -94,7 +94,7 @@ def test_marshal_type(self): def test_marshal_validation(self): """Test that we get expected error with invalid @type in json.""" django_client = self.django_root_client - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) payload = {'Name': 'test_marshal_validation', '@type': OME_SCHEMA_URL + '#Project', @@ -112,7 +112,7 @@ def test_security_violation(self, group_B, user_A): group_A_id = conn.getEventContext().groupId userName = conn.getUser().getName() django_client = self.new_django_client(userName, userName) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] group_B_id = group_B.id.val save_url = reverse('api_save', kwargs={'api_version': version}) # Create project in group_A (default group) @@ -139,7 +139,7 @@ def test_validation_exception(self, user_A): group = conn.getEventContext().groupId userName = conn.getUser().getName() django_client = self.new_django_client(userName, userName) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) save_url += '?group=' + str(group) diff --git a/components/tools/OmeroWeb/test/integration/test_api_login.py b/components/tools/OmeroWeb/test/integration/test_api_login.py index 2c316c9e4cd..b161e8cad58 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_login.py +++ b/components/tools/OmeroWeb/test/integration/test_api_login.py @@ -25,7 +25,7 @@ from omeroweb.testlib import IWebTest, _get_response_json, \ _post_response_json, _csrf_post_response_json from django.core.urlresolvers import reverse, NoReverseMatch -from django.conf import settings +from omeroweb.api import api_settings from django.test import Client from omero_marshal import OME_SCHEMA_URL import json @@ -44,9 +44,9 @@ def test_versions(self): request_url = reverse('api_versions') rsp = _get_response_json(django_client, request_url, {}) versions = rsp['data'] - assert len(versions) == len(settings.API_VERSIONS) + assert len(versions) == len(api_settings.API_VERSIONS) for v in versions: - assert v['version'] in settings.API_VERSIONS + assert v['version'] in api_settings.API_VERSIONS def test_base_url(self): """ @@ -54,7 +54,7 @@ def test_base_url(self): """ django_client = self.django_root_client # test the most recent version - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_base', kwargs={'api_version': version}) rsp = _get_response_json(django_client, request_url, {}) assert 'url:servers' in rsp @@ -71,7 +71,7 @@ def test_base_url_versions_404(self): """ Tests that the base url gives 404 for invalid versions """ - version = settings.API_VERSIONS[-1] + "1" + version = api_settings.API_VERSIONS[-1] + "1" with pytest.raises(NoReverseMatch): reverse('api_base', kwargs={'api_version': version}) @@ -80,7 +80,7 @@ def test_login_get(self): Tests that we get a suitable message if we try to GET login_url """ django_client = self.django_root_client - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_login', kwargs={'api_version': version}) rsp = _get_response_json(django_client, request_url, {}, status_code=405) @@ -93,7 +93,7 @@ def test_login_csrf(self): """ django_client = self.django_root_client # test the most recent version - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_login', kwargs={'api_version': version}) rsp = _post_response_json(django_client, request_url, {}, status_code=403) @@ -123,7 +123,7 @@ def test_login_errors(self, credentials): """ django_client = self.django_root_client # test the most recent version - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_login', kwargs={'api_version': version}) data = credentials[0] message = credentials[1] diff --git a/components/tools/OmeroWeb/test/integration/test_api_projects.py b/components/tools/OmeroWeb/test/integration/test_api_projects.py index f3a02d266c7..9c100d87a6d 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_projects.py +++ b/components/tools/OmeroWeb/test/integration/test_api_projects.py @@ -24,7 +24,7 @@ from omeroweb.testlib import IWebTest, _get_response_json, \ _csrf_post_json, _csrf_put_json, _csrf_delete_response_json from django.core.urlresolvers import reverse -from django.conf import settings +from omeroweb.api import api_settings from django.test import Client import pytest from omero.gateway import BlitzGateway @@ -265,7 +265,7 @@ def test_marshal_projects_not_logged_in(self): Test marshalling projects without log-in """ django_client = Client() - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) rsp = _get_response_json(django_client, request_url, {}, status_code=403) @@ -278,7 +278,7 @@ def test_marshal_projects_no_results(self, user1): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) rsp = _get_response_json(django_client, request_url, {}) assert rsp['data'] == [] @@ -290,7 +290,7 @@ def test_marshal_projects_user(self, user1, projects_user1_group1): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) rsp = _get_response_json(django_client, request_url, {}) # Reload projects with group '-1' to get same 'canLink' perms @@ -306,7 +306,7 @@ def test_marshal_projects_another_user(self, user1, user2, conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) rsp = _get_response_json(django_client, request_url, {}) # user1 reloads user2's projects @@ -320,7 +320,7 @@ def test_marshal_projects_another_group(self, user1, group2, conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) rsp = _get_response_json(django_client, request_url, {}) @@ -339,7 +339,7 @@ def test_marshal_projects_all_groups(self, user1, group1, group2, conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) # All groups @@ -366,7 +366,7 @@ def test_marshal_projects_all_users(self, user1, user2, conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) # Both users @@ -392,7 +392,7 @@ def test_marshal_projects_pagination(self, user1, user2, conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) # First page, just 2 projects. offset = 0 by default @@ -417,7 +417,7 @@ def test_marshal_projects_params(self, user1, user2, conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) # Test 'childCount' parameter @@ -462,7 +462,7 @@ def test_project_create_read(self): """ django_client = self.django_root_client group = self.ctx.groupId - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] # Need to get the Schema url to create @type base_url = reverse('api_base', kwargs={'api_version': version}) rsp = _get_response_json(django_client, base_url, {}) @@ -495,7 +495,7 @@ def test_project_create_other_group(self, user1, projects_user1_group2): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] # We're only using projects_user1_group2 to get group2 id group2_id = projects_user1_group2[0].getDetails().group.id.val # This seems to be the minimum details needed to pass group ID @@ -539,7 +539,7 @@ def test_project_update(self, user1): project = get_update_service(user1).saveAndReturnObject(project) # Update Project in 2 ways... - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] project_url = reverse('api_project', kwargs={'api_version': version, 'object_id': project.id.val}) @@ -590,7 +590,7 @@ def test_project_delete(self, user1): project.name = rstring('test_project_delete') project.description = rstring('Test update') project = get_update_service(user1).saveAndReturnObject(project) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] project_url = reverse('api_project', kwargs={'api_version': version, 'object_id': project.id.val}) diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index d746b2b0cc5..c18e1fc6ad2 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -21,7 +21,7 @@ from omeroweb.testlib import IWebTest, _get_response_json from django.core.urlresolvers import reverse -from django.conf import settings +from omeroweb.api import api_settings import pytest from test_api_projects import get_update_service, \ get_connection, marshal_objects @@ -173,7 +173,7 @@ def test_plate_wells(self, user1, small_plate, bigger_plate): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] wells_url = reverse('api_wells', kwargs={'api_version': version}) @@ -206,7 +206,7 @@ def test_well(self, user1, small_plate): conn = get_connection(user1) user_name = conn.getUser().getName() django_client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] small_plate = conn.getObject('Plate', small_plate.id.val) wells = [w._obj for w in small_plate.listChildren()] From 4b1476049afd4cbcab33b84d7ebcfafbec450b3b Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 21 Feb 2017 11:44:35 +0000 Subject: [PATCH 17/28] Blitz E.g. -> e.g. fixes --- .../tools/OmeroPy/src/omero/gateway/__init__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index fa10f9f12d5..96f8ffdc2fd 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -3020,7 +3020,7 @@ def getObject(self, obj_type, oid=None, params=None, attributes=None, If more than one object found, raises ome.conditions.ApiUsageException See :meth:`getObjects` for more info. - :param obj_type: Object type. E.g. "Project" see above + :param obj_type: Object type, e.g. "Project" see above :type obj_type: String :param ids: object IDs :type ids: List of Long @@ -3048,7 +3048,7 @@ def getObjects(self, obj_type, ids=None, params=None, attributes=None, be returned. i.e. listObjects() Filter objects by attributes. E.g. attributes={'name':name} - :param obj_type: Object type. E.g. "Project" see above + :param obj_type: Object type, e.g. "Project" see above :type obj_type: String :param ids: object IDs :type ids: List of Long @@ -3087,7 +3087,7 @@ def buildQuery(self, obj_type, ids=None, params=None, attributes=None, can be used with the appropriate query method. Used by :meth:`getObjects` and :meth:`getObject` above. - :param obj_type: Object type. E.g. "Project" see above + :param obj_type: Object type, e.g. "Project" see above :type obj_type: String :param ids: object IDs :type ids: List of Long @@ -3190,11 +3190,11 @@ def buildCountQuery(self, obj_type, opts=None): unnecessary objects to query. We return just the query and omero.sys.ParametersI for the query. - :param obj_type: Object type. E.g. "Project" see above + :param obj_type: Object type, e.g. "Project" see above :param opts: Dict of options for filtering by offset, limit and owner for all objects. Additional opts handled by _getQueryString() - E.g. filter Dataset by 'project' + e.g. filter Dataset by 'project' :return: (query, params) """ # We disable pagination since we want to count ALL results @@ -3252,7 +3252,7 @@ def getAnnotationLinks(self, parent_type, parent_ids=None, ann_ids=None, If parent_ids is None, all available objects will be returned. i.e. listObjects() - :param obj_type: Object type. E.g. "Project" see above + :param obj_type: Object type, e.g. "Project" see above :type obj_type: String :param ids: object IDs :type ids: List of Long From c2464537063b583b47bcd7b395de04b2abc800d8 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 21 Feb 2017 12:03:38 +0000 Subject: [PATCH 18/28] More fixes for PR #5067 --- .../OmeroWeb/omeroweb/api/api_settings.py | 2 +- .../test/integration/test_api_projects.py | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_settings.py b/components/tools/OmeroWeb/omeroweb/api/api_settings.py index 0515a0ebcbf..3c8ec797b1f 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_settings.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_settings.py @@ -52,6 +52,6 @@ # For any given release of api, we may support # one or more versions of the api. -# E.g. /api/v1.0/ +# E.g. /api/v0/ # TODO - need to decide how this is configured, strategy for extending etc. API_VERSIONS = ('0',) diff --git a/components/tools/OmeroWeb/test/integration/test_api_projects.py b/components/tools/OmeroWeb/test/integration/test_api_projects.py index 9c100d87a6d..179ceec7691 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_projects.py +++ b/components/tools/OmeroWeb/test/integration/test_api_projects.py @@ -395,16 +395,17 @@ def test_marshal_projects_pagination(self, user1, user2, version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) - # First page, just 2 projects. offset = 0 by default - limit = 2 - rsp = _get_response_json(django_client, request_url, {'limit': limit}) - assert len(rsp['data']) == limit - assert_objects(conn, rsp['data'], projects[0:limit]) - - # Check that page 2 gives next 2 projects - payload = {'limit': limit, 'offset': limit} - rsp = _get_response_json(django_client, request_url, payload) - assert_objects(conn, rsp['data'], projects[limit:limit * 2]) + # Test a range of limits. offset = 0 by default + for limit in range(1, 4): + rsp = _get_response_json(django_client, request_url, + {'limit': limit}) + assert len(rsp['data']) == limit + assert_objects(conn, rsp['data'], projects[0:limit]) + + # Check that page 2 gives next n projects + payload = {'limit': limit, 'offset': limit} + rsp = _get_response_json(django_client, request_url, payload) + assert_objects(conn, rsp['data'], projects[limit:limit * 2]) def test_marshal_projects_params(self, user1, user2, projects_user1_group1, From 9b480c5ed6570bcdbfcdbd8614facfbc618efcbf Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 21 Feb 2017 13:08:28 +0000 Subject: [PATCH 19/28] Test 'meta' 'totalCount' respects filtering by 'owner' --- .../tools/OmeroWeb/test/integration/test_api_projects.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/tools/OmeroWeb/test/integration/test_api_projects.py b/components/tools/OmeroWeb/test/integration/test_api_projects.py index 179ceec7691..c40d7971eda 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_projects.py +++ b/components/tools/OmeroWeb/test/integration/test_api_projects.py @@ -372,14 +372,17 @@ def test_marshal_projects_all_users(self, user1, user2, # Both users rsp = _get_response_json(django_client, request_url, {}) assert_objects(conn, rsp['data'], projects) + assert rsp['meta']['totalCount'] == len(projects) eid = user1[1].id.val rsp = _get_response_json(django_client, request_url, {'owner': eid}) assert_objects(conn, rsp['data'], projects_user1_group1) + assert rsp['meta']['totalCount'] == len(projects_user1_group1) eid = user2[1].id.val rsp = _get_response_json(django_client, request_url, {'owner': eid}) assert_objects(conn, rsp['data'], projects_user2_group1) + assert rsp['meta']['totalCount'] == len(projects_user2_group1) def test_marshal_projects_pagination(self, user1, user2, projects_user1_group1, From 33b817291ad16666e10d12ce24f42a947d86998d Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 21 Feb 2017 13:13:26 +0000 Subject: [PATCH 20/28] Test 'meta' 'totalCount' respects filtering by 'group' --- .../tools/OmeroWeb/test/integration/test_api_projects.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/tools/OmeroWeb/test/integration/test_api_projects.py b/components/tools/OmeroWeb/test/integration/test_api_projects.py index c40d7971eda..ffd65d533f5 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_projects.py +++ b/components/tools/OmeroWeb/test/integration/test_api_projects.py @@ -345,14 +345,17 @@ def test_marshal_projects_all_groups(self, user1, group1, group2, # All groups rsp = _get_response_json(django_client, request_url, {}) assert_objects(conn, rsp['data'], projects_user1) + assert rsp['meta']['totalCount'] == len(projects_user1) # Filter by group A... gid = group1.id.val rsp = _get_response_json(django_client, request_url, {'group': gid}) assert_objects(conn, rsp['data'], projects_user1, group=gid) + assert rsp['meta']['totalCount'] == len(rsp['data']) # ...and group B gid = group2.id.val rsp = _get_response_json(django_client, request_url, {'group': gid}) assert_objects(conn, rsp['data'], projects_user1, group=gid) + assert rsp['meta']['totalCount'] == len(rsp['data']) def test_marshal_projects_all_users(self, user1, user2, projects_user1_group1, From 39aeee1518a961e44175bc947a36a994b2e2b7bb Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 21 Feb 2017 13:28:51 +0000 Subject: [PATCH 21/28] Handle negative ?limit=-1 and ?offset=-1 use defaults instead --- components/tools/OmeroWeb/omeroweb/api/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index e94d585468c..5564bbed20c 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -311,6 +311,10 @@ def get_opts(self, request, **kwargs): orphaned = request.GET.get('orphaned', False) == 'true' except ValueError as ex: raise BadRequestError(str(ex)) + if limit < 0: + limit = api_settings.API_LIMIT + if offset < 0: + offset = 0 # orphaned and child_count not used by every subclass opts = {'offset': offset, From ca8807d2c48879f27869157da34d632581ddf511 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 21 Feb 2017 16:11:51 +0000 Subject: [PATCH 22/28] Use 'select count(distinct obj) to avoid duplicates for example, duplicate Wells were being counted, 1 per WellSample. --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 96f8ffdc2fd..beaa744421d 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -3206,7 +3206,7 @@ def buildCountQuery(self, obj_type, opts=None): query, params, wrapper = self.buildQuery(obj_type, opts=opts_copy) # Modify query to only select count() - query = query.replace("select obj ", "select count(obj) ") + query = query.replace("select obj ", "select count(distinct obj) ") query = query.replace("fetch", "") query = query.split("order by")[0] return query, params From d094f4919c1d8342b9363b1a37d121ad20faccd4 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 21 Feb 2017 21:34:12 +0000 Subject: [PATCH 23/28] Check ['meta']['totalCount'] for api_project and api_wells tests --- .../tools/OmeroWeb/test/integration/test_api_projects.py | 8 ++++++-- .../tools/OmeroWeb/test/integration/test_api_wells.py | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_projects.py b/components/tools/OmeroWeb/test/integration/test_api_projects.py index ffd65d533f5..dea52bc1897 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_projects.py +++ b/components/tools/OmeroWeb/test/integration/test_api_projects.py @@ -293,9 +293,8 @@ def test_marshal_projects_user(self, user1, projects_user1_group1): version = api_settings.API_VERSIONS[-1] request_url = reverse('api_projects', kwargs={'api_version': version}) rsp = _get_response_json(django_client, request_url, {}) - # Reload projects with group '-1' to get same 'canLink' perms - # on owner and group permissions assert_objects(conn, rsp['data'], projects_user1_group1) + assert rsp['meta']['totalCount'] == len(rsp['data']) def test_marshal_projects_another_user(self, user1, user2, projects_user2_group1): @@ -311,6 +310,7 @@ def test_marshal_projects_another_user(self, user1, user2, rsp = _get_response_json(django_client, request_url, {}) # user1 reloads user2's projects assert_objects(conn, rsp['data'], projects_user2_group1) + assert rsp['meta']['totalCount'] == len(rsp['data']) def test_marshal_projects_another_group(self, user1, group2, projects_user1_group2): @@ -329,6 +329,7 @@ def test_marshal_projects_another_group(self, user1, group2, # are same as owner's default group Group 1 (rwra--) instead of # group that the data is in Group 2 (rwr--) assert_objects(conn, rsp['data'], projects_user1_group2) + assert rsp['meta']['totalCount'] == len(rsp['data']) def test_marshal_projects_all_groups(self, user1, group1, group2, projects_user1): @@ -407,11 +408,13 @@ def test_marshal_projects_pagination(self, user1, user2, {'limit': limit}) assert len(rsp['data']) == limit assert_objects(conn, rsp['data'], projects[0:limit]) + assert rsp['meta']['totalCount'] == len(projects) # Check that page 2 gives next n projects payload = {'limit': limit, 'offset': limit} rsp = _get_response_json(django_client, request_url, payload) assert_objects(conn, rsp['data'], projects[limit:limit * 2]) + assert rsp['meta']['totalCount'] == len(projects) def test_marshal_projects_params(self, user1, user2, projects_user1_group1, @@ -446,6 +449,7 @@ def test_marshal_projects_params(self, user1, user2, # Test 'normalize' parameter. payload = {'normalize': 'true'} rsp = _get_response_json(django_client, request_url, payload) + assert rsp['meta']['totalCount'] == len(rsp['data']) for p in rsp['data']: details = p['omero:details'] owner = details['owner'] diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index c18e1fc6ad2..643a399a0f7 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -193,6 +193,7 @@ def test_plate_wells(self, user1, small_plate, bigger_plate): rsp = _get_response_json(django_client, wells_url, payload) # Manual check that Images are loaded but Pixels are not assert len(rsp['data']) == well_count + assert rsp['meta']['totalCount'] == well_count well_sample = rsp['data'][0]['WellSamples'][0] assert 'Image' in well_sample assert ('PlateAcquisition' in well_sample) == with_acq From 832b433b5a4bb8b14ecdc82e5da421db3f2b804d Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 22 Feb 2017 14:17:32 +0000 Subject: [PATCH 24/28] Move 'offset' and 'limit' validation to validate_opts() --- .../tools/OmeroWeb/omeroweb/api/api_query.py | 25 +++++++++++++++---- .../tools/OmeroWeb/omeroweb/api/views.py | 8 ++---- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_query.py b/components/tools/OmeroWeb/omeroweb/api/api_query.py index f681b5b0165..2ff244aef03 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_query.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_query.py @@ -28,6 +28,10 @@ from copy import deepcopy +MAX_LIMIT = max(1, api_settings.API_MAX_LIMIT) +DEFAULT_LIMIT = max(1, api_settings.API_LIMIT) + + def get_child_counts(conn, link_class, parent_ids): """ Count child links for the specified parent_ids. @@ -51,6 +55,18 @@ def get_child_counts(conn, link_class, parent_ids): return counts +def validate_opts(opts): + """Check that opts dict has valid 'limit' and 'offset'.""" + if opts is None: + opts = {} + if opts.get('limit') is None or opts.get('limit') < 0: + opts['limit'] = DEFAULT_LIMIT + opts['limit'] = min(opts['limit'], MAX_LIMIT) + if opts.get('offset') is None or opts.get('offset') < 0: + opts['offset'] = 0 + return opts + + def query_objects(conn, object_type, group=None, opts=None, @@ -67,6 +83,7 @@ def query_objects(conn, object_type, @param opts: Options dict for conn.buildQuery() @param normalize: If true, marshal groups and experimenters separately """ + opts = validate_opts(opts) # buildQuery is used by conn.getObjects() query, params, wrapper = conn.buildQuery(object_type, opts=opts) # Set the desired group context @@ -97,11 +114,9 @@ def query_objects(conn, object_type, result = qs.projection(count_query, params, ctx) meta = {} - if 'offset' in opts: - meta['offset'] = opts['offset'] - if 'limit' in opts: - meta['limit'] = opts['limit'] - meta['maxLimit'] = api_settings.API_MAX_LIMIT + meta['offset'] = opts['offset'] + meta['limit'] = opts['limit'] + meta['maxLimit'] = MAX_LIMIT meta['totalCount'] = result[0][0].val marshalled = marshal_objects(objects, extras=extras, normalize=normalize) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 5564bbed20c..4c7add6389e 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -305,20 +305,16 @@ def get_opts(self, request, **kwargs): """Return an options dict based on request parameters.""" try: offset = getIntOrDefault(request, 'offset', 0) - limit = getIntOrDefault(request, 'limit', api_settings.API_LIMIT) + limit = getIntOrDefault(request, 'limit', None) owner = getIntOrDefault(request, 'owner', None) child_count = request.GET.get('childCount', False) == 'true' orphaned = request.GET.get('orphaned', False) == 'true' except ValueError as ex: raise BadRequestError(str(ex)) - if limit < 0: - limit = api_settings.API_LIMIT - if offset < 0: - offset = 0 # orphaned and child_count not used by every subclass opts = {'offset': offset, - 'limit': min(limit, api_settings.API_MAX_LIMIT), + 'limit': limit, 'owner': owner, 'orphaned': orphaned, 'child_count': child_count, From 57d9199400f844049d5b511c1c360169c138894f Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 22 Feb 2017 14:21:15 +0000 Subject: [PATCH 25/28] Remove try/except from gateway.util.getIntOrDefault() Having this fail silently and return 0 is not helpful. Users need to know that something has gone wrong. Now uses same logic as webcient views.get_long_or_default() --- components/tools/OmeroWeb/omeroweb/webgateway/util.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/webgateway/util.py b/components/tools/OmeroWeb/omeroweb/webgateway/util.py index ec006cd9486..953e42dd84b 100644 --- a/components/tools/OmeroWeb/omeroweb/webgateway/util.py +++ b/components/tools/OmeroWeb/omeroweb/webgateway/util.py @@ -28,12 +28,9 @@ # helper method def getIntOrDefault(request, name, default): - try: - index = request.GET.get(name, request.POST.get(name, default)) - if index is not None: - index = int(index) - except ValueError: - index = 0 + index = request.GET.get(name, request.POST.get(name, default)) + if index is not None: + index = int(index) return index From fb4b621e65669237f59677a865e76a573e9bb7f0 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 22 Feb 2017 23:02:19 +0000 Subject: [PATCH 26/28] Add test_invalid_parameter() to test_api_errors.py --- .../OmeroWeb/test/integration/test_api_errors.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_errors.py b/components/tools/OmeroWeb/test/integration/test_api_errors.py index 3bab8ed0267..3279bcb2bac 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_errors.py +++ b/components/tools/OmeroWeb/test/integration/test_api_errors.py @@ -19,7 +19,8 @@ """Tests querying & editing Projects with webgateway json api.""" -from omeroweb.testlib import IWebTest, _csrf_post_json, _csrf_put_json +from omeroweb.testlib import IWebTest, _csrf_post_json, _csrf_put_json, \ + _get_response_json from django.core.urlresolvers import reverse from omeroweb.api import api_settings import pytest @@ -91,6 +92,17 @@ def test_marshal_type(self): assert (rsp['message'] == 'No decoder found for type: %s' % objType) + def test_invalid_parameter(self): + """Test that invalid query parameter gives suitable message.""" + django_client = self.django_root_client + version = api_settings.API_VERSIONS[-1] + projects_url = reverse('api_projects', kwargs={'api_version': version}) + payload = {'limit': 'foo'} + rsp = _get_response_json(django_client, projects_url, payload, + status_code=400) + assert (rsp['message'] == + "invalid literal for int() with base 10: 'foo'") + def test_marshal_validation(self): """Test that we get expected error with invalid @type in json.""" django_client = self.django_root_client From 2d9cbfbeea4ec5ecc035ca48496bc3760dffde5d Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 22 Feb 2017 23:11:33 +0000 Subject: [PATCH 27/28] tests include limit=-1 and limit=0 --- .../OmeroWeb/test/integration/test_api_projects.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_projects.py b/components/tools/OmeroWeb/test/integration/test_api_projects.py index dea52bc1897..60bdd40ef41 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_projects.py +++ b/components/tools/OmeroWeb/test/integration/test_api_projects.py @@ -168,6 +168,9 @@ def assert_objects(conn, json_objects, omero_ids_objects, dtype="Project", pids.append(long(p)) except TypeError: pids.append(p.id.val) + if len(pids) == 0: + assert len(json_objects) == 0 + return conn.SERVICE_OPTS.setOmeroGroup(group) projects = conn.getObjects(dtype, pids, respect_order=True) projects = [p._obj for p in projects] @@ -403,12 +406,17 @@ def test_marshal_projects_pagination(self, user1, user2, request_url = reverse('api_projects', kwargs={'api_version': version}) # Test a range of limits. offset = 0 by default - for limit in range(1, 4): + for limit in range(-1, 4): rsp = _get_response_json(django_client, request_url, {'limit': limit}) - assert len(rsp['data']) == limit - assert_objects(conn, rsp['data'], projects[0:limit]) assert rsp['meta']['totalCount'] == len(projects) + if limit == -1: + # if we passed a negative number, expect the default to be used + limit = api_settings.API_LIMIT + else: + assert len(rsp['data']) == limit + assert rsp['meta']['limit'] == limit + assert_objects(conn, rsp['data'], projects[0:limit]) # Check that page 2 gives next n projects payload = {'limit': limit, 'offset': limit} From f08de8da82ed0a6c8367da0e0b9eafcd6f138919 Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 23 Feb 2017 23:17:34 +0000 Subject: [PATCH 28/28] If ?limit=0 result = [] --- components/tools/OmeroWeb/omeroweb/api/api_query.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_query.py b/components/tools/OmeroWeb/omeroweb/api/api_query.py index 2ff244aef03..dd08b8fe47a 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_query.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_query.py @@ -97,7 +97,10 @@ def query_objects(conn, object_type, objects = [] extras = {} - result = qs.findAllByQuery(query, params, ctx) + if opts['limit'] == 0: + result = [] + else: + result = qs.findAllByQuery(query, params, ctx) for obj in result: objects.append(obj)