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
4 changes: 2 additions & 2 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
minversion = 3.7
log_cli=true
python_files = test_*.py
pytest_plugins = ['pytest_profiling']
;addopts = -n auto --dist=loadscope
;pytest_plugins = ['pytest_profiling']
addopts = -n auto --dist=loadscope
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ requests==2.26.0
requests-toolbelt>=0.9.1
aiohttp>=3.8.1
tqdm==4.64.0
pillow>=7.2.0<9.0.1
pillow>=7.2.0, <9.0.1
matplotlib>=3.3.1
xmltodict==0.12.0
opencv-python>=4.4.0.42
Expand Down
243 changes: 152 additions & 91 deletions src/superannotate/lib/app/interface/sdk_interface.py

Large diffs are not rendered by default.

180 changes: 4 additions & 176 deletions src/superannotate/lib/app/interface/types.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
import uuid
from functools import wraps
from pathlib import Path
from typing import Optional
from typing import Union

from lib.core.enums import AnnotationStatus
from lib.core.enums import ApprovalStatus
from lib.core.enums import BaseTitledEnum
from lib.core.enums import ClassTypeEnum
from lib.core.enums import FolderStatus
from lib.core.enums import ProjectStatus
from lib.core.enums import ProjectType
from lib.core.enums import UserRole
from lib.core.exceptions import AppException
from lib.infrastructure.validators import wrap_error
from pydantic import BaseModel
from pydantic import conlist
from pydantic import constr
from pydantic import errors
from pydantic import Extra
from pydantic import Field
from pydantic import parse_obj_as
from pydantic import root_validator
from pydantic import StrictStr
from pydantic import validate_arguments as pydantic_validate_arguments
from pydantic import ValidationError
from pydantic.errors import PydanticTypeError
from pydantic.errors import StrRegexError

NotEmptyStr = constr(strict=True, min_length=1)


class EnumMemberError(PydanticTypeError):
code = "enum"
Expand All @@ -54,174 +36,20 @@ def validate(cls, value: Union[str]) -> Union[str]:
regex=r"^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)"
r"*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}"
r"[a-zA-Z0-9])?)*$"
).validate(value)
).validate( # noqa
value
)
except StrRegexError:
raise ValueError("Invalid email")
return value


class ProjectStatusEnum(StrictStr):
@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
if cls.curtail_length and len(value) > cls.curtail_length:
value = value[: cls.curtail_length]
if value.lower() not in ProjectStatus.values():
raise TypeError(
f"Available statuses is {', '.join(ProjectStatus.titles())}. "
)
return value


class FolderStatusEnum(StrictStr):
@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
if cls.curtail_length and len(value) > cls.curtail_length:
value = value[: cls.curtail_length]
if value.lower() not in FolderStatus.values():
raise TypeError(
f"Available statuses is {', '.join(FolderStatus.titles())}. "
)
return value


class AnnotatorRole(StrictStr):
ANNOTATOR_ROLES = (UserRole.ADMIN.name, UserRole.ANNOTATOR.name, UserRole.QA.name)

@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
if cls.curtail_length and len(value) > cls.curtail_length:
value = value[: cls.curtail_length]
if value.lower() not in [role.lower() for role in cls.ANNOTATOR_ROLES]:
raise TypeError(
f"Invalid user role provided. Please specify one of {', '.join(cls.ANNOTATOR_ROLES)}. "
)
return value


class AnnotationType(StrictStr):
VALID_TYPES = ["bbox", "polygon", "point", "tag"]

@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
if value.lower() not in cls.VALID_TYPES:
raise TypeError(
f"Available annotation_types are {', '.join(cls.VALID_TYPES)}. "
)
return value


class AttachmentDict(BaseModel):
url: StrictStr
name: Optional[StrictStr] = Field(default_factory=lambda: str(uuid.uuid4()))

class Config:
extra = Extra.ignore

def __hash__(self):
return hash(self.name)

def __eq__(self, other):
return self.url == other.url and self.name.strip() == other.name.strip()


AttachmentArgType = Union[NotEmptyStr, Path, conlist(AttachmentDict, min_items=1)]


class Setting(BaseModel):
attribute: NotEmptyStr
value: Union[NotEmptyStr, float, int]

class Config:
extra = Extra.ignore


class AttachmentArg(BaseModel):
__root__: AttachmentArgType

def __getitem__(self, index):
return self.__root__[index]

@property
def data(self):
return self.__root__

@root_validator(pre=True)
def validate_root(cls, values):
try:
parse_obj_as(AttachmentArgType, values["__root__"])
except ValidationError:
raise ValueError(
"The value must be str, path, or list of dicts with the required 'url' and optional 'name' keys"
)
return values


class ImageQualityChoices(StrictStr):
VALID_CHOICES = ["compressed", "original"]

@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
super().validate(value)
if value.lower() not in cls.VALID_CHOICES:
raise TypeError(
f"Image quality available choices are {', '.join(cls.VALID_CHOICES)}."
)
return value.lower()


class ProjectTypes(StrictStr):
@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
if value.lower() not in ProjectType.values():
raise TypeError(
f" Available project types are {', '.join(ProjectType.titles())}. "
)
return value


class ClassType(StrictStr):
@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
enum_values = [e.name.lower() for e in ClassTypeEnum]
if value.lower() not in enum_values:
raise TypeError(
f"Invalid type provided. Please specify one of the {', '.join(enum_values)}. "
)
return value.lower()


class AnnotationStatuses(StrictStr):
@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
if value.lower() not in AnnotationStatus.values():
raise TypeError(
f"Available an notation_statuses are {', '.join(AnnotationStatus.titles())}. "
)
return value


class ApprovalStatuses(StrictStr):
@classmethod
def validate(cls, value: Union[str]) -> Union[str]:
if value is None:
return value
if value.lower() not in ApprovalStatus.values() or not isinstance(value, str):
raise TypeError(
f"Available approval_status options are {', '.join(map(str, ApprovalStatus.titles()))}."
)
return value

@classmethod
def __get_validators__(cls):
yield cls.validate


def validate_arguments(func):
@wraps(func)
def wrapped(self, *args, **kwargs):
try:
return pydantic_validate_arguments(func)(self, *args, **kwargs)
except ValidationError as e:
raise AppException(wrap_error(e))
raise AppException(wrap_error(e)) from e

return wrapped
3 changes: 3 additions & 0 deletions src/superannotate/lib/core/entities/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class AttachmentEntity(BaseModel):
class Config:
extra = Extra.ignore

def __hash__(self):
return hash(self.name)


class WorkflowEntity(BaseModel):
id: Optional[int]
Expand Down
11 changes: 9 additions & 2 deletions src/superannotate/lib/core/enums.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import typing
from enum import Enum
from types import DynamicClassAttribute

Expand Down Expand Up @@ -27,6 +28,9 @@ def choices(cls):
def name(self) -> str:
return self.__doc__

def __unicode__(self):
return self.__doc__

@DynamicClassAttribute
def value(self):
return super().value
Expand All @@ -51,15 +55,18 @@ def values(cls):
return [enum.__doc__.lower() if enum else None for enum in list(cls)]

@classmethod
def titles(cls):
return [enum.__doc__ for enum in list(cls)]
def titles(cls) -> typing.Tuple:
return tuple(enum.__doc__ for enum in list(cls))

def equals(self, other: Enum):
return self.__doc__.lower() == other.__doc__.lower()

def __eq__(self, other):
return super().__eq__(other)

def __repr__(self):
return self.name

def __hash__(self):
return hash(self.name)

Expand Down
2 changes: 1 addition & 1 deletion src/superannotate/lib/core/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Config:
extra = Extra.allow


class PriorityScore(BaseModel):
class PriorityScoreEntity(BaseModel):
name: NotEmptyStr
priority: float

Expand Down
7 changes: 4 additions & 3 deletions src/superannotate/lib/core/usecases/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from lib.core.response import Response
from lib.core.service_types import UploadAnnotationAuthData
from lib.core.serviceproviders import BaseServiceProvider
from lib.core.types import PriorityScore
from lib.core.types import PriorityScoreEntity
from lib.core.usecases.base import BaseReportableUseCase
from lib.core.video_convertor import VideoFrameGenerator
from pydantic import BaseModel
Expand Down Expand Up @@ -585,6 +585,7 @@ def get_existing_name_item_mapping(

@property
def annotation_upload_data(self) -> UploadAnnotationAuthData:

CHUNK_SIZE = UploadAnnotationsFromFolderUseCase.CHUNK_SIZE_PATHS

if self._annotation_upload_data:
Expand All @@ -598,7 +599,7 @@ def annotation_upload_data(self) -> UploadAnnotationAuthData:
item_ids=self._item_ids[i : i + CHUNK_SIZE],
)
if not tmp.ok:
raise AppException(tmp.errors)
raise AppException(tmp.error)
else:
images.update(tmp.data.images)

Expand Down Expand Up @@ -1195,7 +1196,7 @@ def __init__(
reporter,
project: ProjectEntity,
folder: FolderEntity,
scores: List[PriorityScore],
scores: List[PriorityScoreEntity],
project_folder_name: str,
service_provider: BaseServiceProvider,
):
Expand Down
6 changes: 3 additions & 3 deletions src/superannotate/lib/core/usecases/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,11 @@ def __init__(self, reporter: Reporter):
self.reporter = reporter


class BaseUserBasedUseCase(BaseReportableUseCase, metaclass=ABCMeta):
class BaseUserBasedUseCase(BaseUseCase, metaclass=ABCMeta):
"""
class contain validation of unique emails
"""

def __init__(self, reporter: Reporter, emails: List[str]):
super().__init__(reporter)
def __init__(self, emails: List[str]):
super().__init__()
self._emails = emails
Loading