Skip to content

Commit a063f94

Browse files
Merge pull request #1943 from kili-technology/feature/lab-3743-aa-api-user-i-can-copy-a-workflow-v2-project-with-labels
feat(LAB-3743): move copy label to backend resolver
2 parents 0fb33dc + c511d7e commit a063f94

File tree

5 files changed

+103
-18
lines changed

5 files changed

+103
-18
lines changed

src/kili/adapters/kili_api_gateway/label/operations.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,10 @@ def get_annotations_partial_query(
153153
{annotation_fragment}
154154
{inline_fragments}
155155
"""
156+
157+
158+
GQL_COPY_LABELS = """
159+
mutation CopyLabels($data: CopyLabelsInput!) {
160+
copyLabels(data: $data)
161+
}
162+
"""

src/kili/adapters/kili_api_gateway/label/operations_mixin.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .formatters import load_label_json_fields
2525
from .mappers import append_label_data_mapper, append_to_labels_data_mapper, label_where_mapper
2626
from .operations import (
27+
GQL_COPY_LABELS,
2728
GQL_COUNT_LABELS,
2829
GQL_DELETE_LABELS,
2930
get_append_many_labels_mutation,
@@ -113,7 +114,7 @@ def list_labels_split(
113114
["content", "jsonContent", "resolution.width", "resolution.height"],
114115
)
115116
converter.patch_label_json_response(asset, label, label["annotations"])
116-
if "annotations" not in fields:
117+
if not any("annotations." in element for element in fields):
117118
label.pop("annotations")
118119
yield label
119120

@@ -200,3 +201,15 @@ def create_honeypot_label(
200201
}
201202
result = self.graphql_client.execute(query, variables)
202203
return result["data"]
204+
205+
def copy_labels(self, src_asset_id: str, dst_asset_id: str, project_id: str) -> bool:
206+
"""Copy labels from one asset to another."""
207+
variables = {
208+
"data": {
209+
"srcAssetId": src_asset_id,
210+
"dstAssetId": dst_asset_id,
211+
"projectId": project_id,
212+
},
213+
}
214+
self.graphql_client.execute(GQL_COPY_LABELS, variables)
215+
return True

src/kili/adapters/kili_api_gateway/project_workflow/mappers.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,3 @@ def update_step_mapper(data: Union[WorkflowStepCreate, WorkflowStepUpdate]) -> D
3838
"assignees": data["assignees"] if "assignees" in data else None,
3939
}
4040
return {k: v for k, v in step.items() if v is not None}
41-
42-
43-
def step_data_mapper(data: Dict) -> Dict:
44-
"""Build the GraphQL StepData variable to be sent in an operation."""
45-
return {
46-
"id": data["id"],
47-
"name": data["name"],
48-
"type": data["type"],
49-
}

src/kili/adapters/kili_api_gateway/project_workflow/operations_mixin.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from kili.domain.project import ProjectId
1010
from kili.exceptions import NotFound
1111

12-
from .mappers import project_input_mapper, step_data_mapper
12+
from .mappers import project_input_mapper
1313
from .operations import (
1414
GQL_GET_STEPS,
1515
get_update_project_workflow_mutation,
@@ -44,7 +44,7 @@ def update_project_workflow(
4444

4545
def get_steps(
4646
self,
47-
project_id: ProjectId,
47+
project_id: str,
4848
) -> List[Dict]:
4949
"""Get steps in a project workflow."""
5050
variables = {"where": {"id": project_id}, "first": 1, "skip": 0}
@@ -60,6 +60,5 @@ def get_steps(
6060
raise NotFound(
6161
f"project ID: {project_id}. The workflow v2 is not activated on this project."
6262
)
63-
steps_mapper = step_data_mapper(steps)
6463

65-
return [step for step in steps_mapper if step.get("isActivated") is True]
64+
return [step for step in steps if step.get("isActivated") is True]

src/kili/services/copy_project/__init__.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
import itertools
44
import logging
5+
import warnings
56
from typing import TYPE_CHECKING, Optional
67

78
from kili.adapters.kili_api_gateway.helpers.queries import QueryOptions
89
from kili.adapters.kili_api_gateway.project.types import CopyProjectInput
910
from kili.domain.asset import AssetFilters
1011
from kili.domain.label import LabelFilters
11-
from kili.domain.project import ProjectId
12+
from kili.domain.project import InputTypeEnum, ProjectId
1213

1314
if TYPE_CHECKING:
1415
from kili.client import Kili
@@ -22,9 +23,13 @@ class ProjectCopier: # pylint: disable=too-few-public-methods
2223
"description",
2324
"id",
2425
"dataConnections.id",
26+
"inputType",
27+
"jsonInterface",
28+
"workflowVersion",
2529
)
2630

2731
def __init__(self, kili: "Kili") -> None:
32+
"""Simple initialization."""
2833
self.disable_tqdm = False
2934
self.kili = kili
3035

@@ -62,6 +67,15 @@ def copy_project( # pylint: disable=too-many-arguments
6267
if src_project["dataConnections"] and copy_assets:
6368
raise NotImplementedError("Copying projects with cloud storage is not supported.")
6469

70+
if src_project["inputType"] in [
71+
InputTypeEnum.LLM_INSTR_FOLLOWING,
72+
InputTypeEnum.LLM_RLHF,
73+
InputTypeEnum.LLM_STATIC,
74+
]:
75+
raise NotImplementedError(
76+
f"Copying projects with input type {src_project['inputType']} is not supported."
77+
)
78+
6579
logger.info("Copying new project...")
6680

6781
new_project_id = self.kili.kili_api_gateway.copy_project(
@@ -72,7 +86,7 @@ def copy_project( # pylint: disable=too-many-arguments
7286
),
7387
)
7488

75-
logger.info(f"Created new project {new_project_id}")
89+
logger.info("Created new project %s", new_project_id)
7690

7791
self.kili.update_properties_in_project(
7892
project_id=new_project_id,
@@ -84,7 +98,18 @@ def copy_project( # pylint: disable=too-many-arguments
8498

8599
if copy_labels:
86100
logger.info("Copying labels...")
87-
self._copy_labels(from_project_id, new_project_id)
101+
if src_project["workflowVersion"] == "V2":
102+
self._copy_labels(from_project_id=from_project_id, new_project_id=new_project_id)
103+
else:
104+
warnings.warn(
105+
"Warning: "
106+
"copying a project of an old workflow mode may cause problems in asset status and assignation. "
107+
"Consider creating a new project instead.",
108+
DeprecationWarning,
109+
)
110+
self._copy_labels_legacy(
111+
from_project_id=from_project_id, new_project_id=new_project_id
112+
)
88113

89114
return new_project_id
90115

@@ -100,8 +125,58 @@ def _generate_project_title(self, src_title: str) -> str:
100125
i += 1
101126
return new_title
102127

103-
# pylint: disable=too-many-locals
104128
def _copy_labels(self, from_project_id: str, new_project_id: str) -> None:
129+
"""Method to copy labels from the source project to the new project : applicable for WFV2."""
130+
nb_labels_to_copy = self.kili.kili_api_gateway.count_labels(
131+
LabelFilters(project_id=ProjectId(from_project_id))
132+
)
133+
134+
if nb_labels_to_copy == 0:
135+
return
136+
137+
assets_src_project = self.kili.kili_api_gateway.list_assets(
138+
AssetFilters(project_id=ProjectId(from_project_id)),
139+
["id", "externalId", "labels.id"],
140+
QueryOptions(disable_tqdm=True),
141+
)
142+
143+
assets_dst_project = self.kili.kili_api_gateway.list_assets(
144+
AssetFilters(project_id=ProjectId(new_project_id)),
145+
["id", "externalId"],
146+
QueryOptions(disable_tqdm=True),
147+
)
148+
149+
# Iterate on assets of the source project
150+
# to copy labels to the new projectq
151+
assets_src_project_list = [
152+
asset
153+
for asset in assets_src_project
154+
if "labels" in asset and asset["labels"] and len(asset["labels"]) > 0
155+
]
156+
assets_dst_project_map = {asset["externalId"]: asset["id"] for asset in assets_dst_project}
157+
158+
for src_asset in assets_src_project_list:
159+
src_asset_id = src_asset["id"]
160+
dst_asset_id = assets_dst_project_map.get(src_asset["externalId"])
161+
if not dst_asset_id:
162+
raise ValueError(
163+
f"Asset with externalId {src_asset['externalId']} not found in new project {new_project_id}."
164+
)
165+
166+
self.kili.kili_api_gateway.copy_labels(
167+
src_asset_id=src_asset_id,
168+
dst_asset_id=dst_asset_id,
169+
project_id=new_project_id,
170+
)
171+
172+
# pylint: disable=too-many-locals
173+
def _copy_labels_legacy(self, from_project_id: str, new_project_id: str) -> None:
174+
"""Legacy mlethod to copy labels from the source project to the new project : applicable for WFV1.
175+
176+
!!! warning
177+
This method is deprecated and will be removed in the next major release.
178+
Asset with send back labels are not supported (status wise) and asset assignation is not supported.
179+
"""
105180
assets_new_project = self.kili.kili_api_gateway.list_assets(
106181
AssetFilters(project_id=ProjectId(new_project_id)),
107182
["id", "externalId"],

0 commit comments

Comments
 (0)