Skip to content

Commit 0bbd7c2

Browse files
authored
Vb/fix ontology leaks plt 1379 (#1814)
1 parent bfed07e commit 0bbd7c2

File tree

10 files changed

+260
-167
lines changed

10 files changed

+260
-167
lines changed

libs/labelbox/src/labelbox/schema/bulk_import_request.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -787,9 +787,7 @@ def validate_feature_schemas(
787787
# A union with custom construction logic to improve error messages
788788
class NDClassification(
789789
SpecialUnion,
790-
Type[ # type: ignore
791-
Union[NDText, NDRadio, NDChecklist]
792-
],
790+
Type[Union[NDText, NDRadio, NDChecklist]], # type: ignore
793791
): ...
794792

795793

@@ -979,9 +977,7 @@ class NDTool(
979977

980978
class NDAnnotation(
981979
SpecialUnion,
982-
Type[ # type: ignore
983-
Union[NDTool, NDClassification]
984-
],
980+
Type[Union[NDTool, NDClassification]], # type: ignore
985981
):
986982
@classmethod
987983
def build(cls: Any, data) -> "NDBase":

libs/labelbox/src/labelbox/schema/labeling_service_dashboard.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ def __init__(self, **kwargs):
8484
super().__init__(**kwargs)
8585
if not self.client.enable_experimental:
8686
raise RuntimeError(
87-
"Please enable experimental in client to use LabelingService")
87+
"Please enable experimental in client to use LabelingService"
88+
)
8889

8990
@property
9091
def service_type(self):
@@ -97,20 +98,28 @@ def service_type(self):
9798
if self.editor_task_type is None:
9899
return sentence_case(self.media_type.value)
99100

100-
if (self.editor_task_type == EditorTaskType.OfflineModelChatEvaluation
101-
and self.media_type == MediaType.Conversational):
101+
if (
102+
self.editor_task_type == EditorTaskType.OfflineModelChatEvaluation
103+
and self.media_type == MediaType.Conversational
104+
):
102105
return "Offline chat evaluation"
103106

104-
if (self.editor_task_type == EditorTaskType.ModelChatEvaluation and
105-
self.media_type == MediaType.Conversational):
107+
if (
108+
self.editor_task_type == EditorTaskType.ModelChatEvaluation
109+
and self.media_type == MediaType.Conversational
110+
):
106111
return "Live chat evaluation"
107112

108-
if (self.editor_task_type == EditorTaskType.ResponseCreation and
109-
self.media_type == MediaType.Text):
113+
if (
114+
self.editor_task_type == EditorTaskType.ResponseCreation
115+
and self.media_type == MediaType.Text
116+
):
110117
return "Response creation"
111118

112-
if (self.media_type == MediaType.LLMPromptCreation or
113-
self.media_type == MediaType.LLMPromptResponseCreation):
119+
if (
120+
self.media_type == MediaType.LLMPromptCreation
121+
or self.media_type == MediaType.LLMPromptResponseCreation
122+
):
114123
return "Prompt response creation"
115124

116125
return sentence_case(self.media_type.value)
@@ -154,7 +163,8 @@ def get_all(
154163
pageInfo { endCursor }
155164
}
156165
}
157-
""")
166+
"""
167+
)
158168
else:
159169
template = Template(
160170
"""query SearchProjectsPyApi($$first: Int, $$from: String) {
@@ -164,11 +174,13 @@ def get_all(
164174
pageInfo { endCursor }
165175
}
166176
}
167-
""")
177+
"""
178+
)
168179
query_str = template.substitute(
169180
labeling_dashboard_selections=GRAPHQL_QUERY_SELECTIONS,
170181
search_query=build_search_filter(search_query)
171-
if search_query else None,
182+
if search_query
183+
else None,
172184
)
173185
params: Dict[str, Union[str, int]] = {}
174186

@@ -186,7 +198,7 @@ def convert_to_labeling_service_dashboard(client, data):
186198
experimental=True,
187199
)
188200

189-
@model_validator(mode='before')
201+
@model_validator(mode="before")
190202
def convert_boost_data(cls, data):
191203
if "boostStatus" in data:
192204
data["status"] = LabelingServiceStatus(data.pop("boostStatus"))

libs/labelbox/tests/conftest.py

Lines changed: 145 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import re
88
import uuid
99
import time
10+
from labelbox.schema.project import Project
1011
import requests
12+
from labelbox.schema.ontology import Ontology
1113
import pytest
1214
from types import SimpleNamespace
1315
from typing import Type
@@ -23,21 +25,11 @@
2325
from labelbox.schema.queue_mode import QueueMode
2426
from labelbox import Client
2527

26-
from labelbox import Dataset, DataRow
2728
from labelbox import LabelingFrontend
28-
from labelbox import OntologyBuilder, Tool, Option, Classification, MediaType
29-
from labelbox.orm import query
30-
from labelbox.pagination import PaginatedCollection
29+
from labelbox import OntologyBuilder, Tool, Option, Classification
3130
from labelbox.schema.annotation_import import LabelImport
32-
from labelbox.schema.catalog import Catalog
3331
from labelbox.schema.enums import AnnotationImportState
34-
from labelbox.schema.invite import Invite
35-
from labelbox.schema.quality_mode import QualityMode
36-
from labelbox.schema.queue_mode import QueueMode
37-
from labelbox.schema.user import User
3832
from labelbox.exceptions import LabelboxError
39-
from contextlib import suppress
40-
from labelbox import Client
4133

4234
IMG_URL = "https://picsum.photos/200/300.jpg"
4335
MASKABLE_IMG_URL = "https://storage.googleapis.com/labelbox-datasets/image_sample_data/2560px-Kitano_Street_Kobe01s5s4110.jpeg"
@@ -638,17 +630,22 @@ def organization(client):
638630
def configured_project_with_label(
639631
client,
640632
rand_gen,
641-
image_url,
642-
project,
643633
dataset,
644634
data_row,
645635
wait_for_label_processing,
636+
teardown_helpers,
646637
):
647638
"""Project with a connected dataset, having one datarow
639+
648640
Project contains an ontology with 1 bbox tool
649641
Additionally includes a create_label method for any needed extra labels
650642
One label is already created and yielded when using fixture
651643
"""
644+
project = client.create_project(
645+
name=rand_gen(str),
646+
queue_mode=QueueMode.Batch,
647+
media_type=MediaType.Image,
648+
)
652649
project._wait_until_data_rows_are_processed(
653650
data_row_ids=[data_row.uid],
654651
wait_processing_max_seconds=DATA_ROW_PROCESSING_WAIT_TIMEOUT_SECONDS,
@@ -666,8 +663,7 @@ def configured_project_with_label(
666663
)
667664
yield [project, dataset, data_row, label]
668665

669-
for label in project.labels():
670-
label.delete()
666+
teardown_helpers.teardown_project_labels_ontology_feature_schemas(project)
671667

672668

673669
def _create_label(project, data_row, ontology, wait_for_label_processing):
@@ -736,13 +732,23 @@ def big_dataset(dataset: Dataset):
736732

737733
@pytest.fixture
738734
def configured_batch_project_with_label(
739-
project, dataset, data_row, wait_for_label_processing
735+
client,
736+
dataset,
737+
data_row,
738+
wait_for_label_processing,
739+
rand_gen,
740+
teardown_helpers,
740741
):
741742
"""Project with a batch having one datarow
742743
Project contains an ontology with 1 bbox tool
743744
Additionally includes a create_label method for any needed extra labels
744745
One label is already created and yielded when using fixture
745746
"""
747+
project = client.create_project(
748+
name=rand_gen(str),
749+
queue_mode=QueueMode.Batch,
750+
media_type=MediaType.Image,
751+
)
746752
data_rows = [dr.uid for dr in list(dataset.data_rows())]
747753
project._wait_until_data_rows_are_processed(
748754
data_row_ids=data_rows, sleep_interval=3
@@ -757,18 +763,27 @@ def configured_batch_project_with_label(
757763

758764
yield [project, dataset, data_row, label]
759765

760-
for label in project.labels():
761-
label.delete()
766+
teardown_helpers.teardown_project_labels_ontology_feature_schemas(project)
762767

763768

764769
@pytest.fixture
765770
def configured_batch_project_with_multiple_datarows(
766-
project, dataset, data_rows, wait_for_label_processing
771+
client,
772+
dataset,
773+
data_rows,
774+
wait_for_label_processing,
775+
rand_gen,
776+
teardown_helpers,
767777
):
768778
"""Project with a batch having multiple datarows
769779
Project contains an ontology with 1 bbox tool
770780
Additionally includes a create_label method for any needed extra labels
771781
"""
782+
project = client.create_project(
783+
name=rand_gen(str),
784+
queue_mode=QueueMode.Batch,
785+
media_type=MediaType.Image,
786+
)
772787
global_keys = [dr.global_key for dr in data_rows]
773788

774789
batch_name = f"batch {uuid.uuid4()}"
@@ -780,26 +795,7 @@ def configured_batch_project_with_multiple_datarows(
780795

781796
yield [project, dataset, data_rows]
782797

783-
for label in project.labels():
784-
label.delete()
785-
786-
787-
@pytest.fixture
788-
def configured_batch_project_for_labeling_service(
789-
project, data_row_and_global_key
790-
):
791-
"""Project with a batch having multiple datarows
792-
Project contains an ontology with 1 bbox tool
793-
Additionally includes a create_label method for any needed extra labels
794-
"""
795-
global_keys = [data_row_and_global_key[1]]
796-
797-
batch_name = f"batch {uuid.uuid4()}"
798-
project.create_batch(batch_name, global_keys=global_keys)
799-
800-
_setup_ontology(project)
801-
802-
yield project
798+
teardown_helpers.teardown_project_labels_ontology_feature_schemas(project)
803799

804800

805801
# NOTE this is nice heuristics, also there is this logic _wait_until_data_rows_are_processed in Project
@@ -1062,7 +1058,7 @@ def project_with_empty_ontology(project):
10621058

10631059
@pytest.fixture
10641060
def configured_project_with_complex_ontology(
1065-
client, initial_dataset, rand_gen, image_url
1061+
client, initial_dataset, rand_gen, image_url, teardown_helpers
10661062
):
10671063
project = client.create_project(
10681064
name=rand_gen(str),
@@ -1127,7 +1123,7 @@ def configured_project_with_complex_ontology(
11271123
project.setup(editor, ontology.asdict())
11281124

11291125
yield [project, data_row]
1130-
project.delete()
1126+
teardown_helpers.teardown_project_labels_ontology_feature_schemas(project)
11311127

11321128

11331129
@pytest.fixture
@@ -1147,12 +1143,13 @@ def valid_model_id():
11471143

11481144
@pytest.fixture
11491145
def requested_labeling_service(
1150-
rand_gen,
1151-
live_chat_evaluation_project_with_new_dataset,
1152-
chat_evaluation_ontology,
1153-
model_config,
1146+
rand_gen, client, chat_evaluation_ontology, model_config, teardown_helpers
11541147
):
1155-
project = live_chat_evaluation_project_with_new_dataset
1148+
project_name = f"test-model-evaluation-project-{rand_gen(str)}"
1149+
dataset_name = f"test-model-evaluation-dataset-{rand_gen(str)}"
1150+
project = client.create_model_evaluation_project(
1151+
name=project_name, dataset_name=dataset_name, data_row_count=1
1152+
)
11561153
project.connect_ontology(chat_evaluation_ontology)
11571154

11581155
project.upsert_instructions("tests/integration/media/sample_pdf.pdf")
@@ -1164,3 +1161,105 @@ def requested_labeling_service(
11641161
labeling_service.request()
11651162

11661163
yield project, project.get_labeling_service()
1164+
1165+
teardown_helpers.teardown_project_labels_ontology_feature_schemas(project)
1166+
1167+
1168+
class TearDownHelpers:
1169+
@staticmethod
1170+
def teardown_project_labels_ontology_feature_schemas(project: Project):
1171+
"""
1172+
Call this function to release project, labels, ontology and feature schemas in fixture teardown
1173+
1174+
NOTE: exception handling is not required as this is a fixture teardown
1175+
"""
1176+
ontology = project.ontology()
1177+
ontology_id = ontology.uid
1178+
client = project.client
1179+
classification_feature_schema_ids = [
1180+
feature["featureSchemaId"]
1181+
for feature in ontology.normalized["classifications"]
1182+
]
1183+
tool_feature_schema_ids = [
1184+
feature["featureSchemaId"]
1185+
for feature in ontology.normalized["tools"]
1186+
]
1187+
1188+
feature_schema_ids = (
1189+
classification_feature_schema_ids + tool_feature_schema_ids
1190+
)
1191+
labels = list(project.labels())
1192+
for label in labels:
1193+
label.delete()
1194+
1195+
project.delete()
1196+
client.delete_unused_ontology(ontology_id)
1197+
for feature_schema_id in feature_schema_ids:
1198+
try:
1199+
project.client.delete_unused_feature_schema(feature_schema_id)
1200+
except LabelboxError as e:
1201+
print(
1202+
f"Failed to delete feature schema {feature_schema_id}: {e}"
1203+
)
1204+
1205+
@staticmethod
1206+
def teardown_ontology_feature_schemas(ontology: Ontology):
1207+
"""
1208+
Call this function to release project, labels, ontology and feature schemas in fixture teardown
1209+
1210+
NOTE: exception handling is not required as this is a fixture teardown
1211+
"""
1212+
ontology_id = ontology.uid
1213+
client = ontology.client
1214+
classification_feature_schema_ids = [
1215+
feature["featureSchemaId"]
1216+
for feature in ontology.normalized["classifications"]
1217+
] + [
1218+
option["featureSchemaId"]
1219+
for feature in ontology.normalized["classifications"]
1220+
for option in feature.get("options", [])
1221+
]
1222+
1223+
tool_feature_schema_ids = (
1224+
[
1225+
feature["featureSchemaId"]
1226+
for feature in ontology.normalized["tools"]
1227+
]
1228+
+ [
1229+
classification["featureSchemaId"]
1230+
for tool in ontology.normalized["tools"]
1231+
for classification in tool.get("classifications", [])
1232+
]
1233+
+ [
1234+
option["featureSchemaId"]
1235+
for tool in ontology.normalized["tools"]
1236+
for classification in tool.get("classifications", [])
1237+
for option in classification.get("options", [])
1238+
]
1239+
)
1240+
1241+
feature_schema_ids = (
1242+
classification_feature_schema_ids + tool_feature_schema_ids
1243+
)
1244+
1245+
client.delete_unused_ontology(ontology_id)
1246+
for feature_schema_id in feature_schema_ids:
1247+
try:
1248+
project.client.delete_unused_feature_schema(feature_schema_id)
1249+
except LabelboxError as e:
1250+
print(
1251+
f"Failed to delete feature schema {feature_schema_id}: {e}"
1252+
)
1253+
1254+
1255+
class ModuleTearDownHelpers(TearDownHelpers): ...
1256+
1257+
1258+
@pytest.fixture
1259+
def teardown_helpers():
1260+
return TearDownHelpers()
1261+
1262+
1263+
@pytest.fixture(scope="module")
1264+
def module_teardown_helpers():
1265+
return TearDownHelpers()

0 commit comments

Comments
 (0)