From 8a9b4e4fdec4099ff40428c0c669a122c893bb1d Mon Sep 17 00:00:00 2001 From: Stanislav Issayenko Date: Tue, 4 Feb 2025 16:58:06 -0300 Subject: [PATCH 1/7] read_only Relationship --- libs/labelbox/pyproject.toml | 2 +- .../src/labelbox/data/annotation_types/relationship.py | 3 ++- .../src/labelbox/data/serialization/ndjson/relationship.py | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libs/labelbox/pyproject.toml b/libs/labelbox/pyproject.toml index 4a8de76db..7fc95af6e 100644 --- a/libs/labelbox/pyproject.toml +++ b/libs/labelbox/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "labelbox" -version = "6.6.0" +version = "6.6.1" description = "Labelbox Python API" authors = [{ name = "Labelbox", email = "engineering@labelbox.com" }] dependencies = [ diff --git a/libs/labelbox/src/labelbox/data/annotation_types/relationship.py b/libs/labelbox/src/labelbox/data/annotation_types/relationship.py index 0e9c4e934..70e3ab073 100644 --- a/libs/labelbox/src/labelbox/data/annotation_types/relationship.py +++ b/libs/labelbox/src/labelbox/data/annotation_types/relationship.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Union, Optional from pydantic import BaseModel from enum import Enum from labelbox.data.annotation_types.annotation import ( @@ -16,6 +16,7 @@ class Type(Enum): source: Union[ObjectAnnotation, ClassificationAnnotation] target: ObjectAnnotation type: Type = Type.UNIDIRECTIONAL + read_only: Optional[bool] class RelationshipAnnotation(BaseAnnotation): diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py index f692bae41..bc3aa19ce 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py @@ -1,3 +1,4 @@ +from typing import Optional from pydantic import BaseModel from .base import NDAnnotation, DataRow from ...annotation_types.data import GenericDataRowData @@ -13,7 +14,7 @@ class _Relationship(BaseModel): source: str target: str type: str - + read_only: Optional[bool] class NDRelationship(NDAnnotation): relationship: _Relationship @@ -50,5 +51,6 @@ def from_common( source=str(relationship.source._uuid), target=str(relationship.target._uuid), type=relationship.type.value, + read_only=relationship.read_only, ), ) From aa53ce73be2cb7d41ac6c396a0a092778f7e7e52 Mon Sep 17 00:00:00 2001 From: Stanislav Issayenko Date: Wed, 5 Feb 2025 17:33:54 -0300 Subject: [PATCH 2/7] Rename to readonly --- .../src/labelbox/data/annotation_types/relationship.py | 2 +- .../src/labelbox/data/serialization/ndjson/relationship.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/labelbox/src/labelbox/data/annotation_types/relationship.py b/libs/labelbox/src/labelbox/data/annotation_types/relationship.py index 70e3ab073..42e2695d2 100644 --- a/libs/labelbox/src/labelbox/data/annotation_types/relationship.py +++ b/libs/labelbox/src/labelbox/data/annotation_types/relationship.py @@ -16,7 +16,7 @@ class Type(Enum): source: Union[ObjectAnnotation, ClassificationAnnotation] target: ObjectAnnotation type: Type = Type.UNIDIRECTIONAL - read_only: Optional[bool] + readonly: Optional[bool] = None class RelationshipAnnotation(BaseAnnotation): diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py index bc3aa19ce..95942ec23 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py @@ -14,7 +14,7 @@ class _Relationship(BaseModel): source: str target: str type: str - read_only: Optional[bool] + readonly: Optional[bool] = None class NDRelationship(NDAnnotation): relationship: _Relationship @@ -51,6 +51,6 @@ def from_common( source=str(relationship.source._uuid), target=str(relationship.target._uuid), type=relationship.type.value, - read_only=relationship.read_only, + readonly=relationship.readonly, ), ) From 7f6200262a66a0a352846d97b2587f686a54f9c2 Mon Sep 17 00:00:00 2001 From: Stanislav Issayenko Date: Wed, 5 Feb 2025 19:46:29 -0300 Subject: [PATCH 3/7] Warn if readonly is True --- .../labelbox/data/annotation_types/relationship.py | 11 ++++++++++- .../data/serialization/ndjson/relationship.py | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/libs/labelbox/src/labelbox/data/annotation_types/relationship.py b/libs/labelbox/src/labelbox/data/annotation_types/relationship.py index 42e2695d2..760c6a529 100644 --- a/libs/labelbox/src/labelbox/data/annotation_types/relationship.py +++ b/libs/labelbox/src/labelbox/data/annotation_types/relationship.py @@ -1,6 +1,7 @@ from typing import Union, Optional -from pydantic import BaseModel +from pydantic import BaseModel, model_validator from enum import Enum +import warnings from labelbox.data.annotation_types.annotation import ( BaseAnnotation, ObjectAnnotation, @@ -18,6 +19,14 @@ class Type(Enum): type: Type = Type.UNIDIRECTIONAL readonly: Optional[bool] = None + @model_validator(mode='after') + def check_readonly(self): + if self.readonly is True: + warnings.warn( + "Creating a relationship with readonly=True is in beta and its behavior may change in future releases.", + ) + return self + class RelationshipAnnotation(BaseAnnotation): value: Relationship diff --git a/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py b/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py index 95942ec23..6ed4d4ac6 100644 --- a/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py +++ b/libs/labelbox/src/labelbox/data/serialization/ndjson/relationship.py @@ -31,6 +31,7 @@ def to_common( source=source, target=target, type=Relationship.Type(annotation.relationship.type), + readonly=annotation.relationship.readonly, ), extra={"uuid": annotation.uuid}, feature_schema_id=annotation.schema_id, From ba0251ccf4790bb47c1183f02e93e6327221644d Mon Sep 17 00:00:00 2001 From: Stanislav Issayenko Date: Wed, 5 Feb 2025 19:46:50 -0300 Subject: [PATCH 4/7] Bump the version to 6.7.0 --- libs/labelbox/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/labelbox/pyproject.toml b/libs/labelbox/pyproject.toml index 7fc95af6e..52796fb3e 100644 --- a/libs/labelbox/pyproject.toml +++ b/libs/labelbox/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "labelbox" -version = "6.6.1" +version = "6.7.0" description = "Labelbox Python API" authors = [{ name = "Labelbox", email = "engineering@labelbox.com" }] dependencies = [ From f09fe19ec41d7f68db798c4881fd4ac11ce22ac8 Mon Sep 17 00:00:00 2001 From: Stanislav Issayenko Date: Wed, 5 Feb 2025 20:14:45 -0300 Subject: [PATCH 5/7] Add data test --- .../annotation_import/test_relationships.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/libs/labelbox/tests/data/annotation_import/test_relationships.py b/libs/labelbox/tests/data/annotation_import/test_relationships.py index f4a80dab9..36d59db72 100644 --- a/libs/labelbox/tests/data/annotation_import/test_relationships.py +++ b/libs/labelbox/tests/data/annotation_import/test_relationships.py @@ -337,3 +337,72 @@ def test_classification_relationship_restrictions(): data={"global_key": "test_key"}, annotations=[relationship] ) list(NDJsonConverter.serialize([label])) + + +def test_relationship_readonly_default_none(): + """Test that relationship readonly field defaults to None when not specified.""" + source = ObjectAnnotation( + name="e1", + value=TextEntity(start=10, end=12), + ) + target = ObjectAnnotation( + name="e2", + value=TextEntity(start=30, end=35), + ) + + relationship = RelationshipAnnotation( + name="rel", + value=Relationship( + source=source, + target=target, + type=Relationship.Type.UNIDIRECTIONAL, + ), + ) + assert relationship.value.readonly is None + + +def test_relationship_readonly_explicit_false(): + """Test that relationship readonly field can be explicitly set to False.""" + source = ObjectAnnotation( + name="e1", + value=TextEntity(start=10, end=12), + ) + target = ObjectAnnotation( + name="e2", + value=TextEntity(start=30, end=35), + ) + + relationship = RelationshipAnnotation( + name="rel", + value=Relationship( + source=source, + target=target, + type=Relationship.Type.UNIDIRECTIONAL, + readonly=False, + ), + ) + assert relationship.value.readonly is False + + +def test_relationship_readonly_explicit_true(): + """Test that setting relationship readonly=True triggers a warning.""" + source = ObjectAnnotation( + name="e1", + value=TextEntity(start=10, end=12), + ) + target = ObjectAnnotation( + name="e2", + value=TextEntity(start=30, end=35), + ) + + with pytest.warns(UserWarning, match="Creating a relationship with readonly=True is in beta.*"): + relationship = RelationshipAnnotation( + name="rel", + value=Relationship( + source=source, + target=target, + type=Relationship.Type.UNIDIRECTIONAL, + readonly=True, + ), + ) + assert relationship.value.readonly is True From e8e2412147b7a993f443484c803ea3515e284b82 Mon Sep 17 00:00:00 2001 From: Stanislav Issayenko Date: Wed, 5 Feb 2025 20:21:28 -0300 Subject: [PATCH 6/7] Add ndjson test --- .../serialization/ndjson/test_relationship.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/libs/labelbox/tests/data/serialization/ndjson/test_relationship.py b/libs/labelbox/tests/data/serialization/ndjson/test_relationship.py index 13ff088aa..bdd3816e7 100644 --- a/libs/labelbox/tests/data/serialization/ndjson/test_relationship.py +++ b/libs/labelbox/tests/data/serialization/ndjson/test_relationship.py @@ -192,3 +192,82 @@ def test_bidirectional_relationship(): ) assert rel_serialized["relationship"]["type"] == "bidirectional" assert rel_2_serialized["relationship"]["type"] == "bidirectional" + + +def test_readonly_relationships(): + ner_source = ObjectAnnotation( + name="e1", + value=TextEntity(start=10, end=12), + ) + + ner_target = ObjectAnnotation( + name="e2", + value=TextEntity(start=30, end=35), + ) + + # Test unidirectional relationship with readonly=True + readonly_relationship = RelationshipAnnotation( + name="readonly_rel", + value=Relationship( + source=ner_source, + target=ner_target, + type=Relationship.Type.UNIDIRECTIONAL, + readonly=True, + ), + ) + + # Test bidirectional relationship with readonly=False + non_readonly_relationship = RelationshipAnnotation( + name="non_readonly_rel", + value=Relationship( + source=ner_source, + target=ner_target, + type=Relationship.Type.BIDIRECTIONAL, + readonly=False, + ), + ) + + label = Label( + data={"uid": "clqbkpy236syk07978v3pscw1"}, + annotations=[ + ner_source, + ner_target, + readonly_relationship, + non_readonly_relationship, + ], + ) + + serialized_label = list(NDJsonConverter.serialize([label])) + + ner_source_serialized = next( + annotation + for annotation in serialized_label + if annotation["name"] == ner_source.name + ) + ner_target_serialized = next( + annotation + for annotation in serialized_label + if annotation["name"] == ner_target.name + ) + readonly_rel_serialized = next( + annotation + for annotation in serialized_label + if annotation["name"] == readonly_relationship.name + ) + non_readonly_rel_serialized = next( + annotation + for annotation in serialized_label + if annotation["name"] == non_readonly_relationship.name + ) + + # Verify readonly relationship + assert readonly_rel_serialized["relationship"]["source"] == ner_source_serialized["uuid"] + assert readonly_rel_serialized["relationship"]["target"] == ner_target_serialized["uuid"] + assert readonly_rel_serialized["relationship"]["type"] == "unidirectional" + assert readonly_rel_serialized["relationship"]["readonly"] is True + + # Verify non-readonly relationship + assert non_readonly_rel_serialized["relationship"]["source"] == ner_source_serialized["uuid"] + assert non_readonly_rel_serialized["relationship"]["target"] == ner_target_serialized["uuid"] + assert non_readonly_rel_serialized["relationship"]["type"] == "bidirectional" + assert non_readonly_rel_serialized["relationship"]["readonly"] is False From 315ce1f407ca5b81d70d9ca83e2f643088932515 Mon Sep 17 00:00:00 2001 From: Stanislav Issayenko Date: Wed, 5 Feb 2025 20:22:29 -0300 Subject: [PATCH 7/7] Don't bump the version --- libs/labelbox/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/labelbox/pyproject.toml b/libs/labelbox/pyproject.toml index 52796fb3e..4a8de76db 100644 --- a/libs/labelbox/pyproject.toml +++ b/libs/labelbox/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "labelbox" -version = "6.7.0" +version = "6.6.0" description = "Labelbox Python API" authors = [{ name = "Labelbox", email = "engineering@labelbox.com" }] dependencies = [