Skip to content

Commit 700f58e

Browse files
authored
[PLT-1201] Deprecate Project setup_editor and add Project connect_ontology (#1713)
1 parent 48bcfb3 commit 700f58e

File tree

9 files changed

+112
-65
lines changed

9 files changed

+112
-65
lines changed

libs/labelbox/src/labelbox/schema/project.py

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,12 @@ def is_chat_evaluation(self) -> bool:
149149
def is_auto_data_generation(self) -> bool:
150150
return (self.upload_type == UploadType.Auto) # type: ignore
151151

152+
# we test not only the project ontology is None, but also a default empty ontology that we create when we attach a labeling front end in createLabelingFrontendOptions
153+
def is_empty_ontology(self) -> bool:
154+
ontology = self.ontology() # type: ignore
155+
return ontology is None or (len(ontology.tools()) == 0 and
156+
len(ontology.classifications()) == 0)
157+
152158
def project_model_configs(self):
153159
query_str = """query ProjectModelConfigsPyApi($id: ID!) {
154160
project(where: {id : $id}) {
@@ -772,35 +778,27 @@ def setup_editor(self, ontology) -> None:
772778
Args:
773779
ontology (Ontology): The ontology to attach to the project
774780
"""
781+
warnings.warn("This method is deprecated use connect_ontology instead.")
782+
self.connect_ontology(ontology)
775783

776-
if self.labeling_frontend() is not None and not self.is_chat_evaluation(
777-
): # Chat evaluation projects are automatically set up via the same api that creates a project
778-
raise ResourceConflict("Editor is already set up.")
779-
780-
if not self.is_chat_evaluation():
781-
labeling_frontend = next(
782-
self.client.get_labeling_frontends(
783-
where=Entity.LabelingFrontend.name == "Editor"))
784-
self.labeling_frontend.connect(labeling_frontend)
785-
786-
LFO = Entity.LabelingFrontendOptions
787-
self.client._create(
788-
LFO, {
789-
LFO.project:
790-
self,
791-
LFO.labeling_frontend:
792-
labeling_frontend,
793-
LFO.customization_options:
794-
json.dumps({
795-
"tools": [],
796-
"classifications": []
797-
})
798-
})
799-
else:
800-
warnings.warn("""
801-
Skipping editor setup for a chat evaluation project.
802-
Editor was setup automatically.
803-
""")
784+
def connect_ontology(self, ontology) -> None:
785+
"""
786+
Connects the ontology to the project. If an editor is not setup, it will be connected as well.
787+
788+
Note: For live chat model evaluation projects, the editor setup is skipped becase it is automatically setup when the project is created.
789+
790+
Args:
791+
ontology (Ontology): The ontology to attach to the project
792+
"""
793+
if self.labeling_frontend(
794+
) is None: # Chat evaluation projects are automatically set up via the same api that creates a project
795+
self._connect_default_labeling_front_end(ontology_as_dict={
796+
"tools": [],
797+
"classifications": []
798+
})
799+
800+
if not self.is_empty_ontology():
801+
raise ValueError("Ontology already connected to project.")
804802

805803
query_str = """mutation ConnectOntologyPyApi($projectId: ID!, $ontologyId: ID!){
806804
project(where: {id: $projectId}) {connectOntology(ontologyId: $ontologyId) {id}}}"""
@@ -812,43 +810,55 @@ def setup_editor(self, ontology) -> None:
812810
self.update(setup_complete=timestamp)
813811

814812
def setup(self, labeling_frontend, labeling_frontend_options) -> None:
815-
""" Finalizes the Project setup.
813+
""" This method will associate default labeling frontend with the project and create an ontology based on labeling_frontend_options.
816814
817815
Args:
818-
labeling_frontend (LabelingFrontend): Which UI to use to label the
819-
data.
816+
labeling_frontend (LabelingFrontend): Do not use, this parameter is deprecated. We now associate the default labeling frontend with the project.
820817
labeling_frontend_options (dict or str): Labeling frontend options,
821818
a.k.a. project ontology. If given a `dict` it will be converted
822819
to `str` using `json.dumps`.
823820
"""
824821

822+
warnings.warn("This method is deprecated use connect_ontology instead.")
823+
if labeling_frontend is not None:
824+
warnings.warn(
825+
"labeling_frontend parameter will not be used to create a new labeling frontend."
826+
)
827+
825828
if self.is_chat_evaluation():
826829
warnings.warn("""
827-
This project is a chat evaluation project.
830+
This project is a live chat evaluation project.
828831
Editor was setup automatically.
829-
No need to call this method.
830832
""")
831833
return
832834

833-
if self.labeling_frontend() is not None:
834-
raise ResourceConflict("Editor is already set up.")
835+
if self.labeling_frontend(
836+
) is None: # Chat evaluation projects are automatically set up via the same api that creates a project
837+
self._connect_default_labeling_front_end(labeling_frontend_options)
835838

836-
if not isinstance(labeling_frontend_options, str):
837-
labeling_frontend_options = json.dumps(labeling_frontend_options)
839+
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
840+
self.update(setup_complete=timestamp)
838841

842+
def _connect_default_labeling_front_end(self, ontology_as_dict: dict):
843+
warnings.warn("Connecting default labeling editor for the project.")
844+
labeling_frontend = next(
845+
self.client.get_labeling_frontends(
846+
where=Entity.LabelingFrontend.name == "Editor"))
839847
self.labeling_frontend.connect(labeling_frontend)
840848

849+
if not isinstance(ontology_as_dict, str):
850+
labeling_frontend_options_str = json.dumps(ontology_as_dict)
851+
else:
852+
labeling_frontend_options_str = ontology_as_dict
853+
841854
LFO = Entity.LabelingFrontendOptions
842855
self.client._create(
843856
LFO, {
844857
LFO.project: self,
845858
LFO.labeling_frontend: labeling_frontend,
846-
LFO.customization_options: labeling_frontend_options
859+
LFO.customization_options: labeling_frontend_options_str
847860
})
848861

849-
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
850-
self.update(setup_complete=timestamp)
851-
852862
def create_batch(
853863
self,
854864
name: str,

libs/labelbox/tests/data/annotation_import/conftest.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,8 @@ def get_data_row_id(indx=0):
608608

609609
yield get_data_row_id
610610

611-
#TODO: Switch to setup_editor, setup might get removed in later releases
611+
612+
#TODO: Switch to connect_ontology, setup might get removed in later releases
612613
@pytest.fixture
613614
def configured_project(client, initial_dataset, ontology, rand_gen, image_url):
614615
dataset = initial_dataset
@@ -645,7 +646,8 @@ def configured_project(client, initial_dataset, ontology, rand_gen, image_url):
645646

646647
project.delete()
647648

648-
#TODO: Switch to setup_editor, setup might get removed in later releases
649+
650+
#TODO: Switch to connect_ontology, setup might get removed in later releases
649651
@pytest.fixture
650652
def project_with_ontology(client, configured_project, ontology, rand_gen):
651653
project = client.create_project(name=rand_gen(str),
@@ -660,7 +662,8 @@ def project_with_ontology(client, configured_project, ontology, rand_gen):
660662

661663
project.delete()
662664

663-
#TODO: Switch to setup_editor, setup might get removed in later releases
665+
666+
#TODO: Switch to connect_ontology, setup might get removed in later releases
664667
@pytest.fixture
665668
def configured_project_pdf(client, ontology, rand_gen, pdf_url):
666669
project = client.create_project(name=rand_gen(str),

libs/labelbox/tests/data/annotation_import/test_send_to_annotate_mea.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def test_send_to_annotate_from_model(client, configured_project,
1414
destination_project = project
1515
model = client.get_model(model_run.model_id)
1616
ontology = client.get_ontology(model.ontology_id)
17-
destination_project.setup_editor(ontology)
17+
destination_project.connect_ontology(ontology)
1818

1919
queues = destination_project.task_queues()
2020
initial_review_task = next(
@@ -55,13 +55,13 @@ def test_send_to_annotate_from_model(client, configured_project,
5555
# Check that the data row was sent to the new project
5656
destination_batches = list(destination_project.batches())
5757
assert len(destination_batches) == 1
58-
58+
5959
export_task = destination_project.export()
6060
export_task.wait_till_done()
6161
stream = export_task.get_buffered_stream()
62-
62+
6363
destination_data_rows = [dr.json["data_row"]["id"] for dr in stream]
64-
64+
6565
assert len(destination_data_rows) == len(data_row_ids)
6666
assert all([dr in data_row_ids for dr in destination_data_rows])
6767

libs/labelbox/tests/integration/test_chat_evaluation_ontology_project.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_create_chat_evaluation_ontology_project(
2828
project = live_chat_evaluation_project_with_new_dataset
2929
assert project.model_setup_complete is None
3030

31-
project.setup_editor(ontology)
31+
project.connect_ontology(ontology)
3232

3333
assert project.labeling_frontend().name == "Editor"
3434
assert project.ontology().name == ontology.name
@@ -61,7 +61,7 @@ def test_create_chat_evaluation_ontology_project_existing_dataset(
6161

6262
project = chat_evaluation_project_append_to_dataset
6363
assert project
64-
project.setup_editor(ontology)
64+
project.connect_ontology(ontology)
6565

6666
assert project.labeling_frontend().name == "Editor"
6767
assert project.ontology().name == ontology.name

libs/labelbox/tests/integration/test_offline_chat_evaluation_project.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
34
def test_create_offline_chat_evaluation_project(client, rand_gen,
45
offline_chat_evaluation_project,
56
chat_evaluation_ontology,
@@ -9,7 +10,7 @@ def test_create_offline_chat_evaluation_project(client, rand_gen,
910
assert project
1011

1112
ontology = chat_evaluation_ontology
12-
project.setup_editor(ontology)
13+
project.connect_ontology(ontology)
1314

1415
assert project.labeling_frontend().name == "Editor"
1516
assert project.ontology().name == ontology.name

libs/labelbox/tests/integration/test_ontology.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def test_cant_delete_an_ontology_with_project(client):
9595
name='ontology name',
9696
feature_schema_ids=[feature_schema_id],
9797
media_type=MediaType.Image)
98-
project.setup_editor(ontology)
98+
project.connect_ontology(ontology)
9999

100100
with pytest.raises(
101101
Exception,
@@ -160,7 +160,7 @@ def test_does_not_include_used_ontologies(client):
160160
project = client.create_project(name="test project",
161161
queue_mode=QueueMode.Batch,
162162
media_type=MediaType.Image)
163-
project.setup_editor(ontology_with_project)
163+
project.connect_ontology(ontology_with_project)
164164
unused_ontologies = client.get_unused_ontologies()
165165

166166
assert ontology_with_project.uid not in unused_ontologies

libs/labelbox/tests/integration/test_project_setup.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def test_project_editor_setup(client, project, rand_gen):
5555
ontology_name = f"test_project_editor_setup_ontology_name-{rand_gen(str)}"
5656
ontology = client.create_ontology(ontology_name, simple_ontology())
5757
now = datetime.now().astimezone(timezone.utc)
58-
project.setup_editor(ontology)
58+
project.connect_ontology(ontology)
5959
assert now - project.setup_complete <= timedelta(seconds=3)
6060
assert now - project.last_activity_time <= timedelta(seconds=3)
6161
assert project.labeling_frontend().name == "Editor"
@@ -68,10 +68,10 @@ def test_project_editor_setup(client, project, rand_gen):
6868
] == [ontology_name]
6969

7070

71-
def test_project_editor_setup_cant_call_multiple_times(client, project,
72-
rand_gen):
71+
def test_project_connect_ontology_cant_call_multiple_times(
72+
client, project, rand_gen):
7373
ontology_name = f"test_project_editor_setup_ontology_name-{rand_gen(str)}"
7474
ontology = client.create_ontology(ontology_name, simple_ontology())
75-
project.setup_editor(ontology)
76-
with pytest.raises(ResourceConflict):
77-
project.setup_editor(ontology)
75+
project.connect_ontology(ontology)
76+
with pytest.raises(ValueError):
77+
project.connect_ontology(ontology)

libs/labelbox/tests/integration/test_send_to_annotate.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44

55

66
def test_send_to_annotate_include_annotations(
7-
client: Client, configured_batch_project_with_label: Project, project_pack: List[Project], ontology: Ontology):
7+
client: Client, configured_batch_project_with_label: Project,
8+
project_pack: List[Project], ontology: Ontology):
89
[source_project, _, data_row, _] = configured_batch_project_with_label
910
destination_project: Project = project_pack[0]
1011

1112
src_ontology = source_project.ontology()
12-
destination_project.setup_editor(ontology)
13+
destination_project.connect_ontology(ontology)
1314

1415
# build an ontology mapping using the top level tools
1516
src_feature_schema_ids = list(
@@ -46,11 +47,11 @@ def test_send_to_annotate_include_annotations(
4647
# Check that the data row was sent to the new project
4748
destination_batches = list(destination_project.batches())
4849
assert len(destination_batches) == 1
49-
50+
5051
export_task = destination_project.export()
5152
export_task.wait_till_done()
5253
stream = export_task.get_buffered_stream()
53-
54+
5455
destination_data_rows = [dr.json["data_row"]["id"] for dr in stream]
5556
assert len(destination_data_rows) == 1
5657
assert destination_data_rows[0] == data_row.uid

libs/labelbox/tests/unit/test_project.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
11
import pytest
2-
from unittest.mock import MagicMock
2+
from unittest.mock import MagicMock, patch
33

44
from labelbox.schema.project import Project
55
from labelbox.schema.ontology_kind import EditorTaskType
66

77

8+
@pytest.fixture
9+
def project_entity():
10+
return Project(
11+
MagicMock(), {
12+
"id": "test",
13+
"name": "test",
14+
"createdAt": "2021-06-01T00:00:00.000Z",
15+
"updatedAt": "2021-06-01T00:00:00.000Z",
16+
"autoAuditNumberOfLabels": 1,
17+
"autoAuditPercentage": 100,
18+
"dataRowCount": 1,
19+
"description": "test",
20+
"editorTaskType": "MODEL_CHAT_EVALUATION",
21+
"lastActivityTime": "2021-06-01T00:00:00.000Z",
22+
"allowedMediaType": "IMAGE",
23+
"queueMode": "BATCH",
24+
"setupComplete": "2021-06-01T00:00:00.000Z",
25+
"modelSetupComplete": None,
26+
"uploadType": "Auto",
27+
})
28+
29+
830
@pytest.mark.parametrize(
931
'api_editor_task_type, expected_editor_task_type',
1032
[(None, EditorTaskType.Missing),
@@ -14,7 +36,7 @@
1436
EditorTaskType.OfflineModelChatEvaluation),
1537
('NEW_TYPE', EditorTaskType.Missing)])
1638
def test_project_editor_task_type(api_editor_task_type,
17-
expected_editor_task_type):
39+
expected_editor_task_type, project_entity):
1840
client = MagicMock()
1941
project = Project(
2042
client, {
@@ -36,3 +58,13 @@ def test_project_editor_task_type(api_editor_task_type,
3658
})
3759

3860
assert project.editor_task_type == expected_editor_task_type
61+
62+
63+
def test_setup_editor_using_connect_ontology(project_entity):
64+
project = project_entity
65+
ontology = MagicMock()
66+
project.connect_ontology = MagicMock()
67+
with patch("warnings.warn") as warn:
68+
project.setup_editor(ontology)
69+
warn.assert_called_once()
70+
project.connect_ontology.assert_called_once_with(ontology)

0 commit comments

Comments
 (0)