Skip to content

Commit 3e025f2

Browse files
authored
Merge pull request #442 from superannotateai/assign_items
adding assign items and un_assign items functionality
2 parents b427b21 + f34e46f commit 3e025f2

File tree

10 files changed

+257
-37
lines changed

10 files changed

+257
-37
lines changed

src/superannotate/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
import sys
33

44
sys.path.append(os.path.split(os.path.realpath(__file__))[0])
5+
from superannotate.lib.app.input_converters.conversion import (
6+
convert_json_version,
7+
) # noqa
8+
from superannotate.lib.app.input_converters.conversion import (
9+
convert_project_type,
10+
) # noqa
11+
512

613
import logging.config # noqa
714
import requests # noqa

src/superannotate/lib/app/analytics/aggregators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import copy
22
import json
3+
from dataclasses import dataclass
34
from pathlib import Path
45
from typing import List
56
from typing import Optional
67
from typing import Union
78

89
import lib.core as constances
910
import pandas as pd
10-
from dataclasses import dataclass
1111
from lib.app.exceptions import AppException
1212
from lib.core import ATTACHED_VIDEO_ANNOTATION_POSTFIX
1313
from lib.core import PIXEL_ANNOTATION_POSTFIX

src/superannotate/lib/app/interface/base_interface.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,27 @@ def __init__(
4646
f"CLI's superannotate init to generate default location config file."
4747
)
4848
config_repo = ConfigRepository(config_path)
49+
main_endpoint = config_repo.get_one("main_endpoint").value
50+
if not main_endpoint:
51+
main_endpoint = constants.BACKEND_URL
4952
token, host, ssl_verify = (
5053
Controller.validate_token(config_repo.get_one("token").value),
51-
config_repo.get_one("main_endpoint").value,
54+
main_endpoint,
5255
config_repo.get_one("ssl_verify").value,
5356
)
5457
self._host = host
5558
self._token = token
5659
self.controller = Controller(token, host, ssl_verify, version)
57-
58-
def __new__(cls, *args, **kwargs):
59-
obj = super().__new__(cls, *args, **kwargs)
60-
cls.REGISTRY.append(obj)
61-
return obj
60+
BaseInterfaceFacade.REGISTRY.append(self)
6261

6362
@property
64-
@abstractmethod
6563
def host(self):
66-
raise NotImplementedError
64+
return self._host
6765

6866
@property
69-
@abstractmethod
7067
def token(self):
71-
raise NotImplementedError
68+
return self._token
7269

73-
@property
74-
@abstractmethod
75-
def logger(self):
76-
raise NotImplementedError
7770

7871

7972
class Tracker:

src/superannotate/lib/app/interface/sdk_interface.py

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
logger = get_default_logger()
6262

6363

64+
6465
class SAClient(BaseInterfaceFacade, metaclass=TrackableMeta):
6566
def get_team_metadata(self):
6667
"""Returns team metadata
@@ -312,6 +313,7 @@ def rename_project(self, project: NotEmptyStr, new_name: NotEmptyStr):
312313
)
313314
return ProjectSerializer(response.data).serialize()
314315

316+
315317
def get_folder_metadata(self, project: NotEmptyStr, folder_name: NotEmptyStr):
316318
"""Returns folder metadata
317319
@@ -667,6 +669,53 @@ def delete_images(
667669
f"Images deleted in project {project_name}{'/' + folder_name if folder_name else ''}"
668670
)
669671

672+
def assign_items(
673+
self, project: Union[NotEmptyStr, dict], item_names: List[str], user: str
674+
):
675+
"""Assigns items to a user. The assignment role, QA or Annotator, will
676+
be deduced from the user's role in the project. The type of the objects` image, video or text
677+
will be deduced from the project type. With SDK, the user can be
678+
assigned to a role in the project with the share_project function.
679+
680+
:param project: project name or folder path (e.g., "project1/folder1")
681+
:type project: str
682+
:param item_names: list of item names to assign
683+
:type item_names: list of str
684+
:param user: user email
685+
:type user: str
686+
"""
687+
688+
project_name, folder_name = extract_project_folder(project)
689+
690+
response = self.controller.assign_items(
691+
project, folder_name, item_names, user
692+
)
693+
694+
if not response.errors:
695+
logger.info(f"Assign items to user {user}")
696+
else:
697+
raise AppException(response.errors)
698+
699+
def unassign_items(
700+
self, project: Union[NotEmptyStr, dict], item_names: List[NotEmptyStr]
701+
):
702+
"""Removes assignment of given items for all assignees. With SDK,
703+
the user can be assigned to a role in the project with the share_project
704+
function.
705+
706+
:param project: project name or folder path (e.g., "project1/folder1")
707+
:type project: str
708+
:param item_names: list of items to unassign
709+
:type item_names: list of str
710+
"""
711+
project_name, folder_name = extract_project_folder(project)
712+
713+
response = self.controller.un_assign_items(
714+
project_name=project_name, folder_name=folder_name, item_names=item_names
715+
)
716+
if response.errors:
717+
raise AppException(response.errors)
718+
670719
def assign_images(
671720
self, project: Union[NotEmptyStr, dict], image_names: List[str], user: str
672721
):
@@ -681,6 +730,14 @@ def assign_images(
681730
:param user: user email
682731
:type user: str
683732
"""
733+
734+
warning_msg = (
735+
"We're deprecating the assign_images function. Please use assign_items instead."
736+
"Learn more. \n"
737+
"https://superannotate.readthedocs.io/en/stable/superannotate.sdk.html#superannotate.assign_items"
738+
)
739+
logger.warning(warning_msg)
740+
warnings.warn(warning_msg, DeprecationWarning)
684741
project_name, folder_name = extract_project_folder(project)
685742
project = self.controller.get_project_metadata(project_name).data
686743

@@ -719,18 +776,26 @@ def assign_images(
719776
def unassign_images(
720777
self, project: Union[NotEmptyStr, dict], image_names: List[NotEmptyStr]
721778
):
722-
"""Removes assignment of given images for all assignees.With SDK,
779+
"""Removes assignment of given images for all assignees. With SDK,
723780
the user can be assigned to a role in the project with the share_project
724781
function.
725782
726783
:param project: project name or folder path (e.g., "project1/folder1")
727784
:type project: str
728-
:param image_names: list of image unassign
785+
:param image_names: list of images to unassign
729786
:type image_names: list of str
730787
"""
788+
789+
warning_msg = (
790+
"We're deprecating the unassign_images function. Please use unassign_items instead."
791+
"Learn more. \n"
792+
"https://superannotate.readthedocs.io/en/stable/superannotate.sdk.html#superannotate.unassign_items"
793+
)
794+
logger.warning(warning_msg)
795+
warnings.warn(warning_msg, DeprecationWarning)
731796
project_name, folder_name = extract_project_folder(project)
732797

733-
response = self.controller.un_assign_images(
798+
response = self.controller.un_assign_items(
734799
project_name=project_name, folder_name=folder_name, image_names=image_names
735800
)
736801
if response.errors:

src/superannotate/lib/core/reporter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ def __exit__(self, type, value, traceback):
4545

4646
def __del__(self):
4747
globs = globals()
48-
if "SESSIONS" in globs and globs["SESSIONS"].get(self.pk):
49-
del globs["SESSIONS"][self.pk]
48+
# if "SESSIONS" in globs and globs.get("SESSIONS", {}).get(self.pk):
49+
# del globs["SESSIONS"][self.pk]
5050

5151
@property
5252
def data(self):

src/superannotate/lib/core/response.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ def __init__(self, status: str = None, data: Union[dict, list] = None):
88
self._report = []
99
self._errors = []
1010

11+
def __str__(self):
12+
return f"Response object with status:{self.status}, data : {self.data}, errors: {self.errors} "
1113
@property
1214
def data(self):
1315
return self._data
@@ -30,7 +32,7 @@ def report_messages(self):
3032

3133
@property
3234
def status(self):
33-
return self.data
35+
return self._status
3436

3537
@status.setter
3638
def status(self, value):

src/superannotate/lib/core/serviceproviders.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@ def assign_images(
175175
):
176176
raise NotImplementedError
177177

178+
def assign_items(
179+
self,
180+
team_id: int,
181+
project_id: int,
182+
folder_name: str,
183+
user: str,
184+
item_names: list,
185+
):
186+
raise NotImplementedError
187+
178188
def get_bulk_images(
179189
self, project_id: int, team_id: int, folder_id: int, images: List[str]
180190
) -> List[dict]:
@@ -202,6 +212,16 @@ def un_assign_images(
202212
):
203213
raise NotImplementedError
204214

215+
def un_assign_items(
216+
self,
217+
team_id: int,
218+
project_id: int,
219+
folder_name: str,
220+
item_names: list,
221+
222+
):
223+
raise NotImplementedError
224+
205225
def un_share_project(
206226
self,
207227
team_id: int,

src/superannotate/lib/core/usecases/items.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
from lib.core.repositories import BaseReadOnlyRepository
1919
from lib.core.response import Response
2020
from lib.core.serviceproviders import SuperannotateServiceProvider
21+
from lib.core.usecases.base import BaseUseCase
22+
from superannotate.logger import get_default_logger
2123
from lib.core.usecases.base import BaseReportableUseCase
2224

2325

26+
logger = get_default_logger()
27+
2428
class GetItem(BaseReportableUseCase):
2529
def __init__(
2630
self,
@@ -187,6 +191,83 @@ def execute(self) -> Response:
187191
return self._response
188192

189193

194+
class AssignItemsUseCase(BaseUseCase):
195+
CHUNK_SIZE = 500
196+
197+
def __init__(
198+
self,
199+
service: SuperannotateServiceProvider,
200+
project: ProjectEntity,
201+
folder: FolderEntity,
202+
item_names: list,
203+
user: str,
204+
):
205+
super().__init__()
206+
self._project = project
207+
self._folder = folder
208+
self._item_names = item_names
209+
self._user = user
210+
self._service = service
211+
212+
def validate_user(self, ):
213+
214+
for c in self._project.users:
215+
if c["user_id"] == self._user:
216+
return True
217+
218+
raise AppValidationException(f"{self._user} is not a verified contributor for the {self._project.name}")
219+
220+
def execute(self):
221+
if self.is_valid():
222+
for i in range(0, len(self._item_names), self.CHUNK_SIZE):
223+
is_assigned = self._service.assign_items(
224+
team_id=self._project.team_id,
225+
project_id=self._project.id,
226+
folder_name=self._folder.name,
227+
user=self._user,
228+
item_names=self._item_names[i : i + self.CHUNK_SIZE], # noqa: E203
229+
)
230+
if not is_assigned:
231+
self._response.errors = AppException(
232+
f"Cant assign {', '.join(self._item_names[i: i + self.CHUNK_SIZE])}"
233+
)
234+
continue
235+
return self._response
236+
237+
238+
class UnAssignItemsUseCase(BaseUseCase):
239+
CHUNK_SIZE = 500
240+
def __init__(
241+
self,
242+
service: SuperannotateServiceProvider,
243+
project_entity: ProjectEntity,
244+
folder: FolderEntity,
245+
item_names: list,
246+
):
247+
super().__init__()
248+
self._project_entity = project_entity
249+
self._folder = folder
250+
self._item_names = item_names
251+
self._service = service
252+
253+
def execute(self):
254+
# todo handling to backend side
255+
for i in range(0, len(self._item_names), self.CHUNK_SIZE):
256+
is_un_assigned = self._service.un_assign_items(
257+
team_id=self._project_entity.team_id,
258+
project_id=self._project_entity.id,
259+
folder_name=self._folder.name,
260+
item_names=self._item_names[i : i + self.CHUNK_SIZE], # noqa: E203
261+
)
262+
if not is_un_assigned:
263+
self._response.errors = AppException(
264+
f"Cant un assign {', '.join(self._item_names[i: i + self.CHUNK_SIZE])}"
265+
)
266+
267+
return self._response
268+
269+
270+
190271
class AttachItems(BaseReportableUseCase):
191272
CHUNK_SIZE = 500
192273

0 commit comments

Comments
 (0)