Skip to content

[PTDT-2967] Relationship read_only #1950

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Union
from pydantic import BaseModel
from typing import Union, Optional
from pydantic import BaseModel, model_validator
from enum import Enum
import warnings
from labelbox.data.annotation_types.annotation import (
BaseAnnotation,
ObjectAnnotation,
Expand All @@ -16,6 +17,15 @@ class Type(Enum):
source: Union[ObjectAnnotation, ClassificationAnnotation]
target: ObjectAnnotation
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):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Optional
from pydantic import BaseModel
from .base import NDAnnotation, DataRow
from ...annotation_types.data import GenericDataRowData
Expand All @@ -13,7 +14,7 @@ class _Relationship(BaseModel):
source: str
target: str
type: str

readonly: Optional[bool] = None

class NDRelationship(NDAnnotation):
relationship: _Relationship
Expand All @@ -30,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,
Expand All @@ -50,5 +52,6 @@ def from_common(
source=str(relationship.source._uuid),
target=str(relationship.target._uuid),
type=relationship.type.value,
readonly=relationship.readonly,
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading