Skip to content

Commit b708373

Browse files
committed
Bug fix
1 parent 23a0418 commit b708373

File tree

5 files changed

+99
-74
lines changed

5 files changed

+99
-74
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ ________
4949
.. autofunction:: superannotate.upload_annotations_from_folder_to_project
5050
.. autofunction:: superannotate.upload_preannotations_from_folder_to_project
5151
.. autofunction:: superannotate.share_project
52+
.. autofunction:: superannotate.add_contributors_to_project
5253
.. autofunction:: superannotate.get_project_settings
5354
.. autofunction:: superannotate.set_project_default_image_quality_in_editor
5455
.. autofunction:: superannotate.get_project_workflow

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from lib.core.types import MLModel
4242
from lib.core.types import Project
4343
from lib.infrastructure.controller import Controller
44+
from pydantic import conlist
4445
from pydantic import parse_obj_as
4546
from pydantic import StrictBool
4647
from tqdm import tqdm
@@ -2904,7 +2905,7 @@ def validate_annotations(
29042905
@Trackable
29052906
@validate_arguments
29062907
def add_contributors_to_project(
2907-
project: NotEmptyStr, emails: List[EmailStr], role: AnnotatorRole
2908+
project: NotEmptyStr, emails: conlist(EmailStr, min_items=1), role: AnnotatorRole
29082909
) -> Tuple[List[str], List[str]]:
29092910
"""Add contributors to project.
29102911
@@ -2930,7 +2931,7 @@ def add_contributors_to_project(
29302931

29312932
@Trackable
29322933
@validate_arguments
2933-
def invite_contributors_to_team(emails: List[EmailStr], admin: StrictBool = False) -> Tuple[List[str], List[str]]:
2934+
def invite_contributors_to_team(emails: conlist(EmailStr, min_items=1), admin: StrictBool = False) -> Tuple[List[str], List[str]]:
29342935
"""Invites contributors to the team.
29352936
29362937
:param emails: list of contributor emails

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def validate(cls, value: Union[str]) -> Union[str]:
4949
value = value[: cls.curtail_length]
5050
if value.lower() not in [role.lower() for role in cls.ANNOTATOR_ROLES]:
5151
raise TypeError(
52-
f"Available statuses is {', '.join(AnnotatorRole)}. "
52+
f"Invalid user role provided. Please specify one of {', '.join(cls.ANNOTATOR_ROLES)}. "
5353
)
5454
return value
5555

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from abc import ABC
2+
from abc import ABCMeta
23
from abc import abstractmethod
34
from typing import Iterable
5+
from typing import List
46

57
from lib.core.exceptions import AppValidationException
68
from lib.core.reporter import Reporter
@@ -56,7 +58,27 @@ def execute(self) -> Iterable:
5658
raise NotImplementedError
5759

5860

59-
class BaseReportableUseCae(BaseUseCase):
61+
class BaseReportableUseCae(BaseUseCase, metaclass=ABCMeta):
6062
def __init__(self, reporter: Reporter):
6163
super().__init__()
6264
self.reporter = reporter
65+
66+
67+
class BaseUserBasedUseCase(BaseReportableUseCae, metaclass=ABCMeta):
68+
"""
69+
class contain validation of unique emails
70+
"""
71+
def __init__(self, reporter: Reporter, emails: List[str]):
72+
super().__init__(reporter)
73+
self._emails = emails
74+
75+
def validate_emails(self):
76+
emails_to_add = set()
77+
duplicated_emails = [
78+
email for email in self._emails if email not in emails_to_add and not emails_to_add.add(email)
79+
]
80+
if duplicated_emails:
81+
self.reporter.log_info(
82+
f"Dropping duplicates. Found {len(duplicated_emails)}/{len(self._emails)} unique users."
83+
)
84+
self._emails = emails_to_add

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

Lines changed: 71 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from lib.core.serviceproviders import SuerannotateServiceProvider
2323
from lib.core.usecases.base import BaseReportableUseCae
2424
from lib.core.usecases.base import BaseUseCase
25+
from lib.core.usecases.base import BaseUserBasedUseCase
2526
from requests.exceptions import RequestException
2627

2728
logger = logging.getLogger("root")
@@ -941,7 +942,7 @@ def execute(self):
941942
return self._response
942943

943944

944-
class AddContributorsToProject(BaseReportableUseCae):
945+
class AddContributorsToProject(BaseUserBasedUseCase):
945946
"""
946947
Returns tuple of lists (added, skipped)
947948
"""
@@ -955,10 +956,9 @@ def __init__(
955956
role: str,
956957
service: SuerannotateServiceProvider,
957958
):
958-
super().__init__(reporter)
959+
super().__init__(reporter, emails)
959960
self._team = team
960961
self._project = project
961-
self._emails = emails
962962
self._role = role
963963
self._service = service
964964

@@ -967,47 +967,48 @@ def user_role(self):
967967
return constances.UserRole.get_value(self._role)
968968

969969
def execute(self):
970-
team_users = set()
971-
project_users = {user["user_id"] for user in self._project.users}
972-
for user in self._team.users:
973-
if user.user_role > constances.UserRole.ADMIN.value:
974-
team_users.add(user.email)
975-
# collecting pending team users which is not admin
976-
for user in self._team.pending_invitations:
977-
if user["user_role"] > constances.UserRole.ADMIN.value:
978-
team_users.add(user["email"])
979-
# collecting pending project users which is not admin
980-
for user in self._project.unverified_users:
981-
if user["user_role"] > constances.UserRole.ADMIN.value:
982-
project_users.add(user["email"])
983-
984-
to_add = list(team_users.intersection(self._emails) - project_users)
985-
to_skip = list(set(self._emails).difference(to_add))
986-
987-
if to_skip:
988-
self.reporter.log_warning(
989-
f"Skipped {len(to_skip)}/{len(self._emails)} "
990-
"contributors that are out of the team scope or already have access to the project."
991-
)
992-
if to_add:
993-
response = self._service.share_project_bulk(
994-
team_id=self._team.uuid,
995-
project_id=self._project.uuid,
996-
users=[
997-
dict(user_id=user_id, user_role=self.user_role)
998-
for user_id in to_add
999-
],
1000-
)
1001-
if response and not response.get("invalidUsers"):
1002-
self.reporter.log_info(
1003-
f"Added {len(to_add)}/{len(self._emails)} "
1004-
f"contributors to the project {self._project.name} with the {self.user_role} role."
970+
if self.is_valid():
971+
team_users = set()
972+
project_users = {user["user_id"] for user in self._project.users}
973+
for user in self._team.users:
974+
if user.user_role > constances.UserRole.ADMIN.value:
975+
team_users.add(user.email)
976+
# collecting pending team users which is not admin
977+
for user in self._team.pending_invitations:
978+
if user["user_role"] > constances.UserRole.ADMIN.value:
979+
team_users.add(user["email"])
980+
# collecting pending project users which is not admin
981+
for user in self._project.unverified_users:
982+
if user["user_role"] > constances.UserRole.ADMIN.value:
983+
project_users.add(user["email"])
984+
985+
to_add = list(team_users.intersection(self._emails) - project_users)
986+
to_skip = list(set(self._emails).difference(to_add))
987+
988+
if to_skip:
989+
self.reporter.log_warning(
990+
f"Skipped {len(to_skip)}/{len(self._emails)} "
991+
"contributors that are out of the team scope or already have access to the project."
1005992
)
1006-
self._response.data = to_add, to_skip
1007-
return self._response
993+
if to_add:
994+
response = self._service.share_project_bulk(
995+
team_id=self._team.uuid,
996+
project_id=self._project.uuid,
997+
users=[
998+
dict(user_id=user_id, user_role=self.user_role)
999+
for user_id in to_add
1000+
],
1001+
)
1002+
if response and not response.get("invalidUsers"):
1003+
self.reporter.log_info(
1004+
f"Added {len(to_add)}/{len(self._emails)} "
1005+
f"contributors to the project {self._project.name} with the {self._role} role."
1006+
)
1007+
self._response.data = to_add, to_skip
1008+
return self._response
10081009

10091010

1010-
class InviteContributorsToTeam(BaseReportableUseCae):
1011+
class InviteContributorsToTeam(BaseUserBasedUseCase):
10111012
"""
10121013
Returns tuple of lists (added, skipped)
10131014
"""
@@ -1020,42 +1021,42 @@ def __init__(
10201021
set_admin: bool,
10211022
service: SuerannotateServiceProvider,
10221023
):
1023-
super().__init__(reporter)
1024+
super().__init__(reporter, emails)
10241025
self._team = team
1025-
self._emails = emails
10261026
self._set_admin = set_admin
10271027
self._service = service
10281028

10291029
def execute(self):
1030-
team_users = {user.email for user in self._team.users}
1031-
# collecting pending team users
1032-
team_users.update({user["email"] for user in self._team.pending_invitations})
1030+
if self.is_valid():
1031+
team_users = {user.email for user in self._team.users}
1032+
# collecting pending team users
1033+
team_users.update({user["email"] for user in self._team.pending_invitations})
10331034

1034-
emails = set(self._emails)
1035+
emails = set(self._emails)
10351036

1036-
to_skip = list(emails.intersection(team_users))
1037-
to_add = list(emails.difference(to_skip))
1037+
to_skip = list(emails.intersection(team_users))
1038+
to_add = list(emails.difference(to_skip))
10381039

1039-
if to_skip:
1040-
self.reporter.log_warning(
1041-
f"Found {len(to_skip)}/{len(self._emails)} existing members of the team."
1042-
)
1043-
if to_add:
1044-
invited, failed = self._service.invite_contributors(
1045-
team_id=self._team.uuid,
1046-
# REMINDER UserRole.VIEWER is the contributor for the teams
1047-
team_role=constances.UserRole.ADMIN.value if self._set_admin else constances.UserRole.VIEWER.value,
1048-
emails=to_add
1049-
)
1050-
if invited:
1051-
self.reporter.log_info(
1052-
f"Sent team {'admin' if self._set_admin else 'contributor'} invitations"
1053-
f" to {len(to_add)}/{len(self._emails)} users."
1040+
if to_skip:
1041+
self.reporter.log_warning(
1042+
f"Found {len(to_skip)}/{len(self._emails)} existing members of the team."
10541043
)
1055-
if failed:
1056-
self.reporter.log_info(
1057-
f"Skipped team {'admin' if self._set_admin else 'contributor'} "
1058-
f"invitations for {len(failed)}/{len(self._emails)} users."
1044+
if to_add:
1045+
invited, failed = self._service.invite_contributors(
1046+
team_id=self._team.uuid,
1047+
# REMINDER UserRole.VIEWER is the contributor for the teams
1048+
team_role=constances.UserRole.ADMIN.value if self._set_admin else constances.UserRole.VIEWER.value,
1049+
emails=to_add
10591050
)
1060-
self._response.data = to_add, to_skip
1061-
return self._response
1051+
if invited:
1052+
self.reporter.log_info(
1053+
f"Sent team {'admin' if self._set_admin else 'contributor'} invitations"
1054+
f" to {len(to_add)}/{len(self._emails)} users."
1055+
)
1056+
if failed:
1057+
self.reporter.log_info(
1058+
f"Skipped team {'admin' if self._set_admin else 'contributor'} "
1059+
f"invitations for {len(failed)}/{len(self._emails)} users."
1060+
)
1061+
self._response.data = to_add, to_skip
1062+
return self._response

0 commit comments

Comments
 (0)