From 3e1ea545732ba7c4bcd4ae58c08035648c272ec8 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 30 Jun 2026 16:21:45 +0100 Subject: [PATCH 01/15] Add test for conn.getObjects('Annotations', opts={...}) --- .../gatewaytest/test_get_objects.py | 90 ++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 5fe206f217f..f2739d6e52e 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -20,10 +20,11 @@ from omero.gateway.scripts import dbhelpers from omero.rtypes import wrap, rlong -from omero.testlib import ITest +from omero.testlib import ITest, rstring from omero.gateway import BlitzGateway, KNOWN_WRAPPERS, DatasetWrapper, \ ProjectWrapper, ImageWrapper, ScreenWrapper, PlateWrapper from omero.model import DatasetI, \ + ProjectI, \ ImageI, \ PlateI, \ ScreenI, \ @@ -635,6 +636,93 @@ def testGetAnnotations(self, gatewaywrapper, author_testimg_tiny): obj.removeAnnotations(ns) dataset.unlinkAnnotations(ns_tag) # unlink tag obj.removeAnnotations(ns_tag) # delete tag + + + def testGetObjectsAnnotation(self): + + # create new group and user - don't rely on existing test user/group + + group = self.new_group(perms='rwra--') + client, user = self.new_client_and_user(group=group) + + conn = BlitzGateway(client_obj=client) + update = conn.getUpdateService() + + ns = "test_get_objects_annotation_comment" + ns_tag = "test_get_objects_annotation_tag" + + + def create_dataset_with_annotations(name, dtype="Dataset"): + if dtype == "Dataset": + obj = DatasetI() + obj.name = rstring(name) + obj = update.saveAndReturnObject(obj) + wrapper = omero.gateway.DatasetWrapper(conn, obj) + elif dtype == "Project": + obj = ProjectI() + obj.name = rstring(name) + obj = update.saveAndReturnObject(obj) + wrapper = omero.gateway.ProjectWrapper(conn, obj) + + # create Comment + ann = omero.gateway.CommentAnnotationWrapper(conn) + ann.setNs(ns) + ann.setValue("Test Comment: " + name) + ann = wrapper.linkAnnotation(ann) + # create Tag + tag = omero.gateway.TagAnnotationWrapper(conn) + tag.setNs(ns_tag) + tag.setValue("Test Tag: " + name) + wrapper.linkAnnotation(tag) + + return wrapper + + dataset1 = create_dataset_with_annotations("Dataset 1") + dataset2 = create_dataset_with_annotations("Dataset 2") + project1 = create_dataset_with_annotations("Project 1", dtype="Project") + + # get all annotations on one Dataset + annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset", 'parent_ids': [dataset1.id]}) + assert len(list(annGen)) == 2 + + # get all annotations on two Datasets + annGen = conn.getObjects("Annotation", + opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, dataset2.id]}) + assert len(list(annGen)) == 4 + + # get all annotations on ALL Datasets + annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset"}) + assert len(list(annGen)) == 4 + + # No annotations on PlateAcquisition (none created) + annGen = conn.getObjects("Annotation", opts={'parent_type': "plateacquisition"}) + assert len(list(annGen)) == 0 + + # get ALL annotations + anns = list(conn.getObjects("Annotation")) + assert len(anns) == 6 + + # We only want Tags on the two Datasets + annGen = conn.getObjects("Annotation", + opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, dataset2.id], + 'ann_type': "tag"}) + assert len(list(annGen)) == 2 + + # ALL Tags + annGen = conn.getObjects("Annotation", opts={'ann_type': "tag"}) + assert len(list(annGen)) == 3 + + # filter by namespace + annGen = conn.getObjects("Annotation", opts={'ns': ns}) + assert len(list(annGen)) == 3 + + # filter by namespace and type + annGen = conn.getObjects("Annotation", opts={'ns': ns, 'ann_type': "comment"}) + assert len(list(annGen)) == 3 + annGen = conn.getObjects("Annotation", opts={'ns': ns, 'ann_type': "tag"}) + assert len(list(annGen)) == 0 + + def testGetImage(self, gatewaywrapper, author_testimg_tiny): testImage = author_testimg_tiny From 102e3a288339d62ee60214780b830d40b42a9138 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 1 Jul 2026 09:57:20 +0100 Subject: [PATCH 02/15] Test getObjects() with FileAnnotations --- .../gatewaytest/test_get_objects.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index f2739d6e52e..6a0e23cb41f 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -674,6 +674,18 @@ def create_dataset_with_annotations(name, dtype="Dataset"): tag.setNs(ns_tag) tag.setValue("Test Tag: " + name) wrapper.linkAnnotation(tag) + # create FileAnnotation + fileAnn = omero.gateway.FileAnnotationWrapper(conn) + fileObj = omero.model.OriginalFileI() + fileObj = omero.gateway.OriginalFileWrapper(conn, fileObj) + fileObj.setName(omero.rtypes.rstring('fileName')) + fileObj.setPath(omero.rtypes.rstring('path/to/file')) + fileObj.setHash(omero.rtypes.rstring('a')) + fileObj.setSize(omero.rtypes.rlong(0)) + fileObj.save() + fileAnn.setFile(fileObj) + fileAnn.save() + wrapper.linkAnnotation(fileAnn) return wrapper @@ -683,16 +695,16 @@ def create_dataset_with_annotations(name, dtype="Dataset"): # get all annotations on one Dataset annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset", 'parent_ids': [dataset1.id]}) - assert len(list(annGen)) == 2 + assert len(list(annGen)) == 3 # get all annotations on two Datasets annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, dataset2.id]}) - assert len(list(annGen)) == 4 + assert len(list(annGen)) == 6 # get all annotations on ALL Datasets annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset"}) - assert len(list(annGen)) == 4 + assert len(list(annGen)) == 6 # No annotations on PlateAcquisition (none created) annGen = conn.getObjects("Annotation", opts={'parent_type': "plateacquisition"}) @@ -700,7 +712,7 @@ def create_dataset_with_annotations(name, dtype="Dataset"): # get ALL annotations anns = list(conn.getObjects("Annotation")) - assert len(anns) == 6 + assert len(anns) == 9 # We only want Tags on the two Datasets annGen = conn.getObjects("Annotation", @@ -722,6 +734,14 @@ def create_dataset_with_annotations(name, dtype="Dataset"): annGen = conn.getObjects("Annotation", opts={'ns': ns, 'ann_type': "tag"}) assert len(list(annGen)) == 0 + # test "file" annotation is loaded + annGen = conn.getObjects("Annotation", + opts={'parent_type': "dataset", + 'parent_ids': [dataset1.id, dataset2.id], + 'ann_type': "file"}) + for ann in annGen: + assert ann.getFile().getName() == 'fileName' + assert ann.getFile().getPath() == 'path/to/file' def testGetImage(self, gatewaywrapper, author_testimg_tiny): From 5fa410d629f5ab56a7d5b2ed7d8d265699ce4de5 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 1 Jul 2026 14:37:28 +0100 Subject: [PATCH 03/15] flake8 fixes --- .../gatewaytest/test_get_objects.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 6a0e23cb41f..869b7a40db3 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -636,7 +636,6 @@ def testGetAnnotations(self, gatewaywrapper, author_testimg_tiny): obj.removeAnnotations(ns) dataset.unlinkAnnotations(ns_tag) # unlink tag obj.removeAnnotations(ns_tag) # delete tag - def testGetObjectsAnnotation(self): @@ -651,7 +650,6 @@ def testGetObjectsAnnotation(self): ns = "test_get_objects_annotation_comment" ns_tag = "test_get_objects_annotation_tag" - def create_dataset_with_annotations(name, dtype="Dataset"): if dtype == "Dataset": obj = DatasetI() @@ -691,15 +689,19 @@ def create_dataset_with_annotations(name, dtype="Dataset"): dataset1 = create_dataset_with_annotations("Dataset 1") dataset2 = create_dataset_with_annotations("Dataset 2") - project1 = create_dataset_with_annotations("Project 1", dtype="Project") + create_dataset_with_annotations("Project 1", dtype="Project") # get all annotations on one Dataset - annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset", 'parent_ids': [dataset1.id]}) + annGen = conn.getObjects("Annotation", + opts={'parent_type': "dataset", + 'parent_ids': [dataset1.id]}) assert len(list(annGen)) == 3 # get all annotations on two Datasets annGen = conn.getObjects("Annotation", - opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, dataset2.id]}) + opts={'parent_type': "dataset", + 'parent_ids': [dataset1.id, + dataset2.id]}) assert len(list(annGen)) == 6 # get all annotations on ALL Datasets @@ -707,16 +709,19 @@ def create_dataset_with_annotations(name, dtype="Dataset"): assert len(list(annGen)) == 6 # No annotations on PlateAcquisition (none created) - annGen = conn.getObjects("Annotation", opts={'parent_type': "plateacquisition"}) + annGen = conn.getObjects("Annotation", + opts={'parent_type': "plateacquisition"}) assert len(list(annGen)) == 0 - # get ALL annotations + # get ALL annotations anns = list(conn.getObjects("Annotation")) assert len(anns) == 9 # We only want Tags on the two Datasets annGen = conn.getObjects("Annotation", - opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, dataset2.id], + opts={'parent_type': "dataset", + 'parent_ids': [dataset1.id, + dataset2.id], 'ann_type': "tag"}) assert len(list(annGen)) == 2 @@ -729,21 +734,23 @@ def create_dataset_with_annotations(name, dtype="Dataset"): assert len(list(annGen)) == 3 # filter by namespace and type - annGen = conn.getObjects("Annotation", opts={'ns': ns, 'ann_type': "comment"}) + annGen = conn.getObjects("Annotation", + opts={'ns': ns, 'ann_type': "comment"}) assert len(list(annGen)) == 3 - annGen = conn.getObjects("Annotation", opts={'ns': ns, 'ann_type': "tag"}) + annGen = conn.getObjects("Annotation", + opts={'ns': ns, 'ann_type': "tag"}) assert len(list(annGen)) == 0 - # test "file" annotation is loaded + # test File Annotation is loaded annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset", - 'parent_ids': [dataset1.id, dataset2.id], + 'parent_ids': [dataset1.id, + dataset2.id], 'ann_type': "file"}) for ann in annGen: assert ann.getFile().getName() == 'fileName' assert ann.getFile().getPath() == 'path/to/file' - def testGetImage(self, gatewaywrapper, author_testimg_tiny): testImage = author_testimg_tiny # This should return image wrapper From d7a8b2b5a976b8f179a63dc806d9f6c5ef1c1fab Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 1 Jul 2026 15:54:28 +0100 Subject: [PATCH 04/15] Test ann Files loaded without ann_type:file --- .../OmeroPy/test/integration/gatewaytest/test_get_objects.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 869b7a40db3..ba1024bbd01 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -649,6 +649,7 @@ def testGetObjectsAnnotation(self): ns = "test_get_objects_annotation_comment" ns_tag = "test_get_objects_annotation_tag" + ns_file = "test_get_objects_annotation_file" def create_dataset_with_annotations(name, dtype="Dataset"): if dtype == "Dataset": @@ -682,6 +683,7 @@ def create_dataset_with_annotations(name, dtype="Dataset"): fileObj.setSize(omero.rtypes.rlong(0)) fileObj.save() fileAnn.setFile(fileObj) + fileAnn.setNs(ns_file) fileAnn.save() wrapper.linkAnnotation(fileAnn) @@ -746,7 +748,7 @@ def create_dataset_with_annotations(name, dtype="Dataset"): opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, dataset2.id], - 'ann_type': "file"}) + 'ns': ns_file}) for ann in annGen: assert ann.getFile().getName() == 'fileName' assert ann.getFile().getPath() == 'path/to/file' From c34a89dd2fc181999bd6aad4aaeef7f1e038a3b7 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 22 Jul 2026 10:43:29 +0100 Subject: [PATCH 05/15] Ignore any 'user' group annotations --- .../OmeroPy/test/integration/gatewaytest/test_get_objects.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index ba1024bbd01..3ce7b7cc883 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -716,8 +716,9 @@ def create_dataset_with_annotations(name, dtype="Dataset"): assert len(list(annGen)) == 0 # get ALL annotations + # NB: this can include 'user' group annotations from other tests anns = list(conn.getObjects("Annotation")) - assert len(anns) == 9 + assert len(anns) >= 9 # We only want Tags on the two Datasets annGen = conn.getObjects("Annotation", From 51c71e7d4defebfe31217091514dd5e3f0f16677 Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 30 Jul 2026 17:42:07 +0100 Subject: [PATCH 06/15] testGetObjectsAnnotation() tests all annotation types --- .../gatewaytest/test_get_objects.py | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 3ce7b7cc883..fbea5aa3c6b 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -647,8 +647,12 @@ def testGetObjectsAnnotation(self): conn = BlitzGateway(client_obj=client) update = conn.getUpdateService() - ns = "test_get_objects_annotation_comment" - ns_tag = "test_get_objects_annotation_tag" + ann_types = ["CommentAnnotation", "TagAnnotation", + "LongAnnotation", "XmlAnnotation", + "DoubleAnnotation", "BooleanAnnotation", + "TimestampAnnotation", "TermAnnotation"] + ns = "test_get_objects_namespace" + ns_map = "test_get_objects_map_namespace" ns_file = "test_get_objects_annotation_file" def create_dataset_with_annotations(name, dtype="Dataset"): @@ -663,16 +667,27 @@ def create_dataset_with_annotations(name, dtype="Dataset"): obj = update.saveAndReturnObject(obj) wrapper = omero.gateway.ProjectWrapper(conn, obj) - # create Comment - ann = omero.gateway.CommentAnnotationWrapper(conn) - ann.setNs(ns) - ann.setValue("Test Comment: " + name) - ann = wrapper.linkAnnotation(ann) - # create Tag - tag = omero.gateway.TagAnnotationWrapper(conn) - tag.setNs(ns_tag) - tag.setValue("Test Tag: " + name) - wrapper.linkAnnotation(tag) + # Create total of 10 annotations on the object... + # create Comment, Tag, Xml, etc + for ann_type in ann_types: + ann = getattr(omero.gateway, ann_type + "Wrapper")(conn) + ann.setNs(ns) + if ann_type in ("LongAnnotation", "DoubleAnnotation", "TimestampAnnotation"): + ann.setValue(100.0) + elif ann_type == "BooleanAnnotation": + ann.setValue(True) + else: + ann.setValue("Test %s: %s" % (ann_type, name)) + wrapper.linkAnnotation(ann) + + # create MapAnnotation + mapAnn = omero.gateway.MapAnnotationWrapper(conn) + mapAnn.setNs(ns) + mapAnn.setValue([("key1", "value1"), ("key2", "value2")]) + mapAnn.setNs(ns_map) + mapAnn.save() + wrapper.linkAnnotation(mapAnn) + # create FileAnnotation fileAnn = omero.gateway.FileAnnotationWrapper(conn) fileObj = omero.model.OriginalFileI() @@ -697,18 +712,18 @@ def create_dataset_with_annotations(name, dtype="Dataset"): annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset", 'parent_ids': [dataset1.id]}) - assert len(list(annGen)) == 3 + assert len(list(annGen)) == 10 # get all annotations on two Datasets annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, dataset2.id]}) - assert len(list(annGen)) == 6 + assert len(list(annGen)) == 20 # get all annotations on ALL Datasets annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset"}) - assert len(list(annGen)) == 6 + assert len(list(annGen)) == 20 # No annotations on PlateAcquisition (none created) annGen = conn.getObjects("Annotation", @@ -718,31 +733,31 @@ def create_dataset_with_annotations(name, dtype="Dataset"): # get ALL annotations # NB: this can include 'user' group annotations from other tests anns = list(conn.getObjects("Annotation")) - assert len(anns) >= 9 + assert len(anns) >= 30 # We only want Tags on the two Datasets - annGen = conn.getObjects("Annotation", + annGen = conn.getObjects("TagAnnotation", opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, - dataset2.id], - 'ann_type': "tag"}) + dataset2.id]}) assert len(list(annGen)) == 2 # ALL Tags - annGen = conn.getObjects("Annotation", opts={'ann_type': "tag"}) + annGen = conn.getObjects("TagAnnotation") assert len(list(annGen)) == 3 # filter by namespace annGen = conn.getObjects("Annotation", opts={'ns': ns}) - assert len(list(annGen)) == 3 + assert len(list(annGen)) == 24 # filter by namespace and type - annGen = conn.getObjects("Annotation", - opts={'ns': ns, 'ann_type': "comment"}) - assert len(list(annGen)) == 3 - annGen = conn.getObjects("Annotation", - opts={'ns': ns, 'ann_type': "tag"}) - assert len(list(annGen)) == 0 + for ann_type in ann_types: + annGen = conn.getObjects(ann_type, + opts={'ns': ns}) + assert len(list(annGen)) == 3 + annGen = conn.getObjects(ann_type, + opts={'ns': ns_map}) + assert len(list(annGen)) == 0 # test File Annotation is loaded annGen = conn.getObjects("Annotation", From e196a0e3d9b0120414ce8d3a0f0792dcd1587521 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 31 Jul 2026 13:24:30 +0100 Subject: [PATCH 07/15] Test parent_type case insensitive --- .../OmeroPy/test/integration/gatewaytest/test_get_objects.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index fbea5aa3c6b..830e4b16abc 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -710,11 +710,11 @@ def create_dataset_with_annotations(name, dtype="Dataset"): # get all annotations on one Dataset annGen = conn.getObjects("Annotation", - opts={'parent_type': "dataset", + opts={'parent_type': "Dataset", 'parent_ids': [dataset1.id]}) assert len(list(annGen)) == 10 - # get all annotations on two Datasets + # annotations on two Datasets - parent_type is case-insensitive annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset", 'parent_ids': [dataset1.id, From 8834cfef46fd959ec9e66a468ffc92a0a04eec8a Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 31 Jul 2026 13:37:13 +0100 Subject: [PATCH 08/15] Test parent_type 'experimentergroup' mapping to ExperimenterGroup --- .../gatewaytest/test_get_objects.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 830e4b16abc..8da70b2a963 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -708,6 +708,17 @@ def create_dataset_with_annotations(name, dtype="Dataset"): dataset2 = create_dataset_with_annotations("Dataset 2") create_dataset_with_annotations("Project 1", dtype="Project") + # Add Tag to Group + groupId = conn.getEventContext().groupId + tag = omero.gateway.TagAnnotationWrapper(conn) + tag.setNs("test_get_objects_group_tag") + tag.setValue("Test Tag on Group") + tag.save() + link = omero.model.ExperimenterGroupAnnotationLinkI() + link.child = omero.model.TagAnnotationI(tag.id, False) + link.parent = omero.model.ExperimenterGroupI(groupId, False) + conn.getUpdateService().saveAndReturnObject(link) + # get all annotations on one Dataset annGen = conn.getObjects("Annotation", opts={'parent_type': "Dataset", @@ -720,6 +731,9 @@ def create_dataset_with_annotations(name, dtype="Dataset"): 'parent_ids': [dataset1.id, dataset2.id]}) assert len(list(annGen)) == 20 + annGen = conn.getObjects("Annotation", + opts={'parent_type': "experimentergroup"}) + assert len(list(annGen)) == 1 # get all annotations on ALL Datasets annGen = conn.getObjects("Annotation", opts={'parent_type': "dataset"}) @@ -733,7 +747,7 @@ def create_dataset_with_annotations(name, dtype="Dataset"): # get ALL annotations # NB: this can include 'user' group annotations from other tests anns = list(conn.getObjects("Annotation")) - assert len(anns) >= 30 + assert len(anns) >= 31 # We only want Tags on the two Datasets annGen = conn.getObjects("TagAnnotation", @@ -744,7 +758,7 @@ def create_dataset_with_annotations(name, dtype="Dataset"): # ALL Tags annGen = conn.getObjects("TagAnnotation") - assert len(list(annGen)) == 3 + assert len(list(annGen)) == 4 # filter by namespace annGen = conn.getObjects("Annotation", opts={'ns': ns}) From 2084d18e97305bd4f6e2b4e34620eeb5fdc3c0c8 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 14 Aug 2026 16:12:56 +0100 Subject: [PATCH 09/15] Add test for conn.getAnnotationLinks() on an Annotation --- .../test/integration/gatewaytest/test_get_objects.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 8da70b2a963..6487eded6ba 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -545,6 +545,8 @@ def testGetAnnotations(self, gatewaywrapper, author_testimg_tiny): tag.setValue("Test Tag") tag = obj.linkAnnotation(tag) dataset.linkAnnotation(tag) + # also link the Tag to the Comment + ann.linkAnnotation(tag) # get the Comment annotation = gatewaywrapper.gateway.getObject( @@ -591,6 +593,15 @@ def testGetAnnotations(self, gatewaywrapper, author_testimg_tiny): assert obj.getId() == al.parent.id.val assert al.parent.__class__ == omero.model.ImageI + # Check links to Tag on the Comment + for obj_type in ["CommentAnnotation", "Annotation"]: + annLinks = list(gatewaywrapper.gateway.getAnnotationLinks( + obj_type, parent_ids=[ann.getId()])) + assert len(annLinks) == 1 + for al in annLinks: + assert ann.getId() == al.parent.id.val + assert tag.getId() == al.child.id.val + # compare with getObjectsByAnnotations annImages = list(gatewaywrapper.gateway.getObjectsByAnnotations( 'Image', [tag.getId()])) From 0c391a6ea93473db20786b5c39361e0ae29d2705 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 17 Aug 2026 13:32:54 +0100 Subject: [PATCH 10/15] flake8 fixes --- .../test/integration/gatewaytest/test_get_objects.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 6487eded6ba..9bbfdd67ab1 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -683,7 +683,8 @@ def create_dataset_with_annotations(name, dtype="Dataset"): for ann_type in ann_types: ann = getattr(omero.gateway, ann_type + "Wrapper")(conn) ann.setNs(ns) - if ann_type in ("LongAnnotation", "DoubleAnnotation", "TimestampAnnotation"): + if ann_type in ("LongAnnotation", "DoubleAnnotation", + "TimestampAnnotation"): ann.setValue(100.0) elif ann_type == "BooleanAnnotation": ann.setValue(True) @@ -778,10 +779,10 @@ def create_dataset_with_annotations(name, dtype="Dataset"): # filter by namespace and type for ann_type in ann_types: annGen = conn.getObjects(ann_type, - opts={'ns': ns}) + opts={'ns': ns}) assert len(list(annGen)) == 3 annGen = conn.getObjects(ann_type, - opts={'ns': ns_map}) + opts={'ns': ns_map}) assert len(list(annGen)) == 0 # test File Annotation is loaded From a43223b55df2e2b2cb8f52e34a7e9711d48f79e4 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 18 Aug 2026 21:53:09 +0100 Subject: [PATCH 11/15] Add test for conn.countAnnotations() --- .../test/integration/gatewaytest/test_annotation.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_annotation.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_annotation.py index 65d1817c45f..5ac642f85d2 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_annotation.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_annotation.py @@ -48,6 +48,15 @@ def _testAnnotation(obj, annclass, ns, value, sameOwner=False, assert ann.getNs() == ns, '%s != %s' % (str(ann.getNs()), str(ns)) if testOwner is not None: testOwner(obj, ann) + # test conn.countAnnotations() + counts = gateway.countAnnotations(obj.OMERO_CLASS, [obj.id]) + assert counts is not None + # Timestamp and Boolean not included in counts + if annclass.OMERO_CLASS not in ["TimestampAnnotation", + "BooleanAnnotation", + # LongAnnotation is only for 'rating' + "LongAnnotation"]: + assert counts[annclass.OMERO_CLASS] >= 1. # e.g. TagAnnotation: 1 # Remove and check obj.removeAnnotations(ns) assert obj.getAnnotation(ns) is None From 76e5514db9cb39e1bde37f61795d95fa7d5b65bc Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 20 Aug 2026 17:04:01 +0100 Subject: [PATCH 12/15] Use group.linkAnnotation(tag) --- .../integration/gatewaytest/test_get_objects.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 9bbfdd67ab1..b0dd3b2c966 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -669,14 +669,12 @@ def testGetObjectsAnnotation(self): def create_dataset_with_annotations(name, dtype="Dataset"): if dtype == "Dataset": obj = DatasetI() - obj.name = rstring(name) - obj = update.saveAndReturnObject(obj) wrapper = omero.gateway.DatasetWrapper(conn, obj) elif dtype == "Project": obj = ProjectI() - obj.name = rstring(name) - obj = update.saveAndReturnObject(obj) wrapper = omero.gateway.ProjectWrapper(conn, obj) + wrapper.setName(name) + wrapper.save() # Create total of 10 annotations on the object... # create Comment, Tag, Xml, etc @@ -721,15 +719,11 @@ def create_dataset_with_annotations(name, dtype="Dataset"): create_dataset_with_annotations("Project 1", dtype="Project") # Add Tag to Group - groupId = conn.getEventContext().groupId tag = omero.gateway.TagAnnotationWrapper(conn) tag.setNs("test_get_objects_group_tag") tag.setValue("Test Tag on Group") - tag.save() - link = omero.model.ExperimenterGroupAnnotationLinkI() - link.child = omero.model.TagAnnotationI(tag.id, False) - link.parent = omero.model.ExperimenterGroupI(groupId, False) - conn.getUpdateService().saveAndReturnObject(link) + group = conn.getGroupFromContext() + group.linkAnnotation(tag) # get all annotations on one Dataset annGen = conn.getObjects("Annotation", From ab93ce0ded49c25d14c1083706ee4e0bc18004cd Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 20 Aug 2026 18:14:02 +0100 Subject: [PATCH 13/15] Add testAnnotationAnnotationLinks() --- .../gatewaytest/test_get_objects.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index b0dd3b2c966..01fd6a5743a 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -789,6 +789,72 @@ def create_dataset_with_annotations(name, dtype="Dataset"): assert ann.getFile().getName() == 'fileName' assert ann.getFile().getPath() == 'path/to/file' + def testAnnotationAnnotationLinks(self): + """ + Test various annotations methods when parent is an Annotation or Group + """ + + group = self.new_group(perms='rwra--') + client, user = self.new_client_and_user(group=group) + + conn = BlitzGateway(client_obj=client) + + parent = omero.gateway.TagAnnotationWrapper(conn) + parent.setNs("test.annotation.parent") + parent.setValue("parent Tag") + parent.save() + + child = omero.gateway.CommentAnnotationWrapper(conn) + child.setNs("test.annotation.child") + child.setValue("test annotation child Comment") + child.save() + child2 = omero.gateway.LongAnnotationWrapper(conn) + child2.setNs("test.annotation.child") + child2.setValue(123) + child2.save() + + # link child to parent + parent.linkAnnotation(child) + parent.linkAnnotation(child2) + group = conn.getGroupFromContext() + # Also annotate the Group + group.linkAnnotation(child) + + + # Test getObjects() - single Comment on group + for ann_type in ["CommentAnnotation", "LongAnnotation", "Annotation"]: + annGen = conn.getObjects(ann_type, + opts={'parent_type': "experimentergroup", + 'parent_ids': [group.id]}) + count = 0 if ann_type == "LongAnnotation" else 1 + assert len(list(annGen)) == count + + # Query annotations on the Annotation + counts = {"CommentAnnotation": 1, "LongAnnotation": 1, + "Annotation": 2, "BooleanAnnotation": 0} + for ann_type, count in counts.items(): + annGen = conn.getObjects(ann_type, + opts={'parent_type': "annotation", + 'parent_ids': [parent.id]}) + assert len(list(annGen)) == count + + # Test conn.getAnnotationLinks() + for parent_types in ["CommentAnnotation", "LongAnnotation", "Annotation"]: + # ALL parent_typess get coerced to "Annotation" - Long/Comment etc. ignored + annLinks = conn.getAnnotationLinks(parent_types, parent_ids=[parent.id]) + assert len(list(annLinks)) == 2 + + for parent_types in ["ExperimenterGroup", "Annotation"]: + annLinks = conn.getAnnotationLinks("Annotation", ann_ids=[child.id]) + # child is on parent (Tag) and Group, so should be 1 link each + assert len(list(annLinks)) == 1 + + # test countAnnotations() + counts = conn.countAnnotations("Annotation", obj_ids=[parent.id]) + assert counts["CommentAnnotation"] == 1 + counts = conn.countAnnotations("experimentergroup", obj_ids=[group.id]) + assert counts["CommentAnnotation"] == 1 + def testGetImage(self, gatewaywrapper, author_testimg_tiny): testImage = author_testimg_tiny # This should return image wrapper From 75c94e5e68c8e5bb4d03b1fc447242d7d3f628db Mon Sep 17 00:00:00 2001 From: William Moore Date: Thu, 20 Aug 2026 18:31:32 +0100 Subject: [PATCH 14/15] flake8 fixes --- .../integration/gatewaytest/test_get_objects.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index 01fd6a5743a..f6f222a5039 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -20,7 +20,7 @@ from omero.gateway.scripts import dbhelpers from omero.rtypes import wrap, rlong -from omero.testlib import ITest, rstring +from omero.testlib import ITest from omero.gateway import BlitzGateway, KNOWN_WRAPPERS, DatasetWrapper, \ ProjectWrapper, ImageWrapper, ScreenWrapper, PlateWrapper from omero.model import DatasetI, \ @@ -656,7 +656,6 @@ def testGetObjectsAnnotation(self): client, user = self.new_client_and_user(group=group) conn = BlitzGateway(client_obj=client) - update = conn.getUpdateService() ann_types = ["CommentAnnotation", "TagAnnotation", "LongAnnotation", "XmlAnnotation", @@ -820,7 +819,6 @@ def testAnnotationAnnotationLinks(self): # Also annotate the Group group.linkAnnotation(child) - # Test getObjects() - single Comment on group for ann_type in ["CommentAnnotation", "LongAnnotation", "Annotation"]: annGen = conn.getObjects(ann_type, @@ -839,13 +837,16 @@ def testAnnotationAnnotationLinks(self): assert len(list(annGen)) == count # Test conn.getAnnotationLinks() - for parent_types in ["CommentAnnotation", "LongAnnotation", "Annotation"]: - # ALL parent_typess get coerced to "Annotation" - Long/Comment etc. ignored - annLinks = conn.getAnnotationLinks(parent_types, parent_ids=[parent.id]) + for parent_types in ["CommentAnnotation", "LongAnnotation", + "Annotation"]: + # ALL parent_typess get coerced to "Annotation" + annLinks = conn.getAnnotationLinks(parent_types, + parent_ids=[parent.id]) assert len(list(annLinks)) == 2 for parent_types in ["ExperimenterGroup", "Annotation"]: - annLinks = conn.getAnnotationLinks("Annotation", ann_ids=[child.id]) + annLinks = conn.getAnnotationLinks("Annotation", + ann_ids=[child.id]) # child is on parent (Tag) and Group, so should be 1 link each assert len(list(annLinks)) == 1 From 1adbba843cc151b98357bf62d98405d15f3f7f65 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 25 Aug 2026 12:14:20 +0100 Subject: [PATCH 15/15] Add group.listAnnotations() to test --- .../integration/gatewaytest/test_get_objects.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py index f6f222a5039..6c5d5c3b11c 100644 --- a/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py +++ b/components/tools/OmeroPy/test/integration/gatewaytest/test_get_objects.py @@ -803,9 +803,10 @@ def testAnnotationAnnotationLinks(self): parent.setValue("parent Tag") parent.save() + COMMENT_TEXT = "test annotation child Comment" child = omero.gateway.CommentAnnotationWrapper(conn) child.setNs("test.annotation.child") - child.setValue("test annotation child Comment") + child.setValue(COMMENT_TEXT) child.save() child2 = omero.gateway.LongAnnotationWrapper(conn) child2.setNs("test.annotation.child") @@ -856,6 +857,20 @@ def testAnnotationAnnotationLinks(self): counts = conn.countAnnotations("experimentergroup", obj_ids=[group.id]) assert counts["CommentAnnotation"] == 1 + # test listAnnotations() + group = conn.getObject("ExperimenterGroup", group.id) + groupAnns = list(group.listAnnotations()) + assert len(groupAnns) == 1 + assert groupAnns[0].getValue() == COMMENT_TEXT + tag = conn.getObject("Annotation", parent.id) + tagAnns = list(tag.listAnnotations()) + assert len(tagAnns) == 2 + # Since obj._loadAnnotationLinks() doesn't load child annotations + # for AnootationAnnotationLink ?? (unexpected) the anns are + # not loaded, so tagAnns are just empty AnnotationWrapper() + # and we can't getValue() + # assert COMMENT_TEXT in [a.getValue() for a in tagAnns] + def testGetImage(self, gatewaywrapper, author_testimg_tiny): testImage = author_testimg_tiny # This should return image wrapper