Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/superannotate/lib/app/interface/base_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ def __init__(self, token: TokenStr = None, config_path: str = None):
config = ConfigEntity(SA_TOKEN=token)
elif config_path:
config_path = Path(config_path)
if not Path(config_path).is_file() or not os.access(config_path, os.R_OK):
if not Path(config_path).is_file() or not os.access(
config_path, os.R_OK
):
raise AppException(
f"SuperAnnotate config file {str(config_path)} not found."
)
Expand Down Expand Up @@ -77,8 +79,12 @@ def _retrieve_configs_from_json(path: Path) -> typing.Union[ConfigEntity]:
config = ConfigEntity(SA_TOKEN=token)
except pydantic.ValidationError:
raise pydantic.ValidationError(
[pydantic.error_wrappers.ErrorWrapper(ValueError("Invalid token."), loc='token')],
model=ConfigEntity
[
pydantic.error_wrappers.ErrorWrapper(
ValueError("Invalid token."), loc="token"
)
],
model=ConfigEntity,
)
host = json_data.get("main_endpoint")
verify_ssl = json_data.get("ssl_verify")
Expand Down
70 changes: 53 additions & 17 deletions src/superannotate/lib/app/interface/sdk_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ def clone_project(
project_description: Optional[NotEmptyStr] = None,
copy_annotation_classes: Optional[StrictBool] = True,
copy_settings: Optional[StrictBool] = True,
copy_workflow: Optional[StrictBool] = True,
copy_workflow: Optional[StrictBool] = False,
copy_contributors: Optional[StrictBool] = False,
):
"""Create a new project in the team using annotation classes and settings from from_project.
Expand All @@ -455,22 +455,54 @@ def clone_project(
:return: dict object metadata of the new project
:rtype: dict
"""
project = self.controller.get_project(from_project)
new_project = copy.copy(project)
new_project.name = project_name
if project_description:
new_project.description = project_description
response = self.controller.projects.clone(
project=project,
new_project=new_project,
copy_annotation_classes=copy_annotation_classes,
copy_settings=copy_settings,
copy_workflow=copy_workflow,
copy_contributors=copy_contributors,
response = self.controller.projects.get_metadata(
self.controller.get_project(from_project),
include_annotation_classes=copy_annotation_classes,
include_settings=copy_settings,
include_workflow=copy_workflow,
include_contributors=copy_contributors,
)
if response.errors:
raise AppException(response.errors)
return ProjectSerializer(response.data).serialize()
response.raise_for_status()
project: entities.ProjectEntity = response.data
if copy_workflow and project.type not in (
constants.ProjectType.VECTOR,
constants.ProjectType.PIXEL,
):
raise AppException(
f"Workflow is not supported in {project.type.name} project."
)
logger.info(
f"Created project {project_name} with type {constants.ProjectType.get_name(project.type)}."
)
project_copy = copy.copy(project)
if project_description:
project_copy.description = project_description
project_copy.name = project_name
create_response = self.controller.projects.create(project_copy)
create_response.raise_for_status()
new_project = create_response.data
if copy_contributors:
logger.info(f"Cloning contributors from {from_project} to {project_name}.")
self.controller.projects.add_contributors(
self.controller.team, new_project, project.contributors
)
if copy_annotation_classes:
logger.info(
f"Cloning annotation classes from {from_project} to {project_name}."
)
classes_response = self.controller.annotation_classes.create_multiple(
new_project, project.classes
)
classes_response.raise_for_status()
project.classes = classes_response.data
if copy_workflow:
logger.info(f"Cloning workflow from {from_project} to {project_name}.")
workflow_response = self.controller.projects.set_workflows(
new_project, project.workflows
)
workflow_response.raise_for_status()
project.workflows = self.controller.projects.list_workflow(project).data
return ProjectSerializer(new_project).serialize()

def create_folder(self, project: NotEmptyStr, folder_name: NotEmptyStr):
"""Create a new folder in the project.
Expand Down Expand Up @@ -2123,8 +2155,12 @@ def add_contributors_to_project(
:rtype: tuple (2 members) of lists of strs
"""
project = self.controller.projects.get_by_name(project).data
contributors = [
entities.ContributorEntity(email=email, user_role=constants.UserRole(role))
for email in emails
]
response = self.controller.projects.add_contributors(
project=project, team=self.controller.team, emails=emails, role=role
team=self.controller.team, project=project, contributors=contributors
)
if response.errors:
raise AppException(response.errors)
Expand Down
1 change: 1 addition & 0 deletions src/superannotate/lib/app/interface/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ def wrapped(self, *args, **kwargs):
return pydantic_validate_arguments(func)(self, *args, **kwargs)
except ValidationError as e:
raise AppException(wrap_error(e)) from e

return wrapped
2 changes: 2 additions & 0 deletions src/superannotate/lib/app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,13 @@ def serialize(
data["settings"] = [
SettingsSerializer(setting).serialize() for setting in data["settings"]
]

if not data.get("status"):
data["status"] = "Undefined"

if data.get("upload_state"):
data["upload_state"] = constance.UploadState(data["upload_state"]).name

if data.get("users"):
for contributor in data["users"]:
contributor["user_role"] = constance.UserRole.get_name(
Expand Down
4 changes: 2 additions & 2 deletions src/superannotate/lib/core/entities/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
from lib.core.entities.items import TiledEntity
from lib.core.entities.items import VideoEntity
from lib.core.entities.project import AttachmentEntity
from lib.core.entities.project import ContributorEntity
from lib.core.entities.project import MLModelEntity
from lib.core.entities.project import ProjectEntity
from lib.core.entities.project import SettingEntity
from lib.core.entities.project import TeamEntity
from lib.core.entities.project import UserEntity
from lib.core.entities.project import WorkflowEntity
from lib.core.entities.project_entities import BaseEntity
from lib.core.entities.project_entities import ImageInfoEntity
Expand All @@ -39,13 +39,13 @@
"AttachmentEntity",
# project
"ProjectEntity",
"ContributorEntity",
"ConfigEntity",
"WorkflowEntity",
"FolderEntity",
"ImageInfoEntity",
"S3FileEntity",
"AnnotationClassEntity",
"UserEntity",
"TeamEntity",
"MLModelEntity",
"IntegrationEntity",
Expand Down
4 changes: 2 additions & 2 deletions src/superannotate/lib/core/entities/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from lib.core.enums import BaseTitledEnum
from pydantic import BaseModel as PydanticBaseModel
from pydantic import Extra
from pydantic import StrictStr
from pydantic import Field
from pydantic import StrictStr
from pydantic.datetime_parse import parse_datetime
from pydantic.typing import is_namedtuple
from pydantic.utils import ROOT_KEY
Expand Down Expand Up @@ -295,7 +295,7 @@ def map_fields(entity: dict) -> dict:


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

@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
Expand Down
32 changes: 17 additions & 15 deletions src/superannotate/lib/core/entities/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from lib.core.enums import BaseTitledEnum
from lib.core.enums import ProjectStatus
from lib.core.enums import ProjectType
from lib.core.enums import UserRole
from pydantic import Extra
from pydantic import Field
from pydantic import StrictBool
Expand Down Expand Up @@ -77,6 +78,17 @@ def __copy__(self):
return SettingEntity(attribute=self.attribute, value=self.value)


class ContributorEntity(BaseModel):
id: Optional[str]
first_name: Optional[str]
last_name: Optional[str]
email: str
user_role: UserRole

class Config:
extra = Extra.ignore


class ProjectEntity(TimedBaseModel):
id: Optional[int]
team_id: Optional[int]
Expand All @@ -93,7 +105,7 @@ class ProjectEntity(TimedBaseModel):
upload_state: Optional[int]
users: Optional[List[Any]] = []
unverified_users: Optional[List[Any]] = []
contributors: List[Any] = []
contributors: List[ContributorEntity] = []
settings: List[SettingEntity] = []
classes: List[AnnotationClassEntity] = []
workflows: Optional[List[WorkflowEntity]] = []
Expand All @@ -117,13 +129,12 @@ def __copy__(self):
team_id=self.team_id,
name=self.name,
type=self.type,
description=self.description,
instructions_link=self.instructions_link
if self.description
else f"Copy of {self.name}.",
description=f"Copy of {self.name}.",
instructions_link=self.instructions_link,
status=self.status,
folder_id=self.folder_id,
users=self.users,
settings=[s.__copy__() for s in self.settings],
upload_state=self.upload_state,
)

Expand Down Expand Up @@ -151,22 +162,13 @@ class Config:
extra = Extra.ignore


class UserEntity(BaseModel):
id: Optional[str]
first_name: Optional[str]
last_name: Optional[str]
email: Optional[str]
picture: Optional[str]
user_role: Optional[int]


class TeamEntity(BaseModel):
id: Optional[int]
name: Optional[str]
description: Optional[str]
type: Optional[str]
user_role: Optional[str]
is_default: Optional[bool]
users: Optional[List[UserEntity]]
users: Optional[List[ContributorEntity]]
pending_invitations: Optional[List]
creator_id: Optional[str]
2 changes: 1 addition & 1 deletion src/superannotate/lib/core/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def images(self):


class UserRole(BaseTitledEnum):
SUPER_ADMIN = "Superadmin", 1
SUPER_ADMIN = "Superadmin", 1 # noqa
ADMIN = "Admin", 2
ANNOTATOR = "Annotator", 3
QA = "QA", 4
Expand Down
Loading