Skip to content

Commit d2ad859

Browse files
Vaghinak BasentsyanVaghinak Basentsyan
authored andcommitted
Added delete_annotation tests
1 parent 73f62f3 commit d2ad859

File tree

10 files changed

+131
-53
lines changed

10 files changed

+131
-53
lines changed

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
minversion = 3.0
33
log_cli=true
44
python_files = test_*.py
5-
addopts = -n32 --dist=loadscope
5+
;addopts = -n32 --dist=loadscope

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pydicom>=2.0.0
22
boto3>=1.14.53
33
requests==2.26.0
44
requests-toolbelt>=0.9.1
5-
tqdm=4.48.2
5+
tqdm==4.48.2
66
pillow>=7.2.0
77
numpy>=1.19.0
88
matplotlib>=3.3.1

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ def init(path_to_config_json: str):
5959
:param path_to_config_json: Location to config JSON file
6060
:type path_to_config_json: str or Path
6161
"""
62-
global controller
63-
controller = Controller(logger, path_to_config_json)
62+
# global controller
63+
controller.init(path_to_config_json)
6464

6565

6666
@validate_input

src/superannotate/lib/app/mixp/decorators.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import functools
2-
import logging
32
import sys
43

54
from mixpanel import Mixpanel
@@ -11,7 +10,7 @@
1110

1211
mp = Mixpanel(TOKEN)
1312

14-
controller = Controller(logger=logging.getLogger())
13+
controller = Controller.get_instance()
1514
res = controller.get_team()
1615
user_id, team_name = res.data.creator_id, res.data.name
1716

src/superannotate/lib/core/serviceproviders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def delete_image_annotations(
303303
project_id: int,
304304
folder_id: int = None,
305305
image_names: List[str] = None,
306-
) -> int:
306+
) -> dict:
307307
raise NotImplementedError
308308

309309
def get_annotations_delete_progress(

src/superannotate/lib/core/usecases.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4266,7 +4266,7 @@ def __init__(
42664266
extensions=constances.DEFAULT_IMAGE_EXTENSIONS,
42674267
annotation_status="NotStarted",
42684268
from_s3_bucket=None,
4269-
exclude_file_patterns=constances.DEFAULT_FILE_EXCLUDE_PATTERNS,
4269+
exclude_file_patterns: List[str] = constances.DEFAULT_FILE_EXCLUDE_PATTERNS,
42704270
recursive_sub_folders: bool = False,
42714271
image_quality_in_editor=None,
42724272
):
@@ -4286,6 +4286,10 @@ def __init__(
42864286
self._from_s3_bucket = from_s3_bucket
42874287
self._extensions = extensions
42884288
self._recursive_sub_folders = recursive_sub_folders
4289+
if exclude_file_patterns:
4290+
list(exclude_file_patterns).extend(
4291+
list(constances.DEFAULT_FILE_EXCLUDE_PATTERNS)
4292+
)
42894293
self._exclude_file_patterns = exclude_file_patterns
42904294
self._annotation_status = annotation_status
42914295

@@ -4302,12 +4306,19 @@ def exclude_file_patterns(self):
43024306
return self._exclude_file_patterns
43034307

43044308
def validate_annotation_status(self):
4305-
if self._annotation_status and self._annotation_status.lower() not in constances.AnnotationStatus.values():
4309+
if (
4310+
self._annotation_status
4311+
and self._annotation_status.lower()
4312+
not in constances.AnnotationStatus.values()
4313+
):
43064314
raise AppValidationException("Invalid annotations status")
43074315

43084316
def validate_extensions(self):
43094317
if self._extensions and not all(
4310-
[extension in constances.DEFAULT_IMAGE_EXTENSIONS for extension in self._extensions]
4318+
[
4319+
extension in constances.DEFAULT_IMAGE_EXTENSIONS
4320+
for extension in self._extensions
4321+
]
43114322
):
43124323
raise AppValidationException("")
43134324

@@ -4420,14 +4431,16 @@ def paths(self):
44204431
paths.append(key)
44214432
break
44224433

4423-
paths = [str(path) for path in paths]
4424-
return [
4425-
path
4426-
for path in paths
4427-
if "___objects" not in path
4428-
and "___fuse" not in path
4429-
and "___pixel" not in path
4430-
]
4434+
data = []
4435+
for path in paths:
4436+
if all(
4437+
[
4438+
True if exclude_pattern not in str(path) else False
4439+
for exclude_pattern in self.exclude_file_patterns
4440+
]
4441+
):
4442+
data.append(str(path))
4443+
return data
44314444

44324445
@property
44334446
def images_to_upload(self):
@@ -4490,8 +4503,9 @@ def execute(self):
44904503
annotation_status=self._annotation_status,
44914504
).execute()
44924505

4493-
attachments, duplications = response.data
4506+
attachments, attach_duplications = response.data
44944507
uploaded.extend(attachments)
4508+
duplications.extend(attach_duplications)
44954509
uploaded = [image["name"] for image in uploaded]
44964510
failed_images = [image.split("/")[-1] for image in failed_images]
44974511

@@ -4517,17 +4531,12 @@ def __init__(
45174531

45184532
def execute(self) -> Response:
45194533

4520-
if self._folder.name == "root" and not self._image_names:
4521-
response = self._backend_service.delete_image_annotations(
4522-
project_id=self._project.uuid, team_id=self._project.team_id, image_names=self._image_names
4523-
)
4524-
else:
4525-
response = self._backend_service.delete_image_annotations(
4526-
project_id=self._project.uuid,
4527-
team_id=self._project.team_id,
4528-
folder_id=self._folder.uuid,
4529-
image_names=self._image_names,
4530-
)
4534+
response = self._backend_service.delete_image_annotations(
4535+
project_id=self._project.uuid,
4536+
team_id=self._project.team_id,
4537+
folder_id=self._folder.uuid,
4538+
image_names=self._image_names,
4539+
)
45314540

45324541
if response:
45334542
timeout_start = time.time()

src/superannotate/lib/infrastructure/controller.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,45 +38,58 @@ def __call__(cls, *args, **kwargs):
3838
SingleInstanceMetaClass._instances[cls] = super().__call__(*args, **kwargs)
3939
return SingleInstanceMetaClass._instances[cls]
4040

41+
def get_instance(cls):
42+
if cls._instances:
43+
return cls._instances[cls]
44+
4145

4246
class BaseController(metaclass=SingleInstanceMetaClass):
4347
def __init__(self, logger, config_path=constances.CONFIG_FILE_LOCATION):
44-
self._config_path = config_path
48+
self._config_path = None
49+
self._backend_client = None
4550
self._logger = logger
51+
self._s3_upload_auth_data = None
52+
self._projects = None
53+
self._folders = None
54+
self._teams = None
55+
self._images = None
56+
self._ml_models = None
57+
self._team_id = None
58+
self.init(config_path)
59+
60+
def init(self, config_path):
61+
self._config_path = config_path
4662
token, main_endpoint = (
47-
self.configs.get_one("token"),
48-
self.configs.get_one("main_endpoint"),
63+
self.configs.get_one("token").value,
64+
self.configs.get_one("main_endpoint").value,
4965
)
5066
if not main_endpoint:
5167
self.configs.insert(ConfigEntity("main_endpoint", constances.BACKEND_URL))
5268
if not token:
5369
self.configs.insert(ConfigEntity("token", ""))
54-
logger.warning("Fill config.json")
70+
self._logger.warning("Fill config.json")
5571
return
5672
verify_ssl_entity = self.configs.get_one("ssl_verify")
5773
if not verify_ssl_entity:
5874
verify_ssl = True
5975
else:
6076
verify_ssl = verify_ssl_entity.value
61-
self._backend_client = SuperannotateBackendService(
62-
api_url=self.configs.get_one("main_endpoint").value,
63-
auth_token=ConfigRepository().get_one("token").value,
64-
logger=logger,
65-
verify_ssl=verify_ssl
66-
)
67-
self._s3_upload_auth_data = None
68-
self._projects = None
69-
self._folders = None
70-
self._teams = None
71-
self._images = None
72-
self._ml_models = None
73-
self._team_id = None
77+
if not self._backend_client:
78+
self._backend_client = SuperannotateBackendService(
79+
api_url=self.configs.get_one("main_endpoint").value,
80+
auth_token=self.configs.get_one("token").value,
81+
logger=self._logger,
82+
verify_ssl=verify_ssl,
83+
)
84+
else:
85+
self._backend_client.api_url = self.configs.get_one("main_endpoint").value
86+
self._backend_client._auth_token = self.configs.get_one("token").value
7487

7588
def set_token(self, token):
7689
self.configs.insert(ConfigEntity("token", token))
7790
self._backend_client = SuperannotateBackendService(
7891
api_url=self.configs.get_one("main_endpoint").value,
79-
auth_token=ConfigRepository().get_one("token").value,
92+
auth_token=self.configs.get_one("token").value,
8093
logger=self._logger,
8194
)
8295

@@ -713,7 +726,7 @@ def search_annotation_classes(self, project_name: str, name_prefix: str = None):
713726
classes=AnnotationClassRepository(
714727
service=self._backend_client, project=project_entity
715728
),
716-
condition=condition
729+
condition=condition,
717730
)
718731
return use_case.execute()
719732

src/superannotate/lib/infrastructure/services.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
from contextlib import contextmanager
32
from datetime import datetime
43
from typing import Dict
@@ -27,7 +26,10 @@ class BaseBackendService(SuerannotateServiceProvider):
2726
Base service class
2827
"""
2928

30-
def __init__(self, api_url: str, auth_token: str, logger, paginate_by=None, verify_ssl=True):
29+
@classmethod
30+
def __init__(
31+
self, api_url: str, auth_token: str, logger, paginate_by=None, verify_ssl=True
32+
):
3133
self.api_url = api_url
3234
self._auth_token = auth_token
3335
self.logger = logger
@@ -956,7 +958,7 @@ def delete_image_annotations(
956958
project_id: int,
957959
folder_id: int = None,
958960
image_names: List[str] = None,
959-
) -> int:
961+
) -> dict:
960962
delete_annotations_url = urljoin(self.api_url, self.URL_DELETE_ANNOTATIONS)
961963
params = {"team_id": team_id, "project_id": project_id}
962964
data = {}

tests/integration/test_annotation_delete.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,25 @@ def test_delete_annotations(self):
4646
self.assertIsNotNone(data["annotation_json_filename"])
4747
self.assertIsNone(data["annotation_mask"])
4848

49+
@pytest.mark.skip(
50+
"waiting for deployment to dev",
51+
)
52+
def test_delete_annotations_by_name(self):
53+
sa.upload_images_from_folder_to_project(
54+
self.PROJECT_NAME, self.folder_path, annotation_status="InProgress"
55+
)
56+
sa.create_annotation_classes_from_classes_json(
57+
self.PROJECT_NAME, self.folder_path + "/classes/classes.json"
58+
)
59+
sa.upload_annotations_from_folder_to_project(
60+
self.PROJECT_NAME, f"{self.folder_path}"
61+
)
62+
sa.delete_annotations(self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])
63+
data = sa.get_image_annotations(self.PROJECT_NAME, self.EXAMPLE_IMAGE_1)
64+
self.assertIsNone(data["annotation_json"])
65+
self.assertIsNotNone(data["annotation_json_filename"])
66+
self.assertIsNone(data["annotation_mask"])
67+
4968
@pytest.mark.skip(
5069
"waiting for deployment to dev",
5170
)
@@ -61,3 +80,39 @@ def test_delete_annotations_by_not_existing_name(self):
6180
)
6281
self.assertRaises(Exception, sa.delete_annotations, self.PROJECT_NAME, [self.EXAMPLE_IMAGE_2])
6382

83+
@pytest.mark.skip(
84+
"waiting for deployment to dev",
85+
)
86+
def test_delete_annotations_wrong_path(self):
87+
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME)
88+
sa.upload_images_from_folder_to_project(
89+
f"{self.PROJECT_NAME}/{self.TEST_FOLDER_NAME}", self.folder_path, annotation_status="InProgress"
90+
)
91+
sa.create_annotation_classes_from_classes_json(
92+
self.PROJECT_NAME, self.folder_path + "/classes/classes.json"
93+
)
94+
sa.upload_annotations_from_folder_to_project(
95+
f"{self.PROJECT_NAME}/{self.TEST_FOLDER_NAME}", f"{self.folder_path}"
96+
)
97+
self.assertRaises(Exception, sa.delete_annotations, self.PROJECT_NAME, [self.EXAMPLE_IMAGE_1])
98+
99+
@pytest.mark.skip(
100+
"waiting for deployment to dev",
101+
)
102+
def test_delete_annotations_from_folder(self):
103+
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME)
104+
105+
sa.upload_images_from_folder_to_project(
106+
f"{self.PROJECT_NAME}/{self.TEST_FOLDER_NAME}", self.folder_path, annotation_status="InProgress"
107+
)
108+
sa.create_annotation_classes_from_classes_json(
109+
self.PROJECT_NAME, self.folder_path + "/classes/classes.json"
110+
)
111+
sa.upload_annotations_from_folder_to_project(
112+
f"{self.PROJECT_NAME}/{self.TEST_FOLDER_NAME}", f"{self.folder_path}"
113+
)
114+
sa.delete_annotations(f"{self.PROJECT_NAME}/{self.TEST_FOLDER_NAME}", [self.EXAMPLE_IMAGE_1])
115+
data = sa.get_image_annotations(f"{self.PROJECT_NAME}/{self.TEST_FOLDER_NAME}", self.EXAMPLE_IMAGE_1)
116+
self.assertIsNone(data["annotation_json"])
117+
self.assertIsNotNone(data["annotation_json_filename"])
118+
self.assertIsNone(data["annotation_mask"])

tests/integration/test_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def test_upload_annotations_from_folder_to_project(self):
5858
sa.upload_images_from_folder_to_project(
5959
self.PROJECT_NAME,
6060
self.folder_path,
61-
annotation_status="InProgress",
61+
annotation_status="Completed",
6262
)
6363
uploaded_annotations, _, _ = sa.upload_annotations_from_folder_to_project(
6464
self.PROJECT_NAME, self.folder_path

0 commit comments

Comments
 (0)