diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index ad8c4c4be..a6596f77c 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -30,6 +30,7 @@ from superannotate_core.core.conditions import Condition from superannotate_core.core.conditions import EmptyCondition from superannotate_core.core.enums import FolderStatus +from superannotate_core.core.enums import ProjectStatus from superannotate_core.core.enums import ClassTypeEnum from superannotate_core.core.enums import AnnotationStatus from superannotate_core.core.enums import ProjectType @@ -37,6 +38,7 @@ from superannotate_core.core.entities import AttributeGroupSchema from superannotate_core.core.entities import AnnotationClassEntity from superannotate_core.core.exceptions import SAException +from superannotate_core.core.exceptions import SAInvalidInput import lib.core as constants from lib.app.helpers import get_annotation_paths from lib.app.helpers import get_name_url_duplicated_from_csv @@ -45,17 +47,15 @@ from lib.app.interface.base_interface import TrackableMeta from lib.app.interface.types import EmailStr from lib.app.serializers import BaseSerializer -from lib.app.serializers import ProjectSerializer -from lib.app.serializers import SettingsSerializer + from lib.app.serializers import TeamSerializer from lib.core import LIMITED_FUNCTIONS from lib.core import entities from lib.core.entities import WorkflowEntity -from lib.core.entities import SettingEntity from lib.core.entities.integrations import IntegrationEntity from lib.core.entities.integrations import IntegrationTypeEnum -from lib.core.enums import ImageQuality +from superannotate_core.core.enums import ImageQuality from lib.core.exceptions import AppException from lib.core.types import MLModel from lib.core.types import PriorityScoreEntity @@ -156,9 +156,6 @@ def get_project_by_id(self, project_id: int): :return: project metadata :rtype: dict """ - # response = self.controller.get_project_by_id(project_id=project_id) - # - # return ProjectSerializer(response.data).serialize() return Project.get_by_id(self.session, project_id).dict() def get_folder_by_id(self, project_id: int, folder_id: int): @@ -288,14 +285,11 @@ def search_projects( condition &= Condition( "status", constants.ProjectStatus.get_value(status), EQ ) - - response = self.controller.projects.list(condition) - if response.errors: - raise AppException(response.errors) + projects = Project.list(session=self.session, condition=condition) if return_metadata: return [ i.dict( - { + exclude={ "settings", "workflows", "contributors", @@ -303,10 +297,10 @@ def search_projects( "item_count", } ) - for i in response.data + for i in projects ] else: - return [project.name for project in response.data] + return [project.name for project in projects] def create_project( self, @@ -314,7 +308,7 @@ def create_project( project_description: NotEmptyStr, project_type: PROJECT_TYPE, settings: List[Setting] = None, - # fix validation for class + # TODO fix validation for class # classes: List[AnnotationClassEntity] = None, classes: List = None, workflows: List = None, @@ -359,15 +353,10 @@ def create_project( raise AppException( "Project with workflows can not be created without classes." ) - if settings: - settings = parse_obj_as(List[SettingEntity], settings) - else: - settings = [] - if classes: - classes = parse_obj_as(List[AnnotationClassEntity], classes) + if workflows and classes: invalid_classes = [] - class_names = [_class.name for _class in classes] + class_names = [_class["name"] for _class in classes] for step in workflows: if step["className"] not in class_names: invalid_classes.append(step["className"]) @@ -380,30 +369,25 @@ def create_project( raise AppException( f"There are no [{', '.join(invalid_classes)}] classes created in the project." ) - project_response = self.controller.projects.create( - entities.ProjectEntity( - name=project_name, - description=project_description, - type=constants.ProjectType.get_value(project_type), - settings=settings, - instructions_link=instructions_link, - ) + + new_project = Project.create( + session=self.session, + name=project_name, + team_id=self.controller.team_id, + description=project_description, + type=ProjectType.get_value(project_type), + status=ProjectStatus.NotStarted.value, + settings=settings, + instructions_link=instructions_link, ) - project_response.raise_for_status() - project = project_response.data + # TODO set instructions_link (delete after adding support in creation) + if instructions_link: + new_project = new_project.update(instructions_link=instructions_link) if classes: - classes_response = self.controller.annotation_classes.create_multiple( - project, classes - ) - classes_response.raise_for_status() - project.classes = classes_response.data + new_project.classes = new_project.create_annotation_classes(classes) if workflows: - workflow_response = self.controller.projects.set_workflows( - project, workflows - ) - workflow_response.raise_for_status() - project.workflows = self.controller.projects.list_workflow(project).data - return ProjectSerializer(project).serialize() + new_project.set_workflows(workflows=workflows) + return new_project.dict() def clone_project( self, @@ -442,75 +426,70 @@ def clone_project( :return: dict object metadata of the new project :rtype: dict """ - response = self.controller.projects.get_metadata( - self.controller.get_project(from_project), + from_project = self.get_project_metadata( + project=from_project, include_annotation_classes=copy_annotation_classes, include_settings=copy_settings, include_workflow=copy_workflow, include_contributors=copy_contributors, ) - response.raise_for_status() - project: entities.ProjectEntity = response.data - if copy_workflow and project.type not in ( - constants.ProjectType.VECTOR, - constants.ProjectType.PIXEL, + project_copy = Project.from_json(copy.copy(from_project)) + + if copy_workflow and project_copy.type.value not in ( + constants.ProjectType.VECTOR.value, + constants.ProjectType.PIXEL.value, ): raise AppException( - f"Workflow is not supported in {project.type.name} project." + f"Workflow is not supported in {project_copy.type.name} project." ) - project_copy = copy.copy(project) - if project_copy.type in ( - constants.ProjectType.VECTOR, - constants.ProjectType.PIXEL, + if project_copy.type.value in ( + constants.ProjectType.VECTOR.value, + constants.ProjectType.PIXEL.value, ): - project_copy.upload_state = constants.UploadState.INITIAL + project_copy.upload_state = constants.UploadState.INITIAL.value if project_description: project_copy.description = project_description - else: - 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 + project_copy.id = None + project_copy.createdAt = None + project_copy.updatedAt = None + project_copy.folder_id = None + new_project = Project.create( + session=self.session, **project_copy.to_json(exclude_none=True) + ) + if project_copy.instructions_link: + new_project = new_project.update( + instructions_link=project_copy.instructions_link ) - if copy_annotation_classes: + if copy_contributors and project_copy.users: logger.info( - f"Cloning annotation classes from {from_project} to {project_name}." + f"Cloning contributors from {project_copy.name} to {new_project.name}." ) - classes_response = self.controller.annotation_classes.create_multiple( - new_project, project.classes + new_project.add_contributors(project_copy.users) + if copy_annotation_classes: + logger.info( + f"Cloning annotation classes from {project_copy.name} to {new_project.name}." ) - classes_response.raise_for_status() - project.classes = classes_response.data + new_project.create_annotation_classes(project_copy.classes) if copy_workflow: if not copy_annotation_classes: logger.info( - f"Skipping the workflow clone from {from_project} to {project_name}." + f"Skipping the workflow clone from {project_copy.name} to {new_project.name}." ) else: - logger.info(f"Cloning workflow from {from_project} to {project_name}.") - workflow_response = self.controller.projects.set_workflows( - new_project, project.workflows + logger.info( + f"Cloning workflow from {project_copy.name} to {new_project.name}." ) - workflow_response.raise_for_status() - project.workflows = self.controller.projects.list_workflow(project).data - response = self.controller.projects.get_metadata( - new_project, + new_project.set_workflows(project_copy.workflows) + return self.get_project_metadata( + new_project.name, include_settings=copy_settings, include_workflow=copy_workflow, include_contributors=copy_contributors, include_annotation_classes=copy_annotation_classes, - include_complete_image_count=True, + include_complete_item_count=True, ) - if response.errors: - raise AppException(response.errors) - return ProjectSerializer(response.data).serialize() - def create_folder(self, project: NotEmptyStr, folder_name: NotEmptyStr): """Create a new folder in the project. @@ -544,10 +523,13 @@ def delete_project(self, project: Union[NotEmptyStr, dict]): :param project: project name :type project: str """ - name = project - if isinstance(project, dict): - name = project["name"] - self.controller.projects.delete(name=name) + project_name, _ = extract_project_folder(project) + try: + project = self.controller.get_project(project_name) + except SAInvalidInput: + pass + else: + project.delete() def rename_project(self, project: NotEmptyStr, new_name: NotEmptyStr): """Renames the project @@ -558,16 +540,13 @@ def rename_project(self, project: NotEmptyStr, new_name: NotEmptyStr): :param new_name: project's new name :type new_name: str """ - old_name = project - project = self.controller.get_project(old_name) # noqa - project.name = new_name - response = self.controller.projects.update(project) - if response.errors: - raise AppException(response.errors) + project_name, _ = extract_project_folder(project) + project = self.controller.get_project(project_name) + updated_project = project.update(name=new_name) logger.info( - "Successfully renamed project %s to %s.", old_name, response.data.name + "Successfully renamed project %s to %s.", project_name, updated_project.name ) - return ProjectSerializer(response.data).serialize() + return updated_project.dict() def get_folder_metadata(self, project: NotEmptyStr, folder_name: NotEmptyStr): """Returns folder metadata @@ -689,19 +668,37 @@ def get_project_metadata( :return: metadata of project :rtype: dict """ - project_name, folder_name = extract_project_folder(project) + project_name, _ = extract_project_folder(project) + project = self.controller.get_project(project_name) - response = self.controller.projects.get_metadata( - project, - include_annotation_classes, - include_settings, - include_workflow, - include_contributors, - include_complete_item_count, - ) - if response.errors: - raise AppException(response.errors) - return ProjectSerializer(response.data).serialize() + # get project include users + project = Project.get_by_id(session=self.session, project_id=project.id) + if include_complete_item_count: + folders = project.list_folders( + condition=Condition("completedImagesCount", True, EQ) + ) + root_completed_count = 0 + total_completed_count = 0 + for folder in folders: + try: + total_completed_count += folder.completedCount # noqa + if folder.is_root: + root_completed_count = folder.completedCount # noqa + except AttributeError: + pass + project.root_folder_completed_items_count = root_completed_count + project.completed_items_count = total_completed_count + if include_annotation_classes: + project.classes = project.list_annotation_classes() + if include_settings: + project.settings = project.list_settings() + if include_workflow: + project.workflows = project.list_workflows() + if include_contributors: + project.contributors = project.users + else: + project.users = [] + return project.dict() def get_project_settings(self, project: Union[NotEmptyStr, dict]): """Gets project's settings. @@ -715,11 +712,12 @@ def get_project_settings(self, project: Union[NotEmptyStr, dict]): :rtype: list of dicts """ project_name, _ = extract_project_folder(project) - project = self.controller.projects.get_by_name(project_name).data - settings = self.controller.projects.list_settings(project).data - settings = [ - SettingsSerializer(attribute.dict()).serialize() for attribute in settings - ] + project = self.controller.get_project(project_name) + settings = [] + for setting in project.list_settings(): + if setting.attribute == "ImageQuality": + setting.value = ImageQuality.get_name(setting.value) + settings.append(setting.dict()) return settings def get_project_workflow(self, project: Union[str, dict]): @@ -733,12 +731,9 @@ def get_project_workflow(self, project: Union[str, dict]): :return: project workflow :rtype: list of dicts """ - project_name, folder_name = extract_project_folder(project) + project_name, _ = extract_project_folder(project) project = self.controller.get_project(project_name) - workflow = self.controller.projects.list_workflow(project) - if workflow.errors: - raise AppException(workflow.errors) - return workflow.data + return [i.dict() for i in project.list_workflows()] def search_annotation_classes( self, @@ -790,10 +785,10 @@ def set_project_status(self, project: NotEmptyStr, status: PROJECT_STATUS): :type status: str """ - project = self.controller.get_project(pk=project) - project.status = constants.ProjectStatus.get_value(status) - response = self.controller.projects.update(project) - if response.errors: + project_name, _ = extract_project_folder(project) + project = self.controller.get_project(project_name) + updated_project = project.update(status=ProjectStatus.get_value(status)) + if updated_project.status.name.lower() != status.lower(): raise AppException(f"Failed to change {project.name} status.") logger.info(f"Successfully updated {project.name} status to {status}") @@ -840,16 +835,11 @@ def set_project_default_image_quality_in_editor( :param image_quality_in_editor: new setting value, should be "original" or "compressed" :type image_quality_in_editor: str """ - project_name, folder_name = extract_project_folder(project) - image_quality_in_editor = ImageQuality.get_value(image_quality_in_editor) + project_name, _ = extract_project_folder(project) project = self.controller.get_project(project_name) - response = self.controller.projects.set_settings( - project=project, - settings=[{"attribute": "ImageQuality", "value": image_quality_in_editor}], + return project.set_settings( + [{"attribute": "ImageQuality", "value": image_quality_in_editor}] ) - if response.errors: - raise AppException(response.errors) - return response.data def pin_image( self, @@ -1647,9 +1637,8 @@ def set_project_workflow( """ project_name, _ = extract_project_folder(project) project = self.controller.get_project(project_name) - response = self.controller.projects.set_workflows(project, steps=new_workflow) - if response.errors: - raise AppException(response.errors) + project.set_workflows(new_workflow) + return [i.dict() for i in project.workflows] def download_image( self, diff --git a/src/superannotate/lib/core/serviceproviders.py b/src/superannotate/lib/core/serviceproviders.py index fe9284f95..a5959c4cd 100644 --- a/src/superannotate/lib/core/serviceproviders.py +++ b/src/superannotate/lib/core/serviceproviders.py @@ -14,10 +14,7 @@ from lib.core.service_types import IntegrationListResponse from lib.core.service_types import ItemListResponse from lib.core.service_types import ModelListResponse -from lib.core.service_types import ProjectListResponse -from lib.core.service_types import ProjectResponse from lib.core.service_types import ServiceResponse -from lib.core.service_types import SettingsListResponse from lib.core.service_types import SubsetListResponse from lib.core.service_types import TeamResponse from lib.core.service_types import UploadAnnotationAuthDataResponse @@ -65,94 +62,6 @@ def __init__(self, client: BaseClient): self.client = client -class BaseProjectService(SuperannotateServiceProvider): - @abstractmethod - def get(self, uuid: int): - raise NotImplementedError - - @abstractmethod - def create(self, entity: entities.ProjectEntity) -> ProjectResponse: - raise NotImplementedError - - @abstractmethod - def list(self, condition: Condition = None) -> ProjectListResponse: - raise NotImplementedError - - @abstractmethod - def update(self, entity: entities.ProjectEntity) -> ProjectResponse: - raise NotImplementedError - - @abstractmethod - def delete(self, entity: entities.ProjectEntity) -> ServiceResponse: - raise NotImplementedError - - @abstractmethod - def list_settings(self, project: entities.ProjectEntity) -> SettingsListResponse: - raise NotImplementedError - - @abstractmethod - def set_settings( - self, project: entities.ProjectEntity, data: List[entities.SettingEntity] - ): - raise NotImplementedError - - @abstractmethod - def list_workflows(self, project: entities.ProjectEntity): - raise NotImplementedError - - @abstractmethod - def set_workflow( - self, project: entities.ProjectEntity, workflow: entities.WorkflowEntity - ): - raise NotImplementedError - - @abstractmethod - def set_workflows(self, project: entities.ProjectEntity, steps: list): - raise NotImplementedError - - @abstractmethod - def share(self, project: entities.ProjectEntity, users: list) -> ServiceResponse: - raise NotImplementedError - - @abstractmethod - def un_share(self, project: entities.ProjectEntity, user_id) -> ServiceResponse: - raise NotImplementedError - - @abstractmethod - def set_project_workflow_attributes( - self, project: entities.ProjectEntity, attributes: list - ): - raise NotImplementedError - - @abstractmethod - def assign_items( - self, - project: entities.ProjectEntity, - folder: entities.FolderEntity, - user: str, - item_names: List[str], - ) -> ServiceResponse: - raise NotImplementedError - - @abstractmethod - def un_assign_items( - self, - project: entities.ProjectEntity, - folder: entities.FolderEntity, - item_names: List[str], - ) -> ServiceResponse: - raise NotImplementedError - - @abstractmethod - def upload_priority_scores( - self, - project: entities.ProjectEntity, - folder: entities.FolderEntity, - priorities: list, - ) -> ServiceResponse: - raise NotImplementedError - - class BaseAnnotationClassService(SuperannotateServiceProvider): @abstractmethod def create_multiple( @@ -449,7 +358,6 @@ def attach_items( class BaseServiceProvider: - projects: BaseProjectService items: BaseItemService annotations: BaseAnnotationService custom_fields: BaseCustomFieldService diff --git a/src/superannotate/lib/core/usecases/annotations.py b/src/superannotate/lib/core/usecases/annotations.py index b8437e72d..77f215e5c 100644 --- a/src/superannotate/lib/core/usecases/annotations.py +++ b/src/superannotate/lib/core/usecases/annotations.py @@ -40,7 +40,6 @@ from lib.core.service_types import UploadAnnotationAuthData from lib.core.serviceproviders import BaseServiceProvider from lib.core.serviceproviders import ServiceResponse -from lib.core.types import PriorityScoreEntity from lib.core.usecases.base import BaseReportableUseCase from lib.core.video_convertor import VideoFrameGenerator from lib.infrastructure.utils import divide_to_chunks @@ -809,94 +808,6 @@ def execute(self): return self._response -class UploadPriorityScoresUseCase(BaseReportableUseCase): - CHUNK_SIZE = 100 - - def __init__( - self, - reporter, - project: ProjectEntity, - folder: FolderEntity, - scores: List[PriorityScoreEntity], - project_folder_name: str, - service_provider: BaseServiceProvider, - ): - super().__init__(reporter) - self._project = project - self._folder = folder - self._scores = scores - self._service_provider = service_provider - self._project_folder_name = project_folder_name - - @staticmethod - def get_clean_priority(priority): - if len(str(priority)) > 8: - priority = float(str(priority)[:8]) - if priority > 1000000: - priority = 1000000 - if priority < 0: - priority = 0 - if str(float(priority)).split(".")[1:2]: - if len(str(float(priority)).split(".")[1]) > 5: - priority = float( - str(float(priority)).split(".")[0] - + "." - + str(float(priority)).split(".")[1][:5] - ) - return priority - - @property - def folder_path(self): - return f"{self._project.name}{f'/{self._folder.name}' if self._folder.name != 'root' else ''}" - - @property - def uploading_info(self): - data_len: int = len(self._scores) - return ( - f"Uploading priority scores for {data_len} item(s) to {self.folder_path}." - ) - - def execute(self): - if self.is_valid(): - priorities = [] - initial_scores = [] - for i in self._scores: - priorities.append( - { - "name": i.name, - "entropy_value": self.get_clean_priority(i.priority), - } - ) - initial_scores.append(i.name) - uploaded_score_names = [] - self.reporter.log_info(self.uploading_info) - iterations = range(0, len(priorities), self.CHUNK_SIZE) - self.reporter.start_progress(iterations, "Uploading priority scores") - if iterations: - for i in iterations: - priorities_to_upload = priorities[ - i : i + self.CHUNK_SIZE - ] # noqa: E203 - res = self._service_provider.projects.upload_priority_scores( - project=self._project, - folder=self._folder, - priorities=priorities_to_upload, - ) - _data = res.data["data"] - if not _data: - _data = [] - self.reporter.update_progress(len(priorities_to_upload)) - uploaded_score_names.extend(list(map(lambda x: x["name"], _data))) - self.reporter.finish_progress() - skipped_score_names = list( - set(initial_scores) - set(uploaded_score_names) - ) - self._response.data = (uploaded_score_names, skipped_score_names) - else: - self.reporter.warning_messages("Empty scores.") - return self._response - - class ValidateAnnotationUseCase(BaseReportableUseCase): DEFAULT_VERSION = "V1.00" SCHEMAS: Dict[str, superannotate_schemas.Draft7Validator] = {} diff --git a/src/superannotate/lib/core/usecases/images.py b/src/superannotate/lib/core/usecases/images.py index 529b25e80..f6ccc7801 100644 --- a/src/superannotate/lib/core/usecases/images.py +++ b/src/superannotate/lib/core/usecases/images.py @@ -850,8 +850,8 @@ class UploadImagesToProject(BaseInteractiveUseCase): def __init__( self, - project: ProjectEntity, - folder: FolderEntity, + project: Project, + folder: Folder, s3_repo, service_provider: BaseServiceProvider, paths: List[str], @@ -1234,11 +1234,8 @@ def execute(self): huge_image, huge_width, huge_height = image_processor.generate_huge() quality = 60 if not self._image_quality_in_editor: - _response = self._service_provider.projects.list_settings(self._project) - if not _response.ok: - self._response.errors = AppException(_response.error) - return self._response - for setting in _response.data: + settings = self._project.list_settings() + for setting in settings: if setting.attribute == "ImageQuality": quality = setting.value else: diff --git a/src/superannotate/lib/core/usecases/projects.py b/src/superannotate/lib/core/usecases/projects.py index e1c02ed4f..b123e0e81 100644 --- a/src/superannotate/lib/core/usecases/projects.py +++ b/src/superannotate/lib/core/usecases/projects.py @@ -1,416 +1,21 @@ -import decimal import logging from collections import defaultdict from typing import List import lib.core as constances from lib.core.conditions import Condition -from lib.core.conditions import CONDITION_EQ as EQ from lib.core.entities import ContributorEntity from lib.core.entities import ProjectEntity -from lib.core.entities import SettingEntity from lib.core.entities import TeamEntity from lib.core.exceptions import AppException -from lib.core.exceptions import AppValidationException from lib.core.response import Response from lib.core.serviceproviders import BaseServiceProvider from lib.core.usecases.base import BaseUseCase from lib.core.usecases.base import BaseUserBasedUseCase -from superannotate_core.app import Project -from superannotate_core.infrastructure.session import Session logger = logging.getLogger("sa") -class GetProjectByIDUseCase(BaseUseCase): - def __init__(self, project_id, service_provider): - self._project_id = project_id - self._service_provider = service_provider - super().__init__() - - def execute(self): - try: - - self._response.data = self._service_provider.projects.get_by_id( - project_id=self._project_id - ).data - - except AppException as e: - self._response.errors = e - else: - if not self._response.data: - self._response.errors = AppException( - "Either the specified project does not exist or you do not have permission to view it" - ) - - return self._response - - -class GetProjectsUseCase(BaseUseCase): - def __init__( - self, - condition: Condition, - session: Session, - service_provider: BaseServiceProvider, - ): - super().__init__() - self._condition = condition - self._session = session - self._service_provider = service_provider - - def execute(self): - if self.is_valid(): - # response = self._service_provider.projects.list(self._condition) - projects = Project.list(self._session, self._condition) - self._response.data = projects - return self._response - - -class GetProjectByNameUseCase(BaseUseCase): - def __init__( - self, - name: str, - service_provider: BaseServiceProvider, - ): - super().__init__() - self._name = name - self._service_provider = service_provider - - def execute(self): - if self.is_valid(): - condition = Condition("name", self._name, EQ) - response = self._service_provider.projects.list(condition) - if response.ok: - if not response.data: - self._response.errors = AppException("Project not found.") - else: - project = next( - ( - project - for project in response.data - if project.name == self._name - ), - None, - ) - if not project: - self._response.errors = AppException("Project not found.") - self._response.data = project - - return self._response - - -class GetProjectMetaDataUseCase(BaseUseCase): - def __init__( - self, - project: ProjectEntity, - service_provider: BaseServiceProvider, - include_annotation_classes: bool, - include_settings: bool, - include_workflow: bool, - include_contributors: bool, - include_complete_image_count: bool, - ): - super().__init__() - self._project = project - self._service_provider = service_provider - - self._include_annotation_classes = include_annotation_classes - self._include_settings = include_settings - self._include_workflow = include_workflow - self._include_contributors = include_contributors - self._include_complete_image_count = include_complete_image_count - - def execute(self): - project = self._service_provider.projects.get(self._project.id).data - if self._include_complete_image_count: - folders = self._service_provider.folders.list( - Condition("project_id", self._project.id, EQ) - & Condition("completedImagesCount", True, EQ) - ).data - root_completed_count = 0 - total_completed_count = 0 - for folder in folders: - try: - total_completed_count += folder.completedCount # noqa - if folder.is_root: - root_completed_count = folder.completedCount # noqa - except AttributeError: - pass - project.root_folder_completed_items_count = root_completed_count - project.completed_items_count = total_completed_count - if self._include_annotation_classes: - project.classes = self._service_provider.annotation_classes.list( - Condition("project_id", self._project.id, EQ) - ).data - - if self._include_settings: - project.settings = self._service_provider.projects.list_settings( - self._project - ).data - - if self._include_workflow: - project.workflows = ( - GetWorkflowsUseCase( - project=self._project, service_provider=self._service_provider - ) - .execute() - .data - ) - - if self._include_contributors: - project.contributors = project.users - else: - project.users = [] - self._response.data = project - return self._response - - -class CreateProjectUseCase(BaseUseCase): - def __init__( - self, - project: ProjectEntity, - service_provider: BaseServiceProvider, - ): - - super().__init__() - self._project = project - self._service_provider = service_provider - - def validate_settings(self): - for setting in self._project.settings[:]: - if setting.attribute not in constances.PROJECT_SETTINGS_VALID_ATTRIBUTES: - self._project.settings.remove(setting) - if setting.attribute == "ImageQuality" and isinstance(setting.value, str): - setting.value = constances.ImageQuality.get_value(setting.value) - elif setting.attribute == "FrameRate": - if not self._project.type == constances.ProjectType.VIDEO.value: - raise AppValidationException( - "FrameRate is available only for Video projects" - ) - try: - setting.value = float(setting.value) - if ( - not (0.0001 < setting.value < 120) - or decimal.Decimal(str(setting.value)).as_tuple().exponent < -3 - ): - raise AppValidationException( - "The FrameRate value range is between 0.001 - 120" - ) - frame_mode = next( - filter( - lambda x: x.attribute == "FrameMode", self._project.settings - ), - None, - ) - if not frame_mode: - self._project.settings.append( - SettingEntity(attribute="FrameMode", value=1) - ) - except ValueError: - raise AppValidationException("The FrameRate value should be float") - - def validate_project_name(self): - if ( - len( - set(self._project.name).intersection( - constances.SPECIAL_CHARACTERS_IN_PROJECT_FOLDER_NAMES - ) - ) - > 0 - ): - self._project.name = "".join( - "_" - if char in constances.SPECIAL_CHARACTERS_IN_PROJECT_FOLDER_NAMES - else char - for char in self._project.name - ) - logger.warning( - "New folder name has special characters. Special characters will be replaced by underscores." - ) - condition = Condition("name", self._project.name, EQ) - response = self._service_provider.projects.list(condition) - if response.ok: - for project in response.data: - if project.name == self._project.name: - logger.error("There are duplicated names.") - raise AppValidationException( - f"Project name {self._project.name} is not unique. " - f"To use SDK please make project names unique." - ) - - def execute(self): - if self.is_valid(): - # new projects can only have the status of NotStarted - self._project.status = constances.ProjectStatus.NotStarted.value - response = self._service_provider.projects.create(self._project) - if not response.ok: - self._response.errors = response.error - entity = response.data - # create project doesn't store attachment data so need to update - instructions_link = self._project.instructions_link - if instructions_link: - entity.instructions_link = instructions_link - self._service_provider.projects.update(entity) - if not entity: - self._response.errors = AppException("Failed to create project.") - return self._response - self._response.data = entity - data = {} - # TODO delete if create_from_metadata deleted - # if self._settings: - # settings_repo = self._settings_repo(self._backend_service, entity) - # for setting in self._settings: - # for new_setting in settings_repo.get_all(): - # if new_setting.attribute == setting.attribute: - # setting_copy = copy.copy(setting) - # setting_copy.id = new_setting.id - # setting_copy.project_id = entity.uuid - # settings_repo.update(setting_copy) - # data["settings"] = self._settings - annotation_classes_mapping = {} - if self._service_provider.annotation_classes: - - for annotation_class in self._project.classes: - annotation_classes_mapping[ - annotation_class.id - ] = self._service_provider.annotation_classes.create_multiple( - entity, [annotation_class] - ) - data["classes"] = self._project.classes - if self._project.workflows: - set_workflow_use_case = SetWorkflowUseCase( - service_provider=self._service_provider, - steps=[i.dict() for i in self._project.workflows], - project=entity, - ) - set_workflow_response = set_workflow_use_case.execute() - data["workflows"] = ( - GetWorkflowsUseCase( - project=self._project, service_provider=self._service_provider - ) - .execute() - .data - ) - if set_workflow_response.errors: - self._response.errors = set_workflow_response.errors - - logger.info( - f"Created project {entity.name} (ID {entity.id}) " - f"with type {constances.ProjectType.get_name(self._response.data.type)}." - ) - return self._response - - -class DeleteProjectUseCase(BaseUseCase): - def __init__( - self, - project_name: str, - service_provider: BaseServiceProvider, - ): - - super().__init__() - self._project_name = project_name - self._service_provider = service_provider - - def execute(self): - use_case = GetProjectByNameUseCase( - name=self._project_name, service_provider=self._service_provider - ) - project_response = use_case.execute() - if project_response.data: - response = self._service_provider.projects.delete(project_response.data) - if response.ok: - logger.info("Successfully deleted project ") - else: - raise AppException("Couldn't delete project") - - -class UpdateProjectUseCase(BaseUseCase): - def __init__( - self, - project: ProjectEntity, - service_provider: BaseServiceProvider, - ): - - super().__init__() - self._project = project - self._service_provider = service_provider - - def validate_settings(self): - for setting in self._project.settings[:]: - if setting.attribute not in constances.PROJECT_SETTINGS_VALID_ATTRIBUTES: - self._project.settings.remove(setting) - if setting.attribute == "ImageQuality" and isinstance(setting.value, str): - setting.value = constances.ImageQuality.get_value(setting.value) - elif setting.attribute == "FrameRate": - if not self._project.type == constances.ProjectType.VIDEO.value: - raise AppValidationException( - "FrameRate is available only for Video projects" - ) - try: - setting.value = float(setting.value) - if ( - not (0.0001 < setting.value < 120) - or decimal.Decimal(str(setting.value)).as_tuple().exponent < -3 - ): - raise AppValidationException( - "The FrameRate value range is between 0.001 - 120" - ) - frame_mode = next( - filter( - lambda x: x.attribute == "FrameMode", self._project.settings - ), - None, - ) - if not frame_mode: - self._project.settings.append( - SettingEntity(attribute="FrameMode", value=1) - ) - except ValueError: - raise AppValidationException("The FrameRate value should be float") - - def validate_project_name(self): - if self._project.name: - if ( - len( - set(self._project.name).intersection( - constances.SPECIAL_CHARACTERS_IN_PROJECT_FOLDER_NAMES - ) - ) - > 0 - ): - self._project.name = "".join( - "_" - if char in constances.SPECIAL_CHARACTERS_IN_PROJECT_FOLDER_NAMES - else char - for char in self._project.name - ) - logger.warning( - "New folder name has special characters. Special characters will be replaced by underscores." - ) - condition = Condition("name", self._project.name, EQ) - response = self._service_provider.projects.list(condition) - if response.ok: - for project in response.data: - if project.name == self._project.name and project != self._project: - logger.error("There are duplicated names.") - raise AppValidationException( - f"Project name {self._project.name} is not unique. " - f"To use SDK please make project names unique." - ) - else: - raise AppException(response.error) - - def execute(self): - if self.is_valid(): - response = self._service_provider.projects.update(self._project) - if not response.ok: - self._response.errors = response.error - else: - self._response.data = response.data - return self._response - - class UnShareProjectUseCase(BaseUseCase): def __init__( self, @@ -434,200 +39,6 @@ def execute(self): return self._response -class GetSettingsUseCase(BaseUseCase): - def __init__(self, project: ProjectEntity, service_provider: BaseServiceProvider): - super().__init__() - self._project = project - self._service_provider = service_provider - - def execute(self): - self._response.data = self._service_provider.projects.list_settings( - self._project - ).data - return self._response - - -class GetWorkflowsUseCase(BaseUseCase): - def __init__(self, project: ProjectEntity, service_provider: BaseServiceProvider): - super().__init__() - self._project = project - self._service_provider = service_provider - - def validate_project_type(self): - if self._project.type in constances.LIMITED_FUNCTIONS: - raise AppValidationException( - constances.LIMITED_FUNCTIONS[self._project.type] - ) - - def execute(self): - if self.is_valid(): - data = [] - workflows = self._service_provider.projects.list_workflows( - self._project - ).data - for workflow in workflows: - workflow_data = workflow.dict() - annotation_classes = self._service_provider.annotation_classes.list( - Condition("project_id", self._project.id, EQ) - ).data - for annotation_class in annotation_classes: - if annotation_class.id == workflow.class_id: - workflow_data["className"] = annotation_class.name - break - data.append(workflow_data) - self._response.data = data - return self._response - - -class UpdateSettingsUseCase(BaseUseCase): - def __init__( - self, - to_update: List, - service_provider: BaseServiceProvider, - project: ProjectEntity, - ): - super().__init__() - self._service_provider = service_provider - self._to_update = to_update - self._project = project - - def validate_image_quality(self): - for setting in self._to_update: - if setting["attribute"].lower() == "imagequality" and isinstance( - setting["value"], str - ): - setting["value"] = constances.ImageQuality.get_value(setting["value"]) - return - - def validate_project_type(self): - for attribute in self._to_update: - if attribute.get( - "attribute", "" - ) == "ImageQuality" and self._project.type in [ - constances.ProjectType.VIDEO.value, - constances.ProjectType.DOCUMENT.value, - ]: - raise AppValidationException( - constances.DEPRICATED_DOCUMENT_VIDEO_MESSAGE - ) - - def execute(self): - if self.is_valid(): - old_settings = self._service_provider.projects.list_settings( - self._project - ).data - attr_id_mapping = {} - for setting in old_settings: - attr_id_mapping[setting.attribute] = setting.id - - new_settings_to_update = [] - for new_setting in self._to_update: - if ( - new_setting["attribute"] - in constances.PROJECT_SETTINGS_VALID_ATTRIBUTES - ): - new_settings_to_update.append( - SettingEntity( - id=attr_id_mapping[new_setting["attribute"]], - attribute=new_setting["attribute"], - value=new_setting["value"], - ) - ) - - response = self._service_provider.projects.set_settings( - project=self._project, - data=new_settings_to_update, - ) - if response.ok: - self._response.data = response.data - else: - self._response.errors = response.error - return self._response - - -class SetWorkflowUseCase(BaseUseCase): - def __init__( - self, - service_provider: BaseServiceProvider, - steps: list, - project: ProjectEntity, - ): - super().__init__() - self._service_provider = service_provider - self._steps = steps - self._project = project - - def validate_project_type(self): - if self._project.type in constances.LIMITED_FUNCTIONS: - raise AppValidationException( - constances.LIMITED_FUNCTIONS[self._project.type] - ) - - def execute(self): - if self.is_valid(): - annotation_classes = self._service_provider.annotation_classes.list( - Condition("project_id", self._project.id, EQ) - ).data - annotation_classes_map = {} - annotations_classes_attributes_map = {} - for annotation_class in annotation_classes: - annotation_classes_map[annotation_class.name] = annotation_class.id - for attribute_group in annotation_class.attribute_groups: - for attribute in attribute_group.attributes: - annotations_classes_attributes_map[ - f"{annotation_class.name}__{attribute_group.name}__{attribute.name}" - ] = attribute.id - - for step in [step for step in self._steps if "className" in step]: - if step.get("id"): - del step["id"] - step["class_id"] = annotation_classes_map.get(step["className"], None) - if not step["class_id"]: - raise AppException("Annotation class not found.") - self._service_provider.projects.set_workflows( - project=self._project, - steps=self._steps, - ) - existing_workflows = self._service_provider.projects.list_workflows( - self._project - ).data - existing_workflows_map = {} - for workflow in existing_workflows: - existing_workflows_map[workflow.step] = workflow.id - - req_data = [] - for step in self._steps: - annotation_class_name = step["className"] - for attribute in step["attribute"]: - attribute_name = attribute["attribute"]["name"] - attribute_group_name = attribute["attribute"]["attribute_group"][ - "name" - ] - if not annotations_classes_attributes_map.get( - f"{annotation_class_name}__{attribute_group_name}__{attribute_name}", - None, - ): - raise AppException( - f"Attribute group name or attribute name not found {attribute_group_name}." - ) - - if not existing_workflows_map.get(step["step"], None): - raise AppException("Couldn't find step in workflow") - req_data.append( - { - "workflow_id": existing_workflows_map[step["step"]], - "attribute_id": annotations_classes_attributes_map[ - f"{annotation_class_name}__{attribute_group_name}__{attribute_name}" - ], - } - ) - self._service_provider.projects.set_project_workflow_attributes( - project=self._project, - attributes=req_data, - ) - return self._response - - class GetTeamUseCase(BaseUseCase): def __init__(self, service_provider: BaseServiceProvider, team_id: int): super().__init__() diff --git a/src/superannotate/lib/infrastructure/controller.py b/src/superannotate/lib/infrastructure/controller.py index 1929aaed8..7be75f4b6 100644 --- a/src/superannotate/lib/infrastructure/controller.py +++ b/src/superannotate/lib/infrastructure/controller.py @@ -16,18 +16,14 @@ from lib.core.entities import AttachmentEntity from lib.core.entities import BaseItemEntity from lib.core.entities import ConfigEntity -from lib.core.entities import ContributorEntity from lib.core.entities import FolderEntity from lib.core.entities import ImageEntity from lib.core.entities import MLModelEntity from lib.core.entities import ProjectEntity -from lib.core.entities import SettingEntity -from lib.core.entities import TeamEntity from lib.core.entities import UserEntity from lib.core.entities.integrations import IntegrationEntity from lib.core.exceptions import AppException from lib.core.reporter import Reporter -from lib.core.response import Response from lib.infrastructure.repositories import S3Repository from lib.infrastructure.serviceprovider import ServiceProvider from lib.infrastructure.services.http_client import HttpClient @@ -52,157 +48,6 @@ def __init__(self, service_provider: ServiceProvider, session: Session): self.service_provider = service_provider -class ProjectManager(BaseManager): - def get_by_id(self, project_id): - use_case = usecases.GetProjectByIDUseCase( - project_id=project_id, service_provider=self.service_provider - ) - response = use_case.execute() - return response - - def get_by_name(self, name: str): - use_case = usecases.GetProjectByNameUseCase( - name=name, service_provider=self.service_provider - ) - response = use_case.execute() - if response.errors: - raise AppException(response.errors) - return response - - def get_metadata( - self, - project: ProjectEntity, - include_annotation_classes: bool = False, - include_settings: bool = False, - include_workflow: bool = False, - include_contributors: bool = False, - include_complete_image_count: bool = False, - ): - use_case = usecases.GetProjectMetaDataUseCase( - project=project, - service_provider=self.service_provider, - include_annotation_classes=include_annotation_classes, - include_settings=include_settings, - include_workflow=include_workflow, - include_contributors=include_contributors, - include_complete_image_count=include_complete_image_count, - ) - return use_case.execute() - - def create(self, entity: ProjectEntity) -> Response: - use_case = usecases.CreateProjectUseCase( - project=entity, service_provider=self.service_provider - ) - return use_case.execute() - - def list(self, condition: Condition): - use_case = usecases.GetProjectsUseCase( - condition=condition, - session=self.session, - service_provider=self.service_provider, - ) - return use_case.execute() - - def delete(self, name: str): - use_case = usecases.DeleteProjectUseCase( - project_name=name, service_provider=self.service_provider - ) - return use_case.execute() - - def update(self, entity: ProjectEntity) -> Response: - use_case = usecases.UpdateProjectUseCase( - entity, service_provider=self.service_provider - ) - return use_case.execute() - - def set_settings(self, project: ProjectEntity, settings: List[SettingEntity]): - use_case = usecases.UpdateSettingsUseCase( - to_update=settings, - service_provider=self.service_provider, - project=project, - ) - return use_case.execute() - - def list_settings(self, project: ProjectEntity): - use_case = usecases.GetSettingsUseCase( - service_provider=self.service_provider, project=project - ) - return use_case.execute() - - def list_workflow(self, project: ProjectEntity): - use_case = usecases.GetWorkflowsUseCase( - project=project, service_provider=self.service_provider - ) - return use_case.execute() - - def set_workflows(self, project: ProjectEntity, steps: List): - use_case = usecases.SetWorkflowUseCase( - service_provider=self.service_provider, - steps=steps, - project=project, - ) - return use_case.execute() - - def add_contributors( - self, - team: TeamEntity, - project: ProjectEntity, - contributors: List[ContributorEntity], - ): - project = self.get_metadata(project).data - use_case = usecases.AddContributorsToProject( - team=team, - project=project, - contributors=contributors, - service_provider=self.service_provider, - ) - return use_case.execute() - - def un_share(self, project: ProjectEntity, user_id: str): - use_case = usecases.UnShareProjectUseCase( - service_provider=self.service_provider, - project=project, - user_id=user_id, - ) - return use_case.execute() - - def assign_items( - self, project: ProjectEntity, folder: FolderEntity, item_names: list, user: str - ): - use_case = usecases.AssignItemsUseCase( - project=project, - service_provider=self.service_provider, - folder=folder, - item_names=item_names, - user=user, - ) - return use_case.execute() - - def un_assign_items( - self, project: ProjectEntity, folder: FolderEntity, item_names: list - ): - use_case = usecases.UnAssignItemsUseCase( - project=project, - service_provider=self.service_provider, - folder=folder, - item_names=item_names, - ) - return use_case.execute() - - def upload_priority_scores( - self, project: ProjectEntity, folder: FolderEntity, scores, project_folder_name - ): - use_case = usecases.UploadPriorityScoresUseCase( - reporter=Reporter(), - project=project, - folder=folder, - scores=scores, - service_provider=self.service_provider, - project_folder_name=project_folder_name, - ) - return use_case.execute() - - class ItemManager(BaseManager): def get_by_name( self, @@ -589,7 +434,6 @@ def __init__(self, config: ConfigEntity): ) self._user = self.get_current_user() self._team = self.get_team().data - self.projects = ProjectManager(self.service_provider, self._session) self.items = ItemManager(self.service_provider, self._session) self.annotations = AnnotationManager( self.service_provider, config, self._session @@ -771,9 +615,9 @@ def upload_images_to_project( image_quality_in_editor: str = None, from_s3_bucket=None, ): + project_name, folder_name = extract_project_folder(project_name) project = self.get_project(project_name) - folder = self.get_folder(project, folder_name) - + folder = project.get_folder(folder_name) return usecases.UploadImagesToProject( project=project, folder=folder, diff --git a/src/superannotate/lib/infrastructure/serviceprovider.py b/src/superannotate/lib/infrastructure/serviceprovider.py index bf18ef46a..cc0f6cd6a 100644 --- a/src/superannotate/lib/infrastructure/serviceprovider.py +++ b/src/superannotate/lib/infrastructure/serviceprovider.py @@ -17,7 +17,6 @@ from lib.infrastructure.services.integration import IntegrationService from lib.infrastructure.services.item import ItemService from lib.infrastructure.services.models import ModelsService -from lib.infrastructure.services.project import ProjectService from lib.infrastructure.services.subset import SubsetService @@ -44,7 +43,6 @@ class ServiceProvider(BaseServiceProvider): def __init__(self, client: HttpClient): self.client = client - self.projects = ProjectService(client) self.items = ItemService(client) self.annotations = AnnotationService(client) self.annotation_classes = AnnotationClassService(client) diff --git a/src/superannotate/lib/infrastructure/services/__init__.py b/src/superannotate/lib/infrastructure/services/__init__.py index 07f9ae493..b22b0ccf0 100644 --- a/src/superannotate/lib/infrastructure/services/__init__.py +++ b/src/superannotate/lib/infrastructure/services/__init__.py @@ -1,12 +1,10 @@ from .annotation_class import AnnotationClassService from .http_client import HttpClient from .item import ItemService -from .project import ProjectService __all__ = [ "HttpClient", - "ProjectService", "ItemService", "AnnotationClassService", ] diff --git a/src/superannotate/lib/infrastructure/services/project.py b/src/superannotate/lib/infrastructure/services/project.py deleted file mode 100644 index 277a2710a..000000000 --- a/src/superannotate/lib/infrastructure/services/project.py +++ /dev/null @@ -1,174 +0,0 @@ -from typing import List - -from lib.core import entities -from lib.core.conditions import Condition -from lib.core.service_types import ProjectResponse -from lib.core.service_types import ServiceResponse -from lib.core.service_types import SettingsListResponse -from lib.core.serviceproviders import BaseProjectService - - -class ProjectService(BaseProjectService): - URL = "project" - URL_LIST = "projects" - URL_GET = "project/{}" - URL_SETTINGS = "project/{}/settings" - URL_WORKFLOW = "project/{}/workflow" - URL_SHARE = "project/{}/share/bulk" - URL_SHARE_PROJECT = "project/{}/share" - URL_WORKFLOW_ATTRIBUTE = "project/{}/workflow_attribute" - URL_UPLOAD_PRIORITY_SCORES = "images/updateEntropy" - URL_ASSIGN_ITEMS = "images/editAssignment/" - URL_GET_BY_ID = "project/{project_id}" - - def get_by_id(self, project_id: int): - params = {} - result = self.client.request( - self.URL_GET_BY_ID.format(project_id=project_id), - "get", - params=params, - content_type=ProjectResponse, - ) - return result - - def get(self, uuid: int): - return self.client.request( - self.URL_GET.format(uuid), "get", content_type=ProjectResponse - ) - - def create(self, entity: entities.ProjectEntity) -> ServiceResponse: - entity.team_id = self.client.team_id - return self.client.request( - self.URL, "post", data=entity, content_type=ProjectResponse - ) - - def list(self, condition: Condition = None): - return self.client.paginate( - url=self.URL_LIST, - query_params=condition.get_as_params_dict(), - item_type=entities.ProjectEntity, - ) - - def update(self, entity: entities.ProjectEntity): - return self.client.request( - self.URL_GET.format(entity.id), - "put", - data=entity, - content_type=ProjectResponse, - ) - - def delete(self, entity: entities.ProjectEntity) -> ServiceResponse: - return self.client.request(self.URL_GET.format(entity.id), "delete") - - def list_settings(self, project: entities.ProjectEntity): - return self.client.request( - self.URL_SETTINGS.format(project.id), - "get", - content_type=SettingsListResponse, - ) - - def set_settings( - self, project: entities.ProjectEntity, data: List[entities.SettingEntity] - ): - return self.client.request( - self.URL_SETTINGS.format(project.id), - "put", - data={"settings": data}, - ) - - def share(self, project: entities.ProjectEntity, users: list): - return self.client.request( - self.URL_SHARE.format(project.id), - "post", - data={"users": users}, - ) - - def list_workflows(self, project: entities.ProjectEntity): - return self.client.paginate( - self.URL_WORKFLOW.format(project.id), item_type=entities.WorkflowEntity - ) - - def set_workflow( - self, project: entities.ProjectEntity, workflow: entities.WorkflowEntity - ): - return self.client.request( - self.URL_WORKFLOW.format(project.id), - "post", - data={"steps": [workflow]}, - ) - - # TODO check - def set_workflows(self, project: entities.ProjectEntity, steps: list): - return self.client.request( - self.URL_WORKFLOW.format(project.id), - "post", - data={"steps": steps}, - ) - - def set_project_workflow_attributes( - self, project: entities.ProjectEntity, attributes: list - ): - return self.client.request( - self.URL_WORKFLOW_ATTRIBUTE.format(project.id), - "post", - data={"data": attributes}, - ) - - def un_share(self, project: entities.ProjectEntity, user_id: int): - return self.client.request( - self.URL_SHARE_PROJECT.format(project.id), - "delete", - data={"user_id": user_id}, - ) - - def assign_items( - self, - project: entities.ProjectEntity, - folder: entities.FolderEntity, - user: str, - item_names: List[str], - ) -> ServiceResponse: - return self.client.request( - self.URL_ASSIGN_ITEMS, - "put", - params={"project_id": project.id}, - data={ - "image_names": item_names, - "assign_user_id": user, - "folder_name": folder.name, - }, - content_type=ServiceResponse, - ) - - def un_assign_items( - self, - project: entities.ProjectEntity, - folder: entities.FolderEntity, - item_names: List[str], - ) -> ServiceResponse: - return self.client.request( - self.URL_ASSIGN_ITEMS, - "put", - params={"project_id": project.id}, - data={ - "image_names": item_names, - "remove_user_ids": ["all"], - "folder_name": folder.name, - }, - ) - - def upload_priority_scores( - self, - project: entities.ProjectEntity, - folder: entities.FolderEntity, - priorities: list, - ): - return self.client.request( - self.URL_UPLOAD_PRIORITY_SCORES, - "post", - params={ - "project_id": project.id, - "folder_id": folder.id, - }, - data={"image_entropies": priorities}, - ) diff --git a/tests/integration/projects/test_basic_project.py b/tests/integration/projects/test_basic_project.py index 8f5d899a3..232af3f08 100644 --- a/tests/integration/projects/test_basic_project.py +++ b/tests/integration/projects/test_basic_project.py @@ -130,7 +130,14 @@ def test_workflow_get(self): ], ) workflows = sa.get_project_workflow(self.PROJECT_NAME) - metadata = sa.get_project_metadata(self.PROJECT_NAME, include_workflow=True) + metadata = sa.get_project_metadata( + self.PROJECT_NAME, + include_workflow=True, + include_complete_item_count=True, + include_settings=True, + include_contributors=True, + include_annotation_classes=True, + ) assert metadata["workflows"][0]["className"] == "class1" assert metadata["workflows"][1]["className"] == "class2" self.assertEqual(workflows[0]["className"], "class1") diff --git a/tests/integration/projects/test_create_project.py b/tests/integration/projects/test_create_project.py index ce49e086f..5df63fe73 100644 --- a/tests/integration/projects/test_create_project.py +++ b/tests/integration/projects/test_create_project.py @@ -4,6 +4,7 @@ import src.superannotate.lib.core as constances from src.superannotate import AppException from src.superannotate import SAClient +from superannotate_core.core.exceptions import SAValidationException sa = SAClient() @@ -85,9 +86,24 @@ class TestCreateVectorProject(ProjectCreateBaseTestCase): PROJECT_TYPE = "Vector" def test_create_project_datetime(self): - project = sa.create_project(self.PROJECT, "desc", self.PROJECT_TYPE) + instructions_link = "https://en.wikipedia.org/wiki/Wiki" + project = sa.create_project( + self.PROJECT, + "desc", + self.PROJECT_TYPE, + instructions_link=instructions_link, + ) metadata = sa.get_project_metadata(project["name"]) - assert "Z" not in metadata["createdAt"] + # assert "Z" not in metadata["createdAt"] + assert instructions_link == metadata["instructions_link"] + + def test_create_project_with_not_unique_name(self): + sa.create_project(self.PROJECT, "desc", self.PROJECT_TYPE) + with self.assertRaisesRegexp( + SAValidationException, + f"Project name {self.PROJECT} is not unique. To use SDK please make project names unique.", + ): + sa.create_project(self.PROJECT, "desc", self.PROJECT_TYPE) def test_create_project_with_wrong_type(self): with self.assertRaisesRegexp( @@ -106,7 +122,7 @@ def test_create_project_with_settings(self): project = sa.get_project_metadata(self.PROJECT, include_settings=True) for setting in project["settings"]: if setting["attribute"] == "ImageQuality": - assert setting["value"] == "original" + assert setting["value"] == 100 def test_create_project_with_classes_and_workflows(self): project = sa.create_project( @@ -185,3 +201,5 @@ def test_create_video_project_frame_mode_on(self): for setting in project["settings"]: if setting["attribute"] == "FrameRate": assert setting["value"] == 1.0 + if setting["attribute"] == "FrameMode": + assert setting["value"] == 1 diff --git a/tests/integration/projects/test_set_project_status.py b/tests/integration/projects/test_set_project_status.py index 358b89eda..efc4f9264 100644 --- a/tests/integration/projects/test_set_project_status.py +++ b/tests/integration/projects/test_set_project_status.py @@ -2,8 +2,9 @@ from unittest.mock import patch from src.superannotate import AppException -from src.superannotate.lib.core.service_types import ServiceResponse from superannotate import SAClient +from superannotate_core.app import Project +from superannotate_core.core.exceptions import SAInvalidInput sa = SAClient() @@ -40,9 +41,9 @@ def test_set_project_status(self): self.assertEqual(status, project["status"]) self.assertEqual(len(cm.output), len(self.PROJECT_STATUSES)) - @patch("lib.infrastructure.services.project.ProjectService.update") + @patch("lib.app.interface.sdk_interface.Project.update") def test_set_project_status_fail(self, update_function): - update_function.return_value = ServiceResponse(_error="ERROR") + update_function.return_value = Project.from_json({"status": "OnHold"}) with self.assertRaisesRegexp( AppException, f"Failed to change {self.PROJECT_NAME} status.", @@ -58,7 +59,7 @@ def test_set_project_status_via_invalid_status(self): def test_set_project_status_via_invalid_project(self): with self.assertRaisesRegexp( - AppException, + SAInvalidInput, "Project not found.", ): sa.set_project_status(project="Invalid name", status="Completed") diff --git a/tests/integration/settings/test_settings.py b/tests/integration/settings/test_settings.py index cf2fb85d7..e3d8b0073 100644 --- a/tests/integration/settings/test_settings.py +++ b/tests/integration/settings/test_settings.py @@ -1,7 +1,7 @@ from unittest import TestCase -from src.superannotate import AppException from src.superannotate import SAClient +from superannotate_core.core.exceptions import SAValidationException sa = SAClient() @@ -64,7 +64,7 @@ def test_create_project_with_settings(self): def test_frame_rate_invalid_range_value(self): with self.assertRaisesRegexp( - AppException, "FrameRate is available only for Video projects" + SAValidationException, "FrameRate is available only for Video projects" ): sa.create_project( self.PROJECT_NAME, @@ -117,7 +117,7 @@ def test_frame_rate_float(self): def test_frame_rate_invalid_range_value(self): with self.assertRaisesRegexp( - AppException, "The FrameRate value range is between 0.001 - 120" + SAValidationException, "The FrameRate value range is between 0.001 - 120" ): sa.create_project( self.PROJECT_NAME, @@ -128,7 +128,7 @@ def test_frame_rate_invalid_range_value(self): def test_frame_rate_invalid_str_value(self): with self.assertRaisesRegexp( - AppException, "The FrameRate value should be float" + SAValidationException, "The FrameRate value should be float" ): sa.create_project( self.PROJECT_NAME,