diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 110c42315..263963c84 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,8 +6,20 @@ History All release highlights of this project will be documented in this file. +4.4.17 - December 21, 2023 +__________________________ + +**Added** + + - ``SAClient.upload_annotations()`` added default values to the annotations during the upload. + +**Updated** + + - Fixed `SAClient.search_project() search with special characters.` + - ``pandas`` dependency ``pandas~=2.0`` + 4.4.16 - November 12, 2023 -_______________________ +__________________________ **Added** @@ -16,9 +28,9 @@ _______________________ **Updated** - Documentation updates - - pillow dependency ``pillow>=9.5,~=10.0``. - - opencv dependency replaced by ``opencv-python-headless~=4.7``. - - pydantic dependency ``pydantic>=1.10,!=2.0.*``. + - ``pillow`` dependency ``pillow>=9.5,~=10.0``. + - ``opencv`` dependency replaced by ``opencv-python-headless~=4.7``. + - ``pydantic`` dependency ``pydantic>=1.10,!=2.0.*``. 4.4.15 - August 20, 2023 ________________________ diff --git a/requirements.txt b/requirements.txt index 88a3562b5..d3fae5493 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ opencv-python-headless~=4.7 packaging~=23.1 plotly~=5.14 email-validator~=2.0 -pandas~=1.3 +pandas~=2.0 ffmpeg-python~=0.2 pillow>=9.5,~=10.0 tqdm~=4.66.1 diff --git a/src/superannotate/__init__.py b/src/superannotate/__init__.py index 2f25a3f8d..aae84e184 100644 --- a/src/superannotate/__init__.py +++ b/src/superannotate/__init__.py @@ -3,7 +3,7 @@ import sys -__version__ = "4.4.16" +__version__ = "4.4.17" sys.path.append(os.path.split(os.path.realpath(__file__))[0]) diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index a467bbe8e..162940830 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -1668,6 +1668,7 @@ def upload_annotations( folder=folder, annotations=annotations, keep_status=keep_status, + user=self.controller.current_user, ) if response.errors: raise AppException(response.errors) diff --git a/src/superannotate/lib/core/usecases/annotations.py b/src/superannotate/lib/core/usecases/annotations.py index 6fa39da63..f3b5d963a 100644 --- a/src/superannotate/lib/core/usecases/annotations.py +++ b/src/superannotate/lib/core/usecases/annotations.py @@ -295,6 +295,7 @@ def __init__( folder: FolderEntity, annotations: List[dict], service_provider: BaseServiceProvider, + user: UserEntity, keep_status: bool = False, ): super().__init__(reporter) @@ -304,6 +305,7 @@ def __init__( self._service_provider = service_provider self._keep_status = keep_status self._report = Report([], [], [], []) + self._user = user def validate_project_type(self): if self._project.type == constants.ProjectType.PIXEL.value: @@ -439,6 +441,9 @@ def execute(self): annotation_name = annotation["metadata"]["name"] item = name_item_map.get(annotation_name) if item: + annotation = UploadAnnotationUseCase.set_defaults( + self._user.email, annotation, self._project.type + ) items_to_upload.append( ItemToUpload(item=item, annotation_json=annotation) ) diff --git a/src/superannotate/lib/infrastructure/controller.py b/src/superannotate/lib/infrastructure/controller.py index 82e139d36..98a9d343f 100644 --- a/src/superannotate/lib/infrastructure/controller.py +++ b/src/superannotate/lib/infrastructure/controller.py @@ -568,6 +568,7 @@ def upload_multiple( folder: FolderEntity, annotations: List[dict], keep_status: bool, + user: UserEntity, ): use_case = usecases.UploadAnnotationsUseCase( reporter=Reporter(), @@ -576,6 +577,7 @@ def upload_multiple( annotations=annotations, service_provider=self.service_provider, keep_status=keep_status, + user=user, ) return use_case.execute() diff --git a/src/superannotate/lib/infrastructure/services/http_client.py b/src/superannotate/lib/infrastructure/services/http_client.py index 5935b4662..071a907c5 100644 --- a/src/superannotate/lib/infrastructure/services/http_client.py +++ b/src/superannotate/lib/infrastructure/services/http_client.py @@ -13,6 +13,7 @@ import aiohttp import requests +from aiohttp.client_exceptions import ClientError from lib.core.exceptions import AppException from lib.core.service_types import ServiceResponse from lib.core.serviceproviders import BaseClient @@ -197,7 +198,7 @@ def serialize_response( if not response.ok: if response.status_code in (502, 504): data[ - "_error" + "res_error" ] = "Our service is currently unavailable, please try again later." return content_type(**data) else: @@ -234,7 +235,13 @@ async def request(self, *args, **kwargs) -> aiohttp.ClientResponse: for _ in range(attempts): delay += self.BACKOFF_FACTOR attempts -= 1 - response = await super()._request(*args, **kwargs) + try: + response = await super()._request(*args, **kwargs) + except ClientError: + if not attempts: + raise + await asyncio.sleep(delay) + continue if response.status not in self.RETRY_STATUS_CODES or not attempts: return response await asyncio.sleep(delay) diff --git a/src/superannotate/lib/infrastructure/services/project.py b/src/superannotate/lib/infrastructure/services/project.py index c304b0d48..277a2710a 100644 --- a/src/superannotate/lib/infrastructure/services/project.py +++ b/src/superannotate/lib/infrastructure/services/project.py @@ -44,9 +44,8 @@ def create(self, entity: entities.ProjectEntity) -> ServiceResponse: def list(self, condition: Condition = None): return self.client.paginate( - url=f"{self.URL_LIST}?{condition.build_query()}" - if condition - else self.URL_LIST, + url=self.URL_LIST, + query_params=condition.get_as_params_dict(), item_type=entities.ProjectEntity, ) diff --git a/tests/integration/annotations/test_upload_annotations.py b/tests/integration/annotations/test_upload_annotations.py index 302e35519..cb10e81c7 100644 --- a/tests/integration/annotations/test_upload_annotations.py +++ b/tests/integration/annotations/test_upload_annotations.py @@ -71,6 +71,8 @@ def test_annotation_folder_upload_download(self): assert i["annotation_status"] == "InProgress" assert annotation["instances"][-1]["type"] == "tag" assert annotation["instances"][-2]["type"] == "tag" + assert annotation["instances"][-2]["probability"] == 100 + assert annotation["instances"][-2]["creationType"] == "Preannotation" def test_upload_keep_true(self): self._attach_items() diff --git a/tests/integration/projects/test_search_project.py b/tests/integration/projects/test_search_project.py new file mode 100644 index 000000000..f96736061 --- /dev/null +++ b/tests/integration/projects/test_search_project.py @@ -0,0 +1,62 @@ +from src.superannotate import SAClient +from tests.integration.base import BaseTestCase + +sa = SAClient() + + +class TestProjectSearch(BaseTestCase): + PROJECT_NAME = "TestProjectSearch" + PROJECT_DESCRIPTION = "Desc" + PROJECT_TYPE = "Vector" + PROJECT_NAME_2 = "TestProjectSearch2" + REPLACED_PROJECT_NAME = "TestProjectSearchReplaced" + PROJECT_NAME_CONTAIN_SPECIAL_CHARACTER = "TestProjectName!@#$%+" + + def setUp(self, *args, **kwargs): + self.tearDown() + self._project = sa.create_project( + self.PROJECT_NAME, self.PROJECT_DESCRIPTION, self.PROJECT_TYPE + ) + self._project_2 = sa.create_project( + self.PROJECT_NAME_2, self.PROJECT_DESCRIPTION, self.PROJECT_TYPE + ) + + def tearDown(self) -> None: + projects = [] + projects.extend(sa.search_projects(self.PROJECT_NAME, return_metadata=True)) + projects.extend( + sa.search_projects(self.REPLACED_PROJECT_NAME, return_metadata=True) + ) + projects.extend( + sa.search_projects( + self.PROJECT_NAME_CONTAIN_SPECIAL_CHARACTER, return_metadata=True + ) + ) + for project in projects: + try: + sa.delete_project(project) + except Exception as _: + pass + + def test_project_search(self): + # search before rename + result = sa.search_projects(self.PROJECT_NAME, return_metadata=True) + assert len(result) == 2 + + # search after rename + sa.rename_project(self.PROJECT_NAME, self.REPLACED_PROJECT_NAME) + meta = sa.get_project_metadata(self.REPLACED_PROJECT_NAME) + self.assertEqual(meta["name"], self.REPLACED_PROJECT_NAME) + result = sa.search_projects(self.REPLACED_PROJECT_NAME, return_metadata=True) + assert len(result) == 1 + assert result[0]["name"] == self.REPLACED_PROJECT_NAME + + def test_project_search_special_character(self): + sa.rename_project( + self.PROJECT_NAME, self.PROJECT_NAME_CONTAIN_SPECIAL_CHARACTER + ) + meta = sa.get_project_metadata(self.PROJECT_NAME_CONTAIN_SPECIAL_CHARACTER) + self.assertEqual(meta["name"], self.PROJECT_NAME_CONTAIN_SPECIAL_CHARACTER) + result = sa.search_projects("!@#$%+", return_metadata=True) + assert len(result) == 1 + assert result[0]["name"] == self.PROJECT_NAME_CONTAIN_SPECIAL_CHARACTER