Skip to content

Commit 45fb47c

Browse files
authored
Add submit_external_metrics (#2042)
1 parent b1b5f7c commit 45fb47c

3 files changed

Lines changed: 171 additions & 0 deletions

File tree

libs/labelbox/src/labelbox/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@
2222
)
2323
from labelbox.schema.dataset import Dataset
2424
from labelbox.schema.enums import AnnotationImportState
25+
from labelbox.schema.project_sync import (
26+
AutoQA,
27+
AutoQaStatus,
28+
CustomScore,
29+
GranularRating,
30+
ProjectSyncEntry,
31+
ProjectSyncLabel,
32+
ProjectSyncResult,
33+
ProjectSyncReview,
34+
ReviewedBy,
35+
SubmittedBy,
36+
)
2537
from labelbox.schema.export_task import (
2638
BufferedJsonConverterOutput,
2739
ExportTask,

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
ProjectExportFilters,
3838
build_filters,
3939
)
40+
from labelbox.schema.project_sync import (
41+
ProjectSyncEntry,
42+
ProjectSyncResult,
43+
_to_gql_input,
44+
)
4045
from labelbox.schema.export_params import ProjectExportParams
4146
from labelbox.schema.export_task import ExportTask
4247
from labelbox.schema.identifiable import DataRowIdentifier
@@ -1001,6 +1006,39 @@ def create_batches(
10011006

10021007
return CreateBatchesTask(self.client, self.uid, batch_ids, task_ids)
10031008

1009+
def sync_external_project(
1010+
self,
1011+
entries: List[ProjectSyncEntry],
1012+
) -> ProjectSyncResult:
1013+
"""Syncs external project data — labels, metrics, and workflow state.
1014+
1015+
Processing is asynchronous. The returned submission ID can be used
1016+
to track the progress of the sync operation.
1017+
1018+
Args:
1019+
entries: A list of ProjectSyncEntry objects.
1020+
1021+
Returns:
1022+
A ProjectSyncResult containing the submission ID.
1023+
"""
1024+
mutation_str = """mutation syncExternalProjectPyApi($input: SyncExternalProjectInput!) {
1025+
syncExternalProject(input: $input) {
1026+
submissionId
1027+
}
1028+
}"""
1029+
1030+
params = {
1031+
"input": {
1032+
"projectId": self.uid,
1033+
"entries": [_to_gql_input(e) for e in entries],
1034+
}
1035+
}
1036+
1037+
response = self.client.execute(mutation_str, params)
1038+
payload = response["syncExternalProject"]
1039+
1040+
return ProjectSyncResult(submission_id=payload["submissionId"])
1041+
10041042
def create_batches_from_dataset(
10051043
self,
10061044
name_prefix: str,
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from enum import Enum
2+
from typing import Any, Dict, List, Optional
3+
4+
from pydantic import BaseModel
5+
6+
7+
class AutoQaStatus(str, Enum):
8+
Approve = "Approve"
9+
Reject = "Reject"
10+
Neutral = "Neutral"
11+
12+
13+
class SubmittedBy(BaseModel):
14+
email: str
15+
16+
17+
class CustomScore(BaseModel):
18+
name: str
19+
value: float
20+
21+
22+
class AutoQA(BaseModel):
23+
status: AutoQaStatus
24+
score: Optional[float] = None
25+
feedback: Optional[str] = None
26+
custom_scores: Optional[List[CustomScore]] = None
27+
28+
29+
class ProjectSyncLabel(BaseModel):
30+
submitted_by: SubmittedBy
31+
auto_qa: Optional[AutoQA] = None
32+
seconds_to_completion: Optional[float] = None
33+
submitted_on: Optional[str] = None
34+
35+
36+
class ReviewedBy(BaseModel):
37+
email: str
38+
39+
40+
class GranularRating(BaseModel):
41+
score: int
42+
comment: Optional[str] = None
43+
44+
45+
class ProjectSyncReview(BaseModel):
46+
reviewed_by: ReviewedBy
47+
rating: Optional[GranularRating] = None
48+
custom_scores: Optional[List[CustomScore]] = None
49+
50+
51+
class ProjectSyncEntry(BaseModel):
52+
task_id: str
53+
content_url: Optional[str] = None
54+
label: Optional[ProjectSyncLabel] = None
55+
review: Optional[ProjectSyncReview] = None
56+
queue_type: Optional[str] = None
57+
58+
59+
class ProjectSyncResult(BaseModel):
60+
submission_id: str
61+
62+
63+
def _to_gql_input(entry: ProjectSyncEntry) -> Dict[str, Any]:
64+
"""Convert a ProjectSyncEntry to a camelCase dict matching the GQL schema."""
65+
result: Dict[str, Any] = {"taskId": entry.task_id}
66+
67+
if entry.content_url is not None:
68+
result["contentUrl"] = entry.content_url
69+
70+
if entry.label is not None:
71+
label: Dict[str, Any] = {
72+
"submittedBy": {"email": entry.label.submitted_by.email},
73+
}
74+
75+
if entry.label.auto_qa is not None:
76+
auto_qa: Dict[str, Any] = {
77+
"status": entry.label.auto_qa.status.value,
78+
}
79+
if entry.label.auto_qa.score is not None:
80+
auto_qa["score"] = entry.label.auto_qa.score
81+
if entry.label.auto_qa.feedback is not None:
82+
auto_qa["feedback"] = entry.label.auto_qa.feedback
83+
if entry.label.auto_qa.custom_scores is not None:
84+
auto_qa["customScores"] = [
85+
{"name": cs.name, "value": cs.value}
86+
for cs in entry.label.auto_qa.custom_scores
87+
]
88+
label["autoQA"] = auto_qa
89+
90+
if entry.label.seconds_to_completion is not None:
91+
label["secondsToCompletion"] = entry.label.seconds_to_completion
92+
93+
if entry.label.submitted_on is not None:
94+
label["submittedOn"] = entry.label.submitted_on
95+
96+
result["label"] = label
97+
elif "label" in entry.model_fields_set:
98+
result["label"] = None
99+
100+
if entry.review is not None:
101+
review: Dict[str, Any] = {
102+
"reviewedBy": {"email": entry.review.reviewed_by.email},
103+
}
104+
if entry.review.rating is not None:
105+
rating: Dict[str, Any] = {"score": entry.review.rating.score}
106+
if entry.review.rating.comment is not None:
107+
rating["comment"] = entry.review.rating.comment
108+
review["rating"] = rating
109+
if entry.review.custom_scores is not None:
110+
review["customScores"] = [
111+
{"name": cs.name, "value": cs.value}
112+
for cs in entry.review.custom_scores
113+
]
114+
result["review"] = review
115+
116+
if entry.queue_type is not None:
117+
result["queueType"] = entry.queue_type
118+
elif "queue_type" in entry.model_fields_set:
119+
result["queueType"] = None
120+
121+
return result

0 commit comments

Comments
 (0)