Skip to content

Commit cf3bd23

Browse files
authored
Merge pull request #572 from superannotateai/1392_implementation
fix video project clone issue and refactoring
2 parents 3f54ae6 + 0dee346 commit cf3bd23

File tree

16 files changed

+331
-472
lines changed

16 files changed

+331
-472
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ def __init__(self, token: TokenStr = None, config_path: str = None):
3434
config = ConfigEntity(SA_TOKEN=token)
3535
elif config_path:
3636
config_path = Path(config_path)
37-
if not Path(config_path).is_file() or not os.access(config_path, os.R_OK):
37+
if not Path(config_path).is_file() or not os.access(
38+
config_path, os.R_OK
39+
):
3840
raise AppException(
3941
f"SuperAnnotate config file {str(config_path)} not found."
4042
)
@@ -77,8 +79,12 @@ def _retrieve_configs_from_json(path: Path) -> typing.Union[ConfigEntity]:
7779
config = ConfigEntity(SA_TOKEN=token)
7880
except pydantic.ValidationError:
7981
raise pydantic.ValidationError(
80-
[pydantic.error_wrappers.ErrorWrapper(ValueError("Invalid token."), loc='token')],
81-
model=ConfigEntity
82+
[
83+
pydantic.error_wrappers.ErrorWrapper(
84+
ValueError("Invalid token."), loc="token"
85+
)
86+
],
87+
model=ConfigEntity,
8288
)
8389
host = json_data.get("main_endpoint")
8490
verify_ssl = json_data.get("ssl_verify")

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

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ def clone_project(
431431
project_description: Optional[NotEmptyStr] = None,
432432
copy_annotation_classes: Optional[StrictBool] = True,
433433
copy_settings: Optional[StrictBool] = True,
434-
copy_workflow: Optional[StrictBool] = True,
434+
copy_workflow: Optional[StrictBool] = False,
435435
copy_contributors: Optional[StrictBool] = False,
436436
):
437437
"""Create a new project in the team using annotation classes and settings from from_project.
@@ -455,22 +455,54 @@ def clone_project(
455455
:return: dict object metadata of the new project
456456
:rtype: dict
457457
"""
458-
project = self.controller.get_project(from_project)
459-
new_project = copy.copy(project)
460-
new_project.name = project_name
461-
if project_description:
462-
new_project.description = project_description
463-
response = self.controller.projects.clone(
464-
project=project,
465-
new_project=new_project,
466-
copy_annotation_classes=copy_annotation_classes,
467-
copy_settings=copy_settings,
468-
copy_workflow=copy_workflow,
469-
copy_contributors=copy_contributors,
458+
response = self.controller.projects.get_metadata(
459+
self.controller.get_project(from_project),
460+
include_annotation_classes=copy_annotation_classes,
461+
include_settings=copy_settings,
462+
include_workflow=copy_workflow,
463+
include_contributors=copy_contributors,
470464
)
471-
if response.errors:
472-
raise AppException(response.errors)
473-
return ProjectSerializer(response.data).serialize()
465+
response.raise_for_status()
466+
project: entities.ProjectEntity = response.data
467+
if copy_workflow and project.type not in (
468+
constants.ProjectType.VECTOR,
469+
constants.ProjectType.PIXEL,
470+
):
471+
raise AppException(
472+
f"Workflow is not supported in {project.type.name} project."
473+
)
474+
logger.info(
475+
f"Created project {project_name} with type {constants.ProjectType.get_name(project.type)}."
476+
)
477+
project_copy = copy.copy(project)
478+
if project_description:
479+
project_copy.description = project_description
480+
project_copy.name = project_name
481+
create_response = self.controller.projects.create(project_copy)
482+
create_response.raise_for_status()
483+
new_project = create_response.data
484+
if copy_contributors:
485+
logger.info(f"Cloning contributors from {from_project} to {project_name}.")
486+
self.controller.projects.add_contributors(
487+
self.controller.team, new_project, project.contributors
488+
)
489+
if copy_annotation_classes:
490+
logger.info(
491+
f"Cloning annotation classes from {from_project} to {project_name}."
492+
)
493+
classes_response = self.controller.annotation_classes.create_multiple(
494+
new_project, project.classes
495+
)
496+
classes_response.raise_for_status()
497+
project.classes = classes_response.data
498+
if copy_workflow:
499+
logger.info(f"Cloning workflow from {from_project} to {project_name}.")
500+
workflow_response = self.controller.projects.set_workflows(
501+
new_project, project.workflows
502+
)
503+
workflow_response.raise_for_status()
504+
project.workflows = self.controller.projects.list_workflow(project).data
505+
return ProjectSerializer(new_project).serialize()
474506

475507
def create_folder(self, project: NotEmptyStr, folder_name: NotEmptyStr):
476508
"""Create a new folder in the project.
@@ -2123,8 +2155,12 @@ def add_contributors_to_project(
21232155
:rtype: tuple (2 members) of lists of strs
21242156
"""
21252157
project = self.controller.projects.get_by_name(project).data
2158+
contributors = [
2159+
entities.ContributorEntity(email=email, user_role=constants.UserRole(role))
2160+
for email in emails
2161+
]
21262162
response = self.controller.projects.add_contributors(
2127-
project=project, team=self.controller.team, emails=emails, role=role
2163+
team=self.controller.team, project=project, contributors=contributors
21282164
)
21292165
if response.errors:
21302166
raise AppException(response.errors)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,5 @@ def wrapped(self, *args, **kwargs):
5151
return pydantic_validate_arguments(func)(self, *args, **kwargs)
5252
except ValidationError as e:
5353
raise AppException(wrap_error(e)) from e
54+
5455
return wrapped

src/superannotate/lib/app/serializers.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,13 @@ def serialize(
128128
data["settings"] = [
129129
SettingsSerializer(setting).serialize() for setting in data["settings"]
130130
]
131+
131132
if not data.get("status"):
132133
data["status"] = "Undefined"
133134

134135
if data.get("upload_state"):
135136
data["upload_state"] = constance.UploadState(data["upload_state"]).name
137+
136138
if data.get("users"):
137139
for contributor in data["users"]:
138140
contributor["user_role"] = constance.UserRole.get_name(

src/superannotate/lib/core/entities/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@
1111
from lib.core.entities.items import TiledEntity
1212
from lib.core.entities.items import VideoEntity
1313
from lib.core.entities.project import AttachmentEntity
14+
from lib.core.entities.project import ContributorEntity
1415
from lib.core.entities.project import MLModelEntity
1516
from lib.core.entities.project import ProjectEntity
1617
from lib.core.entities.project import SettingEntity
1718
from lib.core.entities.project import TeamEntity
18-
from lib.core.entities.project import UserEntity
1919
from lib.core.entities.project import WorkflowEntity
2020
from lib.core.entities.project_entities import BaseEntity
2121
from lib.core.entities.project_entities import ImageInfoEntity
@@ -39,13 +39,13 @@
3939
"AttachmentEntity",
4040
# project
4141
"ProjectEntity",
42+
"ContributorEntity",
4243
"ConfigEntity",
4344
"WorkflowEntity",
4445
"FolderEntity",
4546
"ImageInfoEntity",
4647
"S3FileEntity",
4748
"AnnotationClassEntity",
48-
"UserEntity",
4949
"TeamEntity",
5050
"MLModelEntity",
5151
"IntegrationEntity",

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from lib.core.enums import BaseTitledEnum
1616
from pydantic import BaseModel as PydanticBaseModel
1717
from pydantic import Extra
18-
from pydantic import StrictStr
1918
from pydantic import Field
19+
from pydantic import StrictStr
2020
from pydantic.datetime_parse import parse_datetime
2121
from pydantic.typing import is_namedtuple
2222
from pydantic.utils import ROOT_KEY
@@ -295,7 +295,7 @@ def map_fields(entity: dict) -> dict:
295295

296296

297297
class TokenStr(StrictStr):
298-
regex = r'^[-.@_A-Za-z0-9]+=\d+$'
298+
regex = r"^[-.@_A-Za-z0-9]+=\d+$"
299299

300300
@classmethod
301301
def validate(cls, value: Union[str]) -> Union[str]:

src/superannotate/lib/core/entities/project.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from lib.core.enums import BaseTitledEnum
1111
from lib.core.enums import ProjectStatus
1212
from lib.core.enums import ProjectType
13+
from lib.core.enums import UserRole
1314
from pydantic import Extra
1415
from pydantic import Field
1516
from pydantic import StrictBool
@@ -77,6 +78,17 @@ def __copy__(self):
7778
return SettingEntity(attribute=self.attribute, value=self.value)
7879

7980

81+
class ContributorEntity(BaseModel):
82+
id: Optional[str]
83+
first_name: Optional[str]
84+
last_name: Optional[str]
85+
email: str
86+
user_role: UserRole
87+
88+
class Config:
89+
extra = Extra.ignore
90+
91+
8092
class ProjectEntity(TimedBaseModel):
8193
id: Optional[int]
8294
team_id: Optional[int]
@@ -93,7 +105,7 @@ class ProjectEntity(TimedBaseModel):
93105
upload_state: Optional[int]
94106
users: Optional[List[Any]] = []
95107
unverified_users: Optional[List[Any]] = []
96-
contributors: List[Any] = []
108+
contributors: List[ContributorEntity] = []
97109
settings: List[SettingEntity] = []
98110
classes: List[AnnotationClassEntity] = []
99111
workflows: Optional[List[WorkflowEntity]] = []
@@ -117,13 +129,12 @@ def __copy__(self):
117129
team_id=self.team_id,
118130
name=self.name,
119131
type=self.type,
120-
description=self.description,
121-
instructions_link=self.instructions_link
122-
if self.description
123-
else f"Copy of {self.name}.",
132+
description=f"Copy of {self.name}.",
133+
instructions_link=self.instructions_link,
124134
status=self.status,
125135
folder_id=self.folder_id,
126136
users=self.users,
137+
settings=[s.__copy__() for s in self.settings],
127138
upload_state=self.upload_state,
128139
)
129140

@@ -151,22 +162,13 @@ class Config:
151162
extra = Extra.ignore
152163

153164

154-
class UserEntity(BaseModel):
155-
id: Optional[str]
156-
first_name: Optional[str]
157-
last_name: Optional[str]
158-
email: Optional[str]
159-
picture: Optional[str]
160-
user_role: Optional[int]
161-
162-
163165
class TeamEntity(BaseModel):
164166
id: Optional[int]
165167
name: Optional[str]
166168
description: Optional[str]
167169
type: Optional[str]
168170
user_role: Optional[str]
169171
is_default: Optional[bool]
170-
users: Optional[List[UserEntity]]
172+
users: Optional[List[ContributorEntity]]
171173
pending_invitations: Optional[List]
172174
creator_id: Optional[str]

src/superannotate/lib/core/enums.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def images(self):
100100

101101

102102
class UserRole(BaseTitledEnum):
103-
SUPER_ADMIN = "Superadmin", 1
103+
SUPER_ADMIN = "Superadmin", 1 # noqa
104104
ADMIN = "Admin", 2
105105
ANNOTATOR = "Annotator", 3
106106
QA = "QA", 4

0 commit comments

Comments
 (0)