From 7c632bb871fe7c14c41a8d3300856196c751697e Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 10:01:57 +0000 Subject: [PATCH 01/33] Add /wells/ ordered by column, row NOT name --- .../tools/OmeroWeb/omeroweb/api/urls.py | 8 +++++ .../tools/OmeroWeb/omeroweb/api/views.py | 34 ++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index f2a77063fd3..a3318a97c4d 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -169,6 +169,13 @@ Plate url to GET or DELETE a single Plate """ +api_wells = url(r'^v(?P%s)/m/wells/$' % versions, + views.WellsView.as_view(), + name='api_wells') +""" +GET all wells, using omero-marshal to generate json +""" + urlpatterns = patterns( '', api_versions, @@ -190,4 +197,5 @@ api_plates, api_screen_plates, api_plate, + api_wells, ) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index b1b8bcae317..08b69d71307 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -258,7 +258,6 @@ def get_opts(self, request, **kwargs): 'owner': owner, 'orphaned': orphaned, 'child_count': child_count, - 'order_by': 'name', # NB: will break if object has no name } return opts @@ -280,6 +279,12 @@ class ProjectsView(ObjectsView): OMERO_TYPE = 'Project' + def get_opts(self, request, **kwargs): + """Add extra parameters to the opts dict.""" + opts = super(ProjectsView, self).get_opts(request, **kwargs) + opts['order_by'] = 'lower(name)' + return opts + # To add a url to marshalled object add to this dict # 'name' is url name, kwargs are passed to reverse() # If any kwargs values are 'OBJECT_ID' then this placeholder will be @@ -298,8 +303,9 @@ class DatasetsView(ObjectsView): OMERO_TYPE = 'Dataset' def get_opts(self, request, **kwargs): - """Add filtering by 'project' to the opts dict.""" + """Add extra parameters to the opts dict.""" opts = super(DatasetsView, self).get_opts(request, **kwargs) + opts['order_by'] = 'lower(name)' # at /projects/:project_id/datasets/ we have 'project_id' in kwargs if 'project_id' in kwargs: opts['project'] = long(kwargs['project_id']) @@ -324,6 +330,12 @@ class ScreensView(ObjectsView): OMERO_TYPE = 'Screen' + def get_opts(self, request, **kwargs): + """Add extra parameters to the opts dict.""" + opts = super(ScreensView, self).get_opts(request, **kwargs) + opts['order_by'] = 'lower(name)' + return opts + # Urls to add to marshalled object. See ProjectsView for more details urls = { 'url:plates': {'name': 'api_screen_plates', @@ -339,8 +351,9 @@ class PlatesView(ObjectsView): OMERO_TYPE = 'Plate' def get_opts(self, request, **kwargs): - """Add filtering by 'screen' to the opts dict.""" + """Add extra parameters to the opts dict.""" opts = super(PlatesView, self).get_opts(request, **kwargs) + opts['order_by'] = 'lower(name)' # at /screens/:screen_id/plates/ we have 'screen_id' in kwargs if 'screen_id' in kwargs: opts['screen'] = long(kwargs['screen_id']) @@ -370,8 +383,9 @@ class ImagesView(ObjectsView): } def get_opts(self, request, **kwargs): - """Add filtering by 'dataset' and other params to the opts dict.""" + """Add extra parameters to the opts dict.""" opts = super(ImagesView, self).get_opts(request, **kwargs) + opts['order_by'] = 'lower(name)' # at /datasets/:dataset_id/images/ we have 'dataset_id' in kwargs if 'dataset_id' in kwargs: opts['dataset'] = long(kwargs['dataset_id']) @@ -385,6 +399,18 @@ def get_opts(self, request, **kwargs): return opts +class WellsView(ObjectsView): + """Handles GET for /wells/ to list available Images.""" + + OMERO_TYPE = 'Well' + + def get_opts(self, request, **kwargs): + """Add extra parameters to the opts dict.""" + opts = super(WellsView, self).get_opts(request, **kwargs) + opts['order_by'] = 'column, row' + return opts + + class SaveView(View): """ This view provides 'Save' functionality for all types of objects. From 18434431a84a107647d2328e4a6433cd2680fd72 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 10:03:45 +0000 Subject: [PATCH 02/33] Blitz buildQuery() remove lower(obj.%s) from order_by This allows usage of non-string attributes. E.g. 'obj.column, obj.row' for wells. But now you need to specify the 'obj' in the order_by. E.g. 'lower(obj.name)' for images etc. --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index a6fe2213217..79b963a5ab3 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -3096,6 +3096,7 @@ def buildQuery(self, obj_type, ids=None, params=None, attributes=None, :param opts: Dict of additional options for filtering or defining extra data to load. offset, limit and owner for all objects. + Also 'order_by': 'obj.name' to order results. Additional opts handled by _getQueryString() E.g. 'childCount', or filter Dataset by 'project' :return: (query, params, wrapper) @@ -3171,9 +3172,9 @@ def buildQuery(self, obj_type, ids=None, params=None, attributes=None, if clauses: query += " where " + (" and ".join(clauses)) - # Order by... + # Order by... e.g. 'lower(obj.name)' or 'obj.column, obj.row' for wells if order_by is not None: - query += " order by lower(obj.%s), obj.id" % order_by + query += " order by %s, obj.id" % order_by return (query, baseParams, wrapper) From f26b10e6c3de133679ecbafcc18a2d883902212d Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 10:41:48 +0000 Subject: [PATCH 03/33] ObjectsView order_by uses 'obj.' --- components/tools/OmeroWeb/omeroweb/api/views.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 08b69d71307..971e293710a 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -282,7 +282,7 @@ class ProjectsView(ObjectsView): def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(ProjectsView, self).get_opts(request, **kwargs) - opts['order_by'] = 'lower(name)' + opts['order_by'] = 'lower(obj.name)' return opts # To add a url to marshalled object add to this dict @@ -305,7 +305,7 @@ class DatasetsView(ObjectsView): def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(DatasetsView, self).get_opts(request, **kwargs) - opts['order_by'] = 'lower(name)' + opts['order_by'] = 'lower(obj.name)' # at /projects/:project_id/datasets/ we have 'project_id' in kwargs if 'project_id' in kwargs: opts['project'] = long(kwargs['project_id']) @@ -333,7 +333,7 @@ class ScreensView(ObjectsView): def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(ScreensView, self).get_opts(request, **kwargs) - opts['order_by'] = 'lower(name)' + opts['order_by'] = 'lower(obj.name)' return opts # Urls to add to marshalled object. See ProjectsView for more details @@ -353,7 +353,7 @@ class PlatesView(ObjectsView): def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(PlatesView, self).get_opts(request, **kwargs) - opts['order_by'] = 'lower(name)' + opts['order_by'] = 'lower(obj.name)' # at /screens/:screen_id/plates/ we have 'screen_id' in kwargs if 'screen_id' in kwargs: opts['screen'] = long(kwargs['screen_id']) @@ -385,7 +385,7 @@ class ImagesView(ObjectsView): def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(ImagesView, self).get_opts(request, **kwargs) - opts['order_by'] = 'lower(name)' + opts['order_by'] = 'lower(obj.name)' # at /datasets/:dataset_id/images/ we have 'dataset_id' in kwargs if 'dataset_id' in kwargs: opts['dataset'] = long(kwargs['dataset_id']) @@ -407,7 +407,7 @@ class WellsView(ObjectsView): def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(WellsView, self).get_opts(request, **kwargs) - opts['order_by'] = 'column, row' + opts['order_by'] = 'obj.column, obj.row' return opts From 02bd39bae961c691cf22ee1b11a20e5a72a94aaf Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 10:42:57 +0000 Subject: [PATCH 04/33] Blitz Well.getQueryString() supports filter by 'plate' --- .../tools/OmeroPy/src/omero/gateway/__init__.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 79b963a5ab3..c89e05407ef 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -6054,6 +6054,23 @@ def __reset__(self): """ self._childcache = None + @classmethod + def _getQueryString(cls, opts=None): + """ + Extend base query to handle filtering of Wells by Plate. + Returns a tuple of (query, clauses, params). + Supported opts: 'plate': to filter by Plate + + :param opts: Dictionary of optional parameters. + :return: Tuple of string, list, ParametersI + """ + query, clauses, params = super( + _WellWrapper, cls)._getQueryString(opts) + if opts is not None and 'plate' in opts: + clauses.append('obj.plate.id = :pid') + params.add('pid', rlong(opts['plate'])) + return (query, clauses, params) + def __loadedHotSwap__(self): query = ("select well from Well as well " "join fetch well.details.creationEvent " From e630df594a5778a5dc45d0c6cd68ae9ab0e20f87 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 11:01:30 +0000 Subject: [PATCH 05/33] Filter Wells by ?plate=id or /plates/id/wells --- components/tools/OmeroWeb/omeroweb/api/urls.py | 10 ++++++++++ components/tools/OmeroWeb/omeroweb/api/views.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index a3318a97c4d..51dbedcf067 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -176,6 +176,15 @@ GET all wells, using omero-marshal to generate json """ +api_plate_wells = url( + r'^v(?P%s)/m/plates/' + '(?P[0-9]+)/wells/$' % versions, + views.WellsView.as_view(), + name='api_plate_wells') +""" +GET Wells in Plate, using omero-marshal to generate json +""" + urlpatterns = patterns( '', api_versions, @@ -198,4 +207,5 @@ api_screen_plates, api_plate, api_wells, + api_plate_wells, ) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 971e293710a..1e195f3735d 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -366,6 +366,8 @@ def get_opts(self, request, **kwargs): # Urls to add to marshalled object. See ProjectsView for more details urls = { + 'url:wells': {'name': 'api_plate_wells', + 'kwargs': {'plate_id': 'OBJECT_ID'}}, 'url:plate': {'name': 'api_plate', 'kwargs': {'object_id': 'OBJECT_ID'}} } @@ -408,6 +410,14 @@ def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(WellsView, self).get_opts(request, **kwargs) opts['order_by'] = 'obj.column, obj.row' + # at /plates/:plate_id/wells/ we have 'plate_id' in kwargs + if 'plate_id' in kwargs: + opts['plate'] = long(kwargs['plate_id']) + else: + # filter by query /wells/?plate=:id + plate = getIntOrDefault(request, 'plate', None) + if plate is not None: + opts['plate'] = plate return opts From 51bc36ce554ca8e7f64e0233f2a12f382a44989e Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 11:04:41 +0000 Subject: [PATCH 06/33] Add wells_url to /plates/:id/ json --- components/tools/OmeroWeb/omeroweb/api/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 1e195f3735d..ef460e62595 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -237,6 +237,12 @@ class PlateView(ObjectView): OMERO_TYPE = 'Plate' + # Urls to add to marshalled object. See ProjectsView for more details + urls = { + 'wells_url': {'name': 'api_plate_wells', + 'kwargs': {'plate_id': 'OBJECT_ID'}} + } + class ObjectsView(ApiView): """Base class for listing objects.""" From 7b785cce0476f7c30dc89a4773d59dd9f6c532ff Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 11:15:05 +0000 Subject: [PATCH 07/33] Blitz Well.getQueryString() supports 'load_images' --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index c89e05407ef..8e774805b27 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -6060,6 +6060,7 @@ def _getQueryString(cls, opts=None): Extend base query to handle filtering of Wells by Plate. Returns a tuple of (query, clauses, params). Supported opts: 'plate': to filter by Plate + 'load_images': to load wellSamples and images :param opts: Dictionary of optional parameters. :return: Tuple of string, list, ParametersI @@ -6069,6 +6070,10 @@ def _getQueryString(cls, opts=None): if opts is not None and 'plate' in opts: clauses.append('obj.plate.id = :pid') params.add('pid', rlong(opts['plate'])) + if opts is not None and opts.get('load_images'): + # NB: Using left outer join, we may get Wells with no Images + query += " left outer join fetch obj.wellSamples as wellSamples"\ + " left outer join fetch wellSamples.image as image" return (query, clauses, params) def __loadedHotSwap__(self): From 9139d69b107f33bb190f1d2c5e3464274b1b58c5 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 12:33:40 +0000 Subject: [PATCH 08/33] Blitz Well.getQueryString() supports load_pixels & load_channels --- .../OmeroPy/src/omero/gateway/__init__.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 8e774805b27..e21489ec826 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -6070,10 +6070,28 @@ def _getQueryString(cls, opts=None): if opts is not None and 'plate' in opts: clauses.append('obj.plate.id = :pid') params.add('pid', rlong(opts['plate'])) - if opts is not None and opts.get('load_images'): + load_images = False + load_pixels = False + load_channels = False + if opts is not None: + load_images = opts.get('load_images') + load_pixels = opts.get('load_pixels') + load_channels = opts.get('load_channels') + if load_images or load_pixels or load_channels: # NB: Using left outer join, we may get Wells with no Images query += " left outer join fetch obj.wellSamples as wellSamples"\ " left outer join fetch wellSamples.image as image" + if load_pixels or load_channels: + query += ' left outer join fetch image.pixels pixels' \ + ' left outer join fetch pixels.pixelsType' + if load_channels: + query += ' join fetch pixels.channels as channels' \ + ' join fetch channels.logicalChannel as logicalChannel' \ + ' left outer join fetch logicalChannel.photometricInterpretation' \ + ' left outer join fetch logicalChannel.illumination' \ + ' left outer join fetch logicalChannel.mode' \ + ' left outer join fetch logicalChannel.contrastMethod' + return (query, clauses, params) def __loadedHotSwap__(self): From 6fec3870326499bf1a18ea34f0f919a532c503d7 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 12:37:22 +0000 Subject: [PATCH 09/33] add_data(urls={}) can be used with specified urls --- components/tools/OmeroWeb/omeroweb/api/urls.py | 9 +++++++++ components/tools/OmeroWeb/omeroweb/api/views.py | 8 ++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index 51dbedcf067..236beae82a8 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -185,6 +185,14 @@ GET Wells in Plate, using omero-marshal to generate json """ +api_well = url( + r'^v(?P%s)/m/wells/(?P[0-9]+)/$' % versions, + views.WellView.as_view(), + name='api_well') +""" +Well url to GET or DELETE a single Well +""" + urlpatterns = patterns( '', api_versions, @@ -208,4 +216,5 @@ api_plate, api_wells, api_plate_wells, + api_well, ) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index ef460e62595..22809173a08 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -123,7 +123,7 @@ def dispatch(self, *args, **kwargs): """Wrap other methods to add decorators.""" return super(ApiView, self).dispatch(*args, **kwargs) - def add_data(self, marshalled, request, **kwargs): + def add_data(self, marshalled, request, urls={}, **kwargs): """ Post-process marshalled object to add any extra data. @@ -133,7 +133,7 @@ def add_data(self, marshalled, request, **kwargs): """ object_id = marshalled['@id'] version = kwargs['api_version'] - for key, args in self.urls.items(): + for key, args in urls.items(): name = args['name'] kwargs = args['kwargs'].copy() # If kwargs has 'OBJECT_ID' placeholder, we replace with id @@ -161,7 +161,7 @@ def get(self, request, object_id, conn=None, **kwargs): object_id)) encoder = get_encoder(obj._obj.__class__) marshalled = encoder.encode(obj._obj) - self.add_data(marshalled, request, **kwargs) + self.add_data(marshalled, request, self.urls, **kwargs) return marshalled def delete(self, request, object_id, conn=None, **kwargs): @@ -276,7 +276,7 @@ def get(self, request, conn=None, **kwargs): marshalled = query_objects(conn, self.OMERO_TYPE, group, opts, normalize) for m in marshalled['data']: - self.add_data(m, request, **kwargs) + self.add_data(m, request, self.urls, **kwargs) return marshalled From bf22ebb80cb4515a41961570c0d89d0a40af297d Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 12:41:46 +0000 Subject: [PATCH 10/33] Add WellView with add_data() to add urls to Images --- .../tools/OmeroWeb/omeroweb/api/views.py | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 22809173a08..9233600de51 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -214,7 +214,6 @@ class ImageView(ObjectView): def get_opts(self, request): """Add support for load_pixels and load_channels.""" opts = super(ImageView, self).get_opts(request) - opts['orphaned'] = request.GET.get('orphaned', False) == 'true' # for single image, we always load channels opts['load_channels'] = True return opts @@ -243,6 +242,33 @@ class PlateView(ObjectView): 'kwargs': {'plate_id': 'OBJECT_ID'}} } +class WellView(ObjectView): + """Handle access to an individual Well to GET or DELETE it.""" + + OMERO_TYPE = 'Well' + + def get_opts(self, request): + """Add support for load_images.""" + opts = super(WellView, self).get_opts(request) + # for single well, we always load images + opts['load_pixels'] = True + return opts + + def add_data(self, marshalled, request, urls={}, **kwargs): + """Add 'image_url' to any 'Image' in 'WellSamples'.""" + marshalled = super(WellView, self).add_data(marshalled, request, + urls=urls, **kwargs) + image_urls = { + 'image_url': {'name': 'api_image', + 'kwargs': {'object_id': 'OBJECT_ID'}}, + } + if 'WellSamples' in marshalled: + # For each WellSample, add image_urls to Image + for ws in marshalled['WellSamples']: + if 'Image' in ws: + self.add_data(ws['Image'], request, image_urls, **kwargs) + return marshalled + class ObjectsView(ApiView): """Base class for listing objects.""" @@ -412,6 +438,12 @@ class WellsView(ObjectsView): OMERO_TYPE = 'Well' + # Urls to add to marshalled object. See ProjectsView for more details + urls = { + 'well_url': {'name': 'api_well', + 'kwargs': {'object_id': 'OBJECT_ID'}}, + } + def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(WellsView, self).get_opts(request, **kwargs) @@ -424,6 +456,8 @@ def get_opts(self, request, **kwargs): plate = getIntOrDefault(request, 'plate', None) if plate is not None: opts['plate'] = plate + # Listing Wells, load Images with Pixels + opts['load_pixels'] = True return opts From 43f5971444c8ff99dcdffc7f032b4aa22e8c6498 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 18 Jan 2017 14:08:27 +0000 Subject: [PATCH 11/33] flake8 fixes --- components/tools/OmeroWeb/omeroweb/api/urls.py | 4 ++-- components/tools/OmeroWeb/omeroweb/api/views.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index 236beae82a8..f320700b2bd 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -170,8 +170,8 @@ """ api_wells = url(r'^v(?P%s)/m/wells/$' % versions, - views.WellsView.as_view(), - name='api_wells') + views.WellsView.as_view(), + name='api_wells') """ GET all wells, using omero-marshal to generate json """ diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 9233600de51..0c12891e683 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -242,6 +242,7 @@ class PlateView(ObjectView): 'kwargs': {'plate_id': 'OBJECT_ID'}} } + class WellView(ObjectView): """Handle access to an individual Well to GET or DELETE it.""" From b4fe99f92808a8ae2aa9c7dc4224bd28ec774eeb Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 19 Jan 2017 11:51:35 +0000 Subject: [PATCH 12/33] Use 'url:wells' for wells in Plate json --- components/tools/OmeroWeb/omeroweb/api/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 0c12891e683..4fc3866a601 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -238,7 +238,7 @@ class PlateView(ObjectView): # Urls to add to marshalled object. See ProjectsView for more details urls = { - 'wells_url': {'name': 'api_plate_wells', + 'url:wells': {'name': 'api_plate_wells', 'kwargs': {'plate_id': 'OBJECT_ID'}} } From f2d4fbfb7d31ff7f00037a43b30e120573c7aaec Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 19 Jan 2017 11:58:24 +0000 Subject: [PATCH 13/33] Load images for /wells/ and pixels for /wells/:id --- components/tools/OmeroWeb/omeroweb/api/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 4fc3866a601..6c088ed7ca6 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -251,7 +251,7 @@ class WellView(ObjectView): def get_opts(self, request): """Add support for load_images.""" opts = super(WellView, self).get_opts(request) - # for single well, we always load images + # for single well, we load images with pixels opts['load_pixels'] = True return opts @@ -457,8 +457,8 @@ def get_opts(self, request, **kwargs): plate = getIntOrDefault(request, 'plate', None) if plate is not None: opts['plate'] = plate - # Listing Wells, load Images with Pixels - opts['load_pixels'] = True + # Listing Wells, load Images + opts['load_images'] = True return opts From af784b9f7002247ce222ddf0d5cae3b4bfd8394d Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 19 Jan 2017 15:06:32 +0000 Subject: [PATCH 14/33] Also add 'url:image' to Images in /wells/ --- .../tools/OmeroWeb/omeroweb/api/views.py | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 6c088ed7ca6..efe584b8a3d 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -256,15 +256,15 @@ def get_opts(self, request): return opts def add_data(self, marshalled, request, urls={}, **kwargs): - """Add 'image_url' to any 'Image' in 'WellSamples'.""" + """Add 'url:image' to any 'Image' in 'WellSamples'.""" marshalled = super(WellView, self).add_data(marshalled, request, urls=urls, **kwargs) image_urls = { - 'image_url': {'name': 'api_image', + 'url:image': {'name': 'api_image', 'kwargs': {'object_id': 'OBJECT_ID'}}, } if 'WellSamples' in marshalled: - # For each WellSample, add image_urls to Image + # For each WellSample, add image urls to Image for ws in marshalled['WellSamples']: if 'Image' in ws: self.add_data(ws['Image'], request, image_urls, **kwargs) @@ -441,7 +441,7 @@ class WellsView(ObjectsView): # Urls to add to marshalled object. See ProjectsView for more details urls = { - 'well_url': {'name': 'api_well', + 'url:well': {'name': 'api_well', 'kwargs': {'object_id': 'OBJECT_ID'}}, } @@ -461,6 +461,21 @@ def get_opts(self, request, **kwargs): opts['load_images'] = True return opts + def add_data(self, marshalled, request, urls={}, **kwargs): + """Add 'url:image' to any 'Image' in 'WellSamples'.""" + marshalled = super(WellsView, self).add_data(marshalled, request, + urls=urls, **kwargs) + image_urls = { + 'url:image': {'name': 'api_image', + 'kwargs': {'object_id': 'OBJECT_ID'}}, + } + if 'WellSamples' in marshalled: + # For each WellSample, add image urls to Image + for ws in marshalled['WellSamples']: + if 'Image' in ws: + self.add_data(ws['Image'], request, image_urls, **kwargs) + return marshalled + class SaveView(View): """ From 9b8d9a06993deefea7b81a6cb962960880574367 Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 19 Jan 2017 15:52:27 +0000 Subject: [PATCH 15/33] New test_api_wells.py tests lising /wells/ and /wells/:id/ --- .../test/integration/test_api_wells.py | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 components/tools/OmeroWeb/test/integration/test_api_wells.py diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py new file mode 100644 index 00000000000..2b167db387b --- /dev/null +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -0,0 +1,229 @@ +#!/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 . + +"""Tests querying Wells with web json api.""" + +from omeroweb.testlib import IWebTest, _get_response_json +from django.core.urlresolvers import reverse +from django.conf import settings +import pytest +from omero.gateway import BlitzGateway +from omero_marshal import get_encoder +from omero.model import PlateI, WellI, WellSampleI, ImageI, LengthI +from omero.model.enums import UnitsLength +from omero.rtypes import rstring, rint, unwrap +import json + + +def get_update_service(user): + """Get the update_service for the given user's client.""" + return user[0].getSession().getUpdateService() + + +def get_query_service(user): + """Get the query_service for the given user's client.""" + return user[0].getSession().getQueryService() + + +def get_connection(user, group_id=None): + """Get a BlitzGateway connection for the given user's client.""" + connection = BlitzGateway(client_obj=user[0]) + # Refresh the session context + connection.getEventContext() + if group_id is not None: + connection.SERVICE_OPTS.setOmeroGroup(group_id) + return connection + + +def cmp_name_insensitive(x, y): + """Case-insensitive name comparator.""" + return cmp(unwrap(x.name).lower(), unwrap(y.name).lower()) + +def cmp_column_row(x, y): + """Sort wells by row, then column.""" + sort_by_column = cmp(unwrap(x.column), unwrap(y.column)) + if sort_by_column == 0: + return cmp(unwrap(x.row), unwrap(y.row)) + return sort_by_column + +def marshal_objects(objects): + """Marshal objects using omero_marshal.""" + expected = [] + for obj in objects: + encoder = get_encoder(obj.__class__) + expected.append(encoder.encode(obj)) + return expected + + +def remove_urls(marshalled): + """Traverse a dict (Well) removing 'url:' values.""" + for key, val in marshalled.items(): + if key.startswith('url:'): + del(marshalled[key]) + # We only traverse paths where we know urls are + elif key == 'Image': # isinstance(val, dict): + remove_urls(val) + elif key == 'WellSamples': # isinstance(val, list): + for i in val: + remove_urls(i) + + +def assert_objects(conn, json_objects, omero_ids_objects, dtype="Project", + group='-1', extra=None, opts=None): + """ + Load objects from OMERO, via conn.getObjects(). + + marshal with omero_marshal and compare with json_objects. + omero_ids_objects can be IDs or list of omero.model objects. + + @param: extra List of dicts containing expected extra json data + e.g. {'omero:childCount': 1} + """ + pids = [] + for p in omero_ids_objects: + try: + pids.append(long(p)) + except TypeError: + pids.append(p.id.val) + conn.SERVICE_OPTS.setOmeroGroup(group) + objs = conn.getObjects(dtype, pids, respect_order=True, opts=opts) + objs = [p._obj for p in objs] + expected = marshal_objects(objs) + assert len(json_objects) == len(expected) + for i, o1, o2 in zip(range(len(expected)), json_objects, expected): + if extra is not None and i < len(extra): + o2.update(extra[i]) + # dumping to json and loading (same as test data) means that + # unicode has been handled in same way, e.g. Pixel size symbols. + o2 = json.loads(json.dumps(o2)) + # remove any urls from json (tested elsewhere) + remove_urls(o1) + + assert o1 == o2 + + +class TestWells(IWebTest): + """Tests querying Wells.""" + + @pytest.fixture() + def user1(self): + """Return a new user in a read-annotate group.""" + group = self.new_group(perms='rwra--') + return self.new_client_and_user(group=group) + + def create_plate_wells(self, user1, rows, cols): + """Return Plate with Wells.""" + updateService = get_update_service(user1) + plate = PlateI() + plate.name = rstring('plate') + plate = updateService.saveAndReturnObject(plate) + + # Create Wells for plate + for row in range(rows): + for col in range(cols): + # create Well + well = WellI() + well.column = rint(col) + well.row = rint(row) + well.plate = PlateI(plate.id.val, False) + # Only wells in first Column have well-samples etc + if col == 0: + # Have 3 images/well-samples in these wells + for i in range(3): + image = self.create_test_image( + size_x=5, size_y=5, session=user1[0].getSession()) + ws = WellSampleI() + ws.image = ImageI(image.id, False) + ws.well = well + ws.posX = LengthI(i * 10, UnitsLength.REFERENCEFRAME) + ws.posY = LengthI(i, UnitsLength.REFERENCEFRAME) + well.addWellSample(ws) + updateService.saveObject(well) + return plate + + @pytest.fixture() + def small_plate(self, user1): + """ + Create a small plate with 1 row and 2 columns. + + Two wells are created, but only the first has any Images (3). + """ + return self.create_plate_wells(user1, 1, 2) + + @pytest.fixture() + def bigger_plate(self, user1): + """ + Create a bigger plate with 2 rows and 3 columns. + + Six wells are created, but only wells in the first + column have any Images (3 in each Well). + """ + return self.create_plate_wells(user1, 2, 3) + + def test_plate_wells(self, user1, small_plate, bigger_plate): + """Test listing of Wells in a 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] + + # Use Blitz Plates for listing Wells etc. + bigger_plate = conn.getObject('Plate', bigger_plate.id.val) + wells = [w._obj for w in bigger_plate.listChildren()] + wells.sort(cmp_column_row) + + wells_url = reverse('api_wells', kwargs={'api_version': version}) + + # List ALL Wells in both plates + rsp = _get_response_json(django_client, wells_url, {}) + assert len(rsp['data']) == 8 + + # Filter Wells by Plate + payload = {'plate': bigger_plate.id} + rsp = _get_response_json(django_client, wells_url, payload) + # Manual check that Images are loaded but Pixels are not + assert 'Image' in rsp['data'][0]['WellSamples'][0] + assert 'Pixels' not in rsp['data'][0]['WellSamples'][0]['Image'] + assert len(wells) == 6 + assert_objects(conn, rsp['data'], wells, dtype='Well', + opts={'load_images': True}) + + def test_well(self, user1, small_plate): + """Test loading a single Well, with or without WellSamples.""" + conn = get_connection(user1) + user_name = conn.getUser().getName() + django_client = self.new_django_client(user_name, user_name) + version = settings.API_VERSIONS[-1] + + small_plate = conn.getObject('Plate', small_plate.id.val) + wells = [w._obj for w in small_plate.listChildren()] + wells.sort(cmp_column_row) + + # plate has 2 wells. First has WellSamples, other doesn't + for well, has_image in zip(wells, [True, False]): + well_url = reverse('api_well', kwargs={'api_version': version, + 'object_id': well.id.val}) + rsp = _get_response_json(django_client, well_url, {}) + # Manually check for image and Pixels loaded + assert ('WellSamples' in rsp) == has_image + if has_image: + assert len(rsp['WellSamples']) == 3 + assert 'Pixels' in rsp['WellSamples'][0]['Image'] + assert_objects(conn, [rsp], [well], dtype='Well', + opts={'load_pixels': True}) From d645be82d5c047d27ed1ce7e3725659eafaac7b3 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 20 Jan 2017 09:41:31 +0000 Subject: [PATCH 16/33] Blitz Well getQueryString() also loads plateAcquisition with wellSample --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index e21489ec826..6fe654e4be8 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -6080,7 +6080,8 @@ def _getQueryString(cls, opts=None): if load_images or load_pixels or load_channels: # NB: Using left outer join, we may get Wells with no Images query += " left outer join fetch obj.wellSamples as wellSamples"\ - " left outer join fetch wellSamples.image as image" + " left outer join fetch wellSamples.image as image"\ + " join fetch wellSamples.plateAcquisition as plateAcquisition" if load_pixels or load_channels: query += ' left outer join fetch image.pixels pixels' \ ' left outer join fetch pixels.pixelsType' From ff4a18769c3a45ed032c8bfb1cb48b3ae6e1ed3c Mon Sep 17 00:00:00 2001 From: William Moore Date: Sun, 22 Jan 2017 23:26:08 +0000 Subject: [PATCH 17/33] flake8 fixes --- .../tools/OmeroWeb/test/integration/test_api_wells.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index 2b167db387b..adfaad329a3 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -55,6 +55,7 @@ def cmp_name_insensitive(x, y): """Case-insensitive name comparator.""" return cmp(unwrap(x.name).lower(), unwrap(y.name).lower()) + def cmp_column_row(x, y): """Sort wells by row, then column.""" sort_by_column = cmp(unwrap(x.column), unwrap(y.column)) @@ -62,6 +63,7 @@ def cmp_column_row(x, y): return cmp(unwrap(x.row), unwrap(y.row)) return sort_by_column + def marshal_objects(objects): """Marshal objects using omero_marshal.""" expected = [] @@ -114,7 +116,7 @@ def assert_objects(conn, json_objects, omero_ids_objects, dtype="Project", o2 = json.loads(json.dumps(o2)) # remove any urls from json (tested elsewhere) remove_urls(o1) - + assert o1 == o2 @@ -224,6 +226,6 @@ def test_well(self, user1, small_plate): assert ('WellSamples' in rsp) == has_image if has_image: assert len(rsp['WellSamples']) == 3 - assert 'Pixels' in rsp['WellSamples'][0]['Image'] + assert 'Pixels' in rsp['WellSamples'][0]['Image'] assert_objects(conn, [rsp], [well], dtype='Well', opts={'load_pixels': True}) From 7a5ca346815f75b1c4a6909b7e361960369c8d31 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 23 Jan 2017 10:09:33 +0000 Subject: [PATCH 18/33] Blitz Well _getQueryString uses 'left outer join' for plateAcquisition --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 6fe654e4be8..05eb64144cb 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -6081,14 +6081,16 @@ def _getQueryString(cls, opts=None): # NB: Using left outer join, we may get Wells with no Images query += " left outer join fetch obj.wellSamples as wellSamples"\ " left outer join fetch wellSamples.image as image"\ - " join fetch wellSamples.plateAcquisition as plateAcquisition" + " left outer join fetch wellSamples.plateAcquisition"\ + " as plateAcquisition" if load_pixels or load_channels: query += ' left outer join fetch image.pixels pixels' \ ' left outer join fetch pixels.pixelsType' if load_channels: query += ' join fetch pixels.channels as channels' \ ' join fetch channels.logicalChannel as logicalChannel' \ - ' left outer join fetch logicalChannel.photometricInterpretation' \ + ' left outer join fetch ' \ + ' logicalChannel.photometricInterpretation' \ ' left outer join fetch logicalChannel.illumination' \ ' left outer join fetch logicalChannel.mode' \ ' left outer join fetch logicalChannel.contrastMethod' From d47446151e2126473abf4957803ef99dcec73f4f Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 23 Jan 2017 13:33:04 +0000 Subject: [PATCH 19/33] Added 'url:well' test to test_api_containers.py --- .../test/integration/test_api_containers.py | 53 +++++++++++++++++-- 1 file changed, 50 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 e8594383ea7..c09e420faca 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -26,8 +26,9 @@ import pytest from omero.gateway import BlitzGateway from omero_marshal import get_encoder -from omero.model import DatasetI, ProjectI, ScreenI, PlateI, ImageI -from omero.rtypes import rstring, unwrap +from omero.model import DatasetI, ProjectI, ScreenI, PlateI, ImageI, \ + WellSampleI, WellI +from omero.rtypes import rstring, unwrap, rint def get_update_service(user): @@ -69,8 +70,20 @@ def marshal_objects(objects): return expected +def add_image_urls(expected, client): + """Add urls to expected Images within Well dict.""" + version = settings.API_VERSIONS[-1] + if 'WellSamples' in expected: + for ws in expected['WellSamples']: + image_id = ws['Image']['@id'] + url = build_url(client, 'api_image', {'api_version': version, + 'object_id': image_id}) + ws['Image']['url:image'] = url + return expected + + def assert_objects(conn, json_objects, omero_ids_objects, dtype="Project", - group='-1', extra=None, opts=None): + group='-1', extra=None, opts=None, client=None): """ Load objects from OMERO, via conn.getObjects(). @@ -98,6 +111,8 @@ def assert_objects(conn, json_objects, omero_ids_objects, dtype="Project", for key in o1.keys(): if key.startswith('url:') and key not in o2: del(o1[key]) + # add urls to any 'Image' in expected 'Wells' dict + add_image_urls(o2, client) assert o1 == o2 @@ -156,6 +171,22 @@ def screen_plates(self, user1): screen = get_update_service(user1).saveAndReturnObject(screen) plate = get_update_service(user1).saveAndReturnObject(plate) + + # Add well to first plate + plates = screen.linkedPlateList() + plates.sort(cmp_name_insensitive) + plate_id = plates[0].id.val + well = WellI() + well.column = rint(0) + well.row = rint(0) + well.plate = PlateI(plate_id, False) + image = self.create_test_image( + size_x=5, size_y=5, session=user1[0].getSession()) + ws = WellSampleI() + ws.image = ImageI(image.id, False) + ws.well = well + well.addWellSample(ws) + well = get_update_service(user1).saveAndReturnObject(well) return screen, plate @pytest.fixture() @@ -340,12 +371,28 @@ def test_spw_urls(self, user1, screen_plates): 'url:plate': build_url(client, 'api_plate', {'api_version': version, 'object_id': p.id.val}), + 'url:wells': build_url(client, 'api_plate_wells', + {'api_version': version, + 'plate_id': p.id.val}) }) assert_objects(conn, plates_json, plates, dtype='Plate', extra=extra) # View single plate rsp = _get_response_json(client, plates_json[0]['url:plate'], {}) assert_objects(conn, [rsp], plates[0:1], dtype='Plate') + # List wells of first plate + wells_url = plates_json[0]['url:wells'] + rsp = _get_response_json(client, wells_url, {}) + wells_json = rsp['data'] + well_id = wells_json[0]['@id'] + extra = [{'url:well': build_url(client, 'api_well', + {'api_version': version, + 'object_id': well_id}) + }] + assert_objects(conn, wells_json, [well_id], dtype='Well', + extra=extra, opts={'load_images': True}, client=client) + + def test_pdi_urls(self, user1, project_datasets): """Test browsing via urls in json /api/->PDI.""" conn = get_connection(user1) From 3bc00961cc6f0390f8539684353501a3bc2ebf50 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 23 Jan 2017 23:53:22 +0000 Subject: [PATCH 20/33] flake8 fixes --- .../tools/OmeroWeb/test/integration/test_api_containers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 2765513fec7..31fca87b5b6 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -386,13 +386,11 @@ def test_spw_urls(self, user1, screen_plates): wells_json = rsp['data'] well_id = wells_json[0]['@id'] extra = [{'url:well': build_url(client, 'api_well', - {'api_version': version, - 'object_id': well_id}) - }] + {'api_version': version, 'object_id': well_id})} + ] assert_objects(conn, wells_json, [well_id], dtype='Well', extra=extra, opts={'load_images': True}, client=client) - def test_pdi_urls(self, user1, project_datasets): """Test browsing via urls in json /api/->PDI.""" conn = get_connection(user1) From ff3711b441774d46cff1765c7073c46be4af7414 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 24 Jan 2017 23:11:35 +0000 Subject: [PATCH 21/33] Blitz fixes for PR #5047 --- .../OmeroPy/src/omero/gateway/__init__.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 05eb64144cb..298a3aa9818 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -141,6 +141,17 @@ def fileread_gen(fin, fsize, bufsize): fin.close() +def getChannelsQuery(): + """Helper for building Query for Images or Wells & Images""" + return (' join fetch pixels.channels as channels' \ + ' join fetch channels.logicalChannel as logicalChannel' \ + ' left outer join fetch ' \ + ' logicalChannel.photometricInterpretation' \ + ' left outer join fetch logicalChannel.illumination' \ + ' left outer join fetch logicalChannel.mode' \ + ' left outer join fetch logicalChannel.contrastMethod') + + class OmeroRestrictionWrapper (object): def canDownload(self): @@ -6084,16 +6095,10 @@ def _getQueryString(cls, opts=None): " left outer join fetch wellSamples.plateAcquisition"\ " as plateAcquisition" if load_pixels or load_channels: - query += ' left outer join fetch image.pixels pixels' \ + query += ' left outer join fetch image.pixels as pixels' \ ' left outer join fetch pixels.pixelsType' if load_channels: - query += ' join fetch pixels.channels as channels' \ - ' join fetch channels.logicalChannel as logicalChannel' \ - ' left outer join fetch ' \ - ' logicalChannel.photometricInterpretation' \ - ' left outer join fetch logicalChannel.illumination' \ - ' left outer join fetch logicalChannel.mode' \ - ' left outer join fetch logicalChannel.contrastMethod' + query += getChannelsQuery() return (query, clauses, params) @@ -7187,12 +7192,7 @@ def _getQueryString(cls, opts=None): query += ' left outer join fetch obj.pixels pixels' \ ' left outer join fetch pixels.pixelsType' if load_channels: - query += ' join fetch pixels.channels as channels' \ - ' join fetch channels.logicalChannel as logicalChannel' \ - ' left outer join fetch logicalChannel.photometricInterpretation' \ - ' left outer join fetch logicalChannel.illumination' \ - ' left outer join fetch logicalChannel.mode' \ - ' left outer join fetch logicalChannel.contrastMethod' + query += getChannelsQuery() if orphaned: clauses.append( """ From e02844b8a834018e95884b9ff4d0702c731b2e61 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 24 Jan 2017 23:12:19 +0000 Subject: [PATCH 22/33] test_api fixes for PR #5047 --- .../test/integration/test_api_containers.py | 42 +++++-------------- .../test/integration/test_api_errors.py | 13 +----- .../test/integration/test_api_images.py | 35 ++-------------- .../test/integration/test_api_wells.py | 40 ++++-------------- 4 files changed, 22 insertions(+), 108 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 31fca87b5b6..994a7b343a7 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -24,32 +24,19 @@ from django.core.urlresolvers import reverse from django.conf import settings import pytest +from test_api_projects import cmp_name_insensitive, get_update_service, \ + get_connection, marshal_objects from omero.gateway import BlitzGateway -from omero_marshal import get_encoder -from omero.model import DatasetI, ProjectI, ScreenI, PlateI, ImageI, \ - WellSampleI, WellI -from omero.rtypes import rstring, unwrap, rint +from omero.model import DatasetI, \ + ImageI, \ + PlateI, \ + ProjectI, \ + ScreenI, \ + WellI, \ + WellSampleI +from omero.rtypes import rstring, rint -def get_update_service(user): - """Get the update_service for the given user's client.""" - return user[0].getSession().getUpdateService() - - -def get_connection(user, group_id=None): - """Get a BlitzGateway connection for the given user's client.""" - connection = BlitzGateway(client_obj=user[0]) - # Refresh the session context - connection.getEventContext() - if group_id is not None: - connection.SERVICE_OPTS.setOmeroGroup(group_id) - return connection - - -def cmp_name_insensitive(x, y): - """Case-insensitive name comparator.""" - return cmp(unwrap(x.name).lower(), unwrap(y.name).lower()) - def build_url(client, url_name, url_kwargs): """Build an absolute url using client response url.""" @@ -61,15 +48,6 @@ def build_url(client, url_name, url_kwargs): return url -def marshal_objects(objects): - """Marshal objects using omero_marshal.""" - expected = [] - for obj in objects: - encoder = get_encoder(obj.__class__) - expected.append(encoder.encode(obj)) - return expected - - def add_image_urls(expected, client): """Add urls to expected Images within Well dict.""" version = settings.API_VERSIONS[-1] diff --git a/components/tools/OmeroWeb/test/integration/test_api_errors.py b/components/tools/OmeroWeb/test/integration/test_api_errors.py index a5de350f209..848529d2a85 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_errors.py +++ b/components/tools/OmeroWeb/test/integration/test_api_errors.py @@ -26,24 +26,13 @@ from django.conf import settings from omero.gateway import BlitzGateway import pytest +from test_api_projects import get_connection from omero.model import ProjectI from omero.rtypes import rstring from omero_marshal import get_encoder, get_decoder, OME_SCHEMA_URL from omero import ValidationException -def get_connection(user, group_id=None): - """ - Get a BlitzGateway connection for the given user's client - """ - connection = BlitzGateway(client_obj=user[0]) - # Refresh the session context - connection.getEventContext() - if group_id is not None: - connection.SERVICE_OPTS.setOmeroGroup(group_id) - return connection - - class TestErrors(IWebTest): """ Tests the response status with various error types diff --git a/components/tools/OmeroWeb/test/integration/test_api_images.py b/components/tools/OmeroWeb/test/integration/test_api_images.py index e0fadf2a9d6..067ccc62528 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_images.py +++ b/components/tools/OmeroWeb/test/integration/test_api_images.py @@ -23,47 +23,18 @@ from django.core.urlresolvers import reverse from django.conf import settings import pytest -from omero.gateway import BlitzGateway -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 omero.rtypes import rstring, unwrap +from omero.rtypes import rstring import json -def get_update_service(user): - """Get the update_service for the given user's client.""" - return user[0].getSession().getUpdateService() - - def get_query_service(user): """Get the query_service for the given user's client.""" return user[0].getSession().getQueryService() -def get_connection(user, group_id=None): - """Get a BlitzGateway connection for the given user's client.""" - connection = BlitzGateway(client_obj=user[0]) - # Refresh the session context - connection.getEventContext() - if group_id is not None: - connection.SERVICE_OPTS.setOmeroGroup(group_id) - return connection - - -def cmp_name_insensitive(x, y): - """Case-insensitive name comparator.""" - return cmp(unwrap(x.name).lower(), unwrap(y.name).lower()) - - -def marshal_objects(objects): - """Marshal objects using omero_marshal.""" - expected = [] - for obj in objects: - encoder = get_encoder(obj.__class__) - expected.append(encoder.encode(obj)) - return expected - - def assert_objects(conn, json_objects, omero_ids_objects, dtype="Project", group='-1', extra=None, opts=None): """ diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index adfaad329a3..415325c6819 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -23,39 +23,24 @@ from django.core.urlresolvers import reverse from django.conf import settings import pytest -from omero.gateway import BlitzGateway +from test_api_projects import get_update_service, \ + get_connection, marshal_objects from omero_marshal import get_encoder -from omero.model import PlateI, WellI, WellSampleI, ImageI, LengthI +from omero.model import ImageI, \ + LengthI, \ + PlateI, \ + WellI, \ + WellSampleI from omero.model.enums import UnitsLength from omero.rtypes import rstring, rint, unwrap import json -def get_update_service(user): - """Get the update_service for the given user's client.""" - return user[0].getSession().getUpdateService() - - def get_query_service(user): """Get the query_service for the given user's client.""" return user[0].getSession().getQueryService() -def get_connection(user, group_id=None): - """Get a BlitzGateway connection for the given user's client.""" - connection = BlitzGateway(client_obj=user[0]) - # Refresh the session context - connection.getEventContext() - if group_id is not None: - connection.SERVICE_OPTS.setOmeroGroup(group_id) - return connection - - -def cmp_name_insensitive(x, y): - """Case-insensitive name comparator.""" - return cmp(unwrap(x.name).lower(), unwrap(y.name).lower()) - - def cmp_column_row(x, y): """Sort wells by row, then column.""" sort_by_column = cmp(unwrap(x.column), unwrap(y.column)) @@ -64,15 +49,6 @@ def cmp_column_row(x, y): return sort_by_column -def marshal_objects(objects): - """Marshal objects using omero_marshal.""" - expected = [] - for obj in objects: - encoder = get_encoder(obj.__class__) - expected.append(encoder.encode(obj)) - return expected - - def remove_urls(marshalled): """Traverse a dict (Well) removing 'url:' values.""" for key, val in marshalled.items(): @@ -144,7 +120,7 @@ def create_plate_wells(self, user1, rows, cols): well.column = rint(col) well.row = rint(row) well.plate = PlateI(plate.id.val, False) - # Only wells in first Column have well-samples etc + # Only wells in first Column have well-samples etc. if col == 0: # Have 3 images/well-samples in these wells for i in range(3): From 84ad329202a8d9294dcef9ef3e7a7eff21399f38 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 24 Jan 2017 23:31:43 +0000 Subject: [PATCH 23/33] Add PlateAcquisition to test_api_wells.py --- .../test/integration/test_api_wells.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index 415325c6819..51f0cdc71e9 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -25,14 +25,14 @@ import pytest from test_api_projects import get_update_service, \ get_connection, marshal_objects -from omero_marshal import get_encoder from omero.model import ImageI, \ LengthI, \ + PlateAcquisitionI, \ PlateI, \ WellI, \ WellSampleI from omero.model.enums import UnitsLength -from omero.rtypes import rstring, rint, unwrap +from omero.rtypes import rstring, rint, unwrap, rtime import json @@ -112,6 +112,16 @@ def create_plate_wells(self, user1, rows, cols): plate.name = rstring('plate') plate = updateService.saveAndReturnObject(plate) + # Single PlateAcquisition for plate + plate_acq = PlateAcquisitionI() + plate_acq.name = rstring('plateacquisition') + plate_acq.description = rstring('plateacquisition_description') + plate_acq.maximumFieldCount = rint(3) + plate_acq.startTime = rtime(1L) + plate_acq.endTime = rtime(2L) + plate_acq.plate = PlateI(plate.id.val, False) + plate_acq = updateService.saveAndReturnObject(plate_acq) + # Create Wells for plate for row in range(rows): for col in range(cols): @@ -131,6 +141,8 @@ def create_plate_wells(self, user1, rows, cols): ws.well = well ws.posX = LengthI(i * 10, UnitsLength.REFERENCEFRAME) ws.posY = LengthI(i, UnitsLength.REFERENCEFRAME) + ws.setPlateAcquisition( + PlateAcquisitionI(plate_acq.id.val, False)) well.addWellSample(ws) updateService.saveObject(well) return plate @@ -176,8 +188,10 @@ def test_plate_wells(self, user1, small_plate, bigger_plate): payload = {'plate': bigger_plate.id} rsp = _get_response_json(django_client, wells_url, payload) # Manual check that Images are loaded but Pixels are not - assert 'Image' in rsp['data'][0]['WellSamples'][0] - assert 'Pixels' not in rsp['data'][0]['WellSamples'][0]['Image'] + well_sample = rsp['data'][0]['WellSamples'][0] + assert 'Image' in well_sample + assert 'PlateAcquisition' in well_sample + assert 'Pixels' not in well_sample['Image'] assert len(wells) == 6 assert_objects(conn, rsp['data'], wells, dtype='Well', opts={'load_images': True}) From 7664049e796f80042586ea20876d6465be3b72f4 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 24 Jan 2017 23:49:58 +0000 Subject: [PATCH 24/33] flake* and pep8 fixes for test_api_errors.py --- .../test/integration/test_api_containers.py | 1 - .../test/integration/test_api_errors.py | 42 +++++++------------ 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 994a7b343a7..a8d011dcb40 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -37,7 +37,6 @@ from omero.rtypes import rstring, rint - def build_url(client, url_name, url_kwargs): """Build an absolute url using client response url.""" response = client.request() diff --git a/components/tools/OmeroWeb/test/integration/test_api_errors.py b/components/tools/OmeroWeb/test/integration/test_api_errors.py index 848529d2a85..ba702fe3718 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_errors.py +++ b/components/tools/OmeroWeb/test/integration/test_api_errors.py @@ -17,14 +17,11 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -""" -Tests querying & editing Projects with webgateway json api -""" +"""Tests querying & editing Projects with webgateway json api.""" from omeroweb.testlib import IWebTest, _csrf_post_json, _csrf_put_json from django.core.urlresolvers import reverse from django.conf import settings -from omero.gateway import BlitzGateway import pytest from test_api_projects import get_connection from omero.model import ProjectI @@ -34,32 +31,30 @@ class TestErrors(IWebTest): - """ - Tests the response status with various error types - """ + """Tests the response status with various error types.""" # Create a read-annotate group @pytest.fixture(scope='function') def group_A(self): - """Returns a new read-only group.""" + """Return a new read-only group.""" return self.new_group(perms='rwra--') # Create a read-only group @pytest.fixture(scope='function') def group_B(self): - """Returns a new read-only group.""" + """Return a new read-only group.""" return self.new_group(perms='rwr---') # Create users in the read-only group @pytest.fixture() def user_A(self, group_A, group_B): - """Returns a new user in the group_A group and also add to group_B""" + """Return a new user in the group_A group and also add to group_B.""" user = self.new_client_and_user(group=group_A) self.add_groups(user[1], [group_B]) return user def test_save_post_no_id(self): - """ If POST to /save/ data shouldn't contain @id """ + """If POST to /save/ data shouldn't contain @id.""" django_client = self.django_root_client version = settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) @@ -71,7 +66,7 @@ def test_save_post_no_id(self): "Object has '@id' attribute. Use PUT to update objects") def test_save_put_id(self): - """ If PUT to /save/ to update, data must contain @id """ + """If PUT to /save/ to update, data must contain @id.""" django_client = self.django_root_client version = settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) @@ -82,7 +77,7 @@ def test_save_put_id(self): "No '@id' attribute. Use POST to create new objects") def test_marshal_type(self): - """ If no decoder found for @type, get suitable message""" + """If no decoder found for @type, get suitable message.""" django_client = self.django_root_client version = settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) @@ -95,6 +90,7 @@ def test_marshal_type(self): 'No decoder found for type: %s' % objType) def test_marshal_validation(self): + """Test we get expected error with invalid @type in json.""" django_client = self.django_root_client version = settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) @@ -109,6 +105,7 @@ def test_marshal_validation(self): 'Traceback (most recent call last):') def test_security_violation(self, group_B, user_A): + """Test saving to incorrect group.""" conn = get_connection(user_A) groupAid = conn.getEventContext().groupId userName = conn.getUser().getName() @@ -133,21 +130,8 @@ def test_security_violation(self, group_B, user_A): assert rsp['stacktrace'].startswith( 'Traceback (most recent call last):') - def test_marshal_exception(self): - django_client = self.django_root_client - version = settings.API_VERSIONS[-1] - save_url = reverse('api_save', kwargs={'api_version': version}) - payload = {'Name': 'test_type_error', - '@type': OME_SCHEMA_URL + '#Project', - 'omero:details': {'@type': 'foo'}} - rsp = _csrf_post_json(django_client, save_url, payload, - status_code=400) - assert (rsp['message'] == - "Error in decode of json data by omero_marshal") - assert rsp['stacktrace'].startswith( - 'Traceback (most recent call last):') - def test_validation_exception(self, user_A): + """Test handling when we try to save something invalid.""" conn = get_connection(user_A) group = conn.getEventContext().groupId userName = conn.getUser().getName() @@ -176,7 +160,9 @@ def test_validation_exception(self, user_A): def test_project_validation(self, user_A): """ - This test illustrates the ValidationException we see when + Test to demonstrate details bug on encode->deconde. + + Test illustrates the ValidationException we see when Project is encoded to dict then decoded back to Project and saved. No exception is seen if the original Project is simply From 2314276644822456875a3f3b768799236cb90da3 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 24 Jan 2017 23:52:17 +0000 Subject: [PATCH 25/33] Blitz flake8 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 298a3aa9818..d5fcb890de8 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -143,12 +143,12 @@ def fileread_gen(fin, fsize, bufsize): def getChannelsQuery(): """Helper for building Query for Images or Wells & Images""" - return (' join fetch pixels.channels as channels' \ - ' join fetch channels.logicalChannel as logicalChannel' \ - ' left outer join fetch ' \ - ' logicalChannel.photometricInterpretation' \ - ' left outer join fetch logicalChannel.illumination' \ - ' left outer join fetch logicalChannel.mode' \ + return (' join fetch pixels.channels as channels' + ' join fetch channels.logicalChannel as logicalChannel' + ' left outer join fetch ' + ' logicalChannel.photometricInterpretation' + ' left outer join fetch logicalChannel.illumination' + ' left outer join fetch logicalChannel.mode' ' left outer join fetch logicalChannel.contrastMethod') From 256ce26abbcaefee2c620cb3e00a3b4e0f9c5df3 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 25 Jan 2017 00:12:04 +0000 Subject: [PATCH 26/33] flake8 fix --- components/tools/OmeroWeb/test/integration/test_api_wells.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index 51f0cdc71e9..39346320889 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -142,7 +142,7 @@ def create_plate_wells(self, user1, rows, cols): ws.posX = LengthI(i * 10, UnitsLength.REFERENCEFRAME) ws.posY = LengthI(i, UnitsLength.REFERENCEFRAME) ws.setPlateAcquisition( - PlateAcquisitionI(plate_acq.id.val, False)) + PlateAcquisitionI(plate_acq.id.val, False)) well.addWellSample(ws) updateService.saveObject(well) return plate From 1c0dbc6acd570d64f69e4d6a9a565d6ae509e84a Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 25 Jan 2017 10:24:10 +0000 Subject: [PATCH 27/33] test_plate_wells() tests plates with & without plate_acquisition --- .../test/integration/test_api_wells.py | 59 ++++++++++--------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index 39346320889..4d0e8f776a5 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -105,7 +105,7 @@ def user1(self): group = self.new_group(perms='rwra--') return self.new_client_and_user(group=group) - def create_plate_wells(self, user1, rows, cols): + def create_plate_wells(self, user1, rows, cols, with_plate_acq=True): """Return Plate with Wells.""" updateService = get_update_service(user1) plate = PlateI() @@ -113,14 +113,15 @@ def create_plate_wells(self, user1, rows, cols): plate = updateService.saveAndReturnObject(plate) # Single PlateAcquisition for plate - plate_acq = PlateAcquisitionI() - plate_acq.name = rstring('plateacquisition') - plate_acq.description = rstring('plateacquisition_description') - plate_acq.maximumFieldCount = rint(3) - plate_acq.startTime = rtime(1L) - plate_acq.endTime = rtime(2L) - plate_acq.plate = PlateI(plate.id.val, False) - plate_acq = updateService.saveAndReturnObject(plate_acq) + if with_plate_acq: + plate_acq = PlateAcquisitionI() + plate_acq.name = rstring('plateacquisition') + plate_acq.description = rstring('plateacquisition_description') + plate_acq.maximumFieldCount = rint(3) + plate_acq.startTime = rtime(1L) + plate_acq.endTime = rtime(2L) + plate_acq.plate = PlateI(plate.id.val, False) + plate_acq = updateService.saveAndReturnObject(plate_acq) # Create Wells for plate for row in range(rows): @@ -141,8 +142,9 @@ def create_plate_wells(self, user1, rows, cols): ws.well = well ws.posX = LengthI(i * 10, UnitsLength.REFERENCEFRAME) ws.posY = LengthI(i, UnitsLength.REFERENCEFRAME) - ws.setPlateAcquisition( - PlateAcquisitionI(plate_acq.id.val, False)) + if with_plate_acq: + ws.setPlateAcquisition( + PlateAcquisitionI(plate_acq.id.val, False)) well.addWellSample(ws) updateService.saveObject(well) return plate @@ -154,7 +156,7 @@ def small_plate(self, user1): Two wells are created, but only the first has any Images (3). """ - return self.create_plate_wells(user1, 1, 2) + return self.create_plate_wells(user1, 1, 2, with_plate_acq=False) @pytest.fixture() def bigger_plate(self, user1): @@ -173,11 +175,6 @@ def test_plate_wells(self, user1, small_plate, bigger_plate): django_client = self.new_django_client(user_name, user_name) version = settings.API_VERSIONS[-1] - # Use Blitz Plates for listing Wells etc. - bigger_plate = conn.getObject('Plate', bigger_plate.id.val) - wells = [w._obj for w in bigger_plate.listChildren()] - wells.sort(cmp_column_row) - wells_url = reverse('api_wells', kwargs={'api_version': version}) # List ALL Wells in both plates @@ -185,16 +182,24 @@ def test_plate_wells(self, user1, small_plate, bigger_plate): assert len(rsp['data']) == 8 # Filter Wells by Plate - payload = {'plate': bigger_plate.id} - rsp = _get_response_json(django_client, wells_url, payload) - # Manual check that Images are loaded but Pixels are not - well_sample = rsp['data'][0]['WellSamples'][0] - assert 'Image' in well_sample - assert 'PlateAcquisition' in well_sample - assert 'Pixels' not in well_sample['Image'] - assert len(wells) == 6 - assert_objects(conn, rsp['data'], wells, dtype='Well', - opts={'load_images': True}) + for plate, with_acq, well_count in zip([small_plate, bigger_plate], + [False, True], + [2, 6]): + # Use Blitz Plates for listing Wells etc. + plate_wrapper = conn.getObject('Plate', plate.id.val) + wells = [w._obj for w in plate_wrapper.listChildren()] + wells.sort(cmp_column_row) + payload = {'plate': plate.id.val} + 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 + well_sample = rsp['data'][0]['WellSamples'][0] + assert 'Image' in well_sample + assert ('PlateAcquisition' in well_sample) == with_acq + assert 'Pixels' not in well_sample['Image'] + + assert_objects(conn, rsp['data'], wells, dtype='Well', + opts={'load_images': True}) def test_well(self, user1, small_plate): """Test loading a single Well, with or without WellSamples.""" From 8797960e36b6498fc27d3eef045cbf4aeffa94f5 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 25 Jan 2017 10:43:29 +0000 Subject: [PATCH 28/33] Blitz getPixelsQuery() helper --- .../tools/OmeroPy/src/omero/gateway/__init__.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index d5fcb890de8..a7aef44190b 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -141,6 +141,11 @@ def fileread_gen(fin, fsize, bufsize): fin.close() +def getPixelsQuery(imageName): + """Helper for building Query for Images or Wells & Images""" + return (' left outer join fetch %s.pixels as pixels' + ' left outer join fetch pixels.pixelsType' % imageName) + def getChannelsQuery(): """Helper for building Query for Images or Wells & Images""" return (' join fetch pixels.channels as channels' @@ -6095,8 +6100,7 @@ def _getQueryString(cls, opts=None): " left outer join fetch wellSamples.plateAcquisition"\ " as plateAcquisition" if load_pixels or load_channels: - query += ' left outer join fetch image.pixels as pixels' \ - ' left outer join fetch pixels.pixelsType' + query += getPixelsQuery("image") if load_channels: query += getChannelsQuery() @@ -7189,8 +7193,7 @@ def _getQueryString(cls, opts=None): orphaned = opts.get('orphaned') if load_pixels or load_channels: # We use 'left outer join', since we still want images if no pixels - query += ' left outer join fetch obj.pixels pixels' \ - ' left outer join fetch pixels.pixelsType' + query += getPixelsQuery("obj") if load_channels: query += getChannelsQuery() if orphaned: From 3a90c5cc5a2ed8f23b80a61d7e82ff972f42f1ad Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 25 Jan 2017 10:44:05 +0000 Subject: [PATCH 29/33] Typos in web api tests --- .../tools/OmeroWeb/test/integration/test_api_errors.py | 4 ++-- .../tools/OmeroWeb/test/integration/test_api_wells.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_errors.py b/components/tools/OmeroWeb/test/integration/test_api_errors.py index ba702fe3718..7c9a5cfdaa0 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_errors.py +++ b/components/tools/OmeroWeb/test/integration/test_api_errors.py @@ -90,7 +90,7 @@ def test_marshal_type(self): 'No decoder found for type: %s' % objType) def test_marshal_validation(self): - """Test we get expected error with invalid @type in json.""" + """Test that we get expected error with invalid @type in json.""" django_client = self.django_root_client version = settings.API_VERSIONS[-1] save_url = reverse('api_save', kwargs={'api_version': version}) @@ -160,7 +160,7 @@ def test_validation_exception(self, user_A): def test_project_validation(self, user_A): """ - Test to demonstrate details bug on encode->deconde. + Test to demonstrate details bug on encode->decode. Test illustrates the ValidationException we see when Project is encoded to dict then decoded back to Project diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index 4d0e8f776a5..b301a32786c 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -55,9 +55,9 @@ def remove_urls(marshalled): if key.startswith('url:'): del(marshalled[key]) # We only traverse paths where we know urls are - elif key == 'Image': # isinstance(val, dict): + elif key == 'Image': remove_urls(val) - elif key == 'WellSamples': # isinstance(val, list): + elif key == 'WellSamples': for i in val: remove_urls(i) @@ -87,7 +87,7 @@ def assert_objects(conn, json_objects, omero_ids_objects, dtype="Project", for i, o1, o2 in zip(range(len(expected)), json_objects, expected): if extra is not None and i < len(extra): o2.update(extra[i]) - # dumping to json and loading (same as test data) means that + # We dump to json and re-load (same as test data). This means that # unicode has been handled in same way, e.g. Pixel size symbols. o2 = json.loads(json.dumps(o2)) # remove any urls from json (tested elsewhere) From 78a5a94cf93abe23747d1afff5d73a1207413cdb Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 25 Jan 2017 11:03:02 +0000 Subject: [PATCH 30/33] Blitz flake8 fix --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index a7aef44190b..ea682010700 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -146,6 +146,7 @@ def getPixelsQuery(imageName): return (' left outer join fetch %s.pixels as pixels' ' left outer join fetch pixels.pixelsType' % imageName) + def getChannelsQuery(): """Helper for building Query for Images or Wells & Images""" return (' join fetch pixels.channels as channels' From 852c3eda97518885bf8e83e2b5ce886013558efe Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 27 Jan 2017 12:55:23 +0000 Subject: [PATCH 31/33] Temp use chris' well-wellsample-image branch of omero-marshal --- components/tools/OmeroWeb/requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/requirements-common.txt b/components/tools/OmeroWeb/requirements-common.txt index bd48a1d03c4..e4101ba2902 100644 --- a/components/tools/OmeroWeb/requirements-common.txt +++ b/components/tools/OmeroWeb/requirements-common.txt @@ -10,4 +10,4 @@ django-pipeline==1.3.20 # Until Screens are supported in release, we need to use master branch # See https://github.com/openmicroscopy/openmicroscopy/pull/5006 # omero-marshal==0.4.1 -git+git://github.com/openmicroscopy/omero-marshal.git@master#egg=omero-marshal +git+git://github.com/chris-allan/omero-marshal.git@well-wellsample-image#egg=omero-marshal From 024a1887698313d266eec0a076249a98ccaa0a12 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 30 Jan 2017 23:40:41 +0000 Subject: [PATCH 32/33] Don't use urls={} in ApiView.add_data() --- .../tools/OmeroWeb/omeroweb/api/views.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index efe584b8a3d..e2530f9555a 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -123,7 +123,7 @@ def dispatch(self, *args, **kwargs): """Wrap other methods to add decorators.""" return super(ApiView, self).dispatch(*args, **kwargs) - def add_data(self, marshalled, request, urls={}, **kwargs): + def add_data(self, marshalled, request, urls=None, **kwargs): """ Post-process marshalled object to add any extra data. @@ -133,15 +133,16 @@ def add_data(self, marshalled, request, urls={}, **kwargs): """ object_id = marshalled['@id'] version = kwargs['api_version'] - for key, args in urls.items(): - name = args['name'] - kwargs = args['kwargs'].copy() - # If kwargs has 'OBJECT_ID' placeholder, we replace with id - for k, v in kwargs.items(): - if v == 'OBJECT_ID': - kwargs[k] = object_id - url = build_url(request, name, version, **kwargs) - marshalled[key] = url + if urls is not None: + for key, args in urls.items(): + name = args['name'] + kwargs = args['kwargs'].copy() + # If kwargs has 'OBJECT_ID' placeholder, we replace with id + for k, v in kwargs.items(): + if v == 'OBJECT_ID': + kwargs[k] = object_id + url = build_url(request, name, version, **kwargs) + marshalled[key] = url return marshalled @@ -255,7 +256,7 @@ def get_opts(self, request): opts['load_pixels'] = True return opts - def add_data(self, marshalled, request, urls={}, **kwargs): + def add_data(self, marshalled, request, urls=None, **kwargs): """Add 'url:image' to any 'Image' in 'WellSamples'.""" marshalled = super(WellView, self).add_data(marshalled, request, urls=urls, **kwargs) @@ -461,7 +462,7 @@ def get_opts(self, request, **kwargs): opts['load_images'] = True return opts - def add_data(self, marshalled, request, urls={}, **kwargs): + def add_data(self, marshalled, request, urls=None, **kwargs): """Add 'url:image' to any 'Image' in 'WellSamples'.""" marshalled = super(WellsView, self).add_data(marshalled, request, urls=urls, **kwargs) From 8eb3ad2a15f7dd1241de62911c844fdab1f28670 Mon Sep 17 00:00:00 2001 From: William Moore Date: Sun, 12 Feb 2017 21:53:46 +0000 Subject: [PATCH 33/33] Revert "Temp use chris' well-wellsample-image branch of omero-marshal" This reverts commit 852c3eda97518885bf8e83e2b5ce886013558efe. --- components/tools/OmeroWeb/requirements-common.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/requirements-common.txt b/components/tools/OmeroWeb/requirements-common.txt index e4101ba2902..bd48a1d03c4 100644 --- a/components/tools/OmeroWeb/requirements-common.txt +++ b/components/tools/OmeroWeb/requirements-common.txt @@ -10,4 +10,4 @@ django-pipeline==1.3.20 # Until Screens are supported in release, we need to use master branch # See https://github.com/openmicroscopy/openmicroscopy/pull/5006 # omero-marshal==0.4.1 -git+git://github.com/chris-allan/omero-marshal.git@well-wellsample-image#egg=omero-marshal +git+git://github.com/openmicroscopy/omero-marshal.git@master#egg=omero-marshal