Skip to content

Commit 3df7fac

Browse files
authored
Merge pull request #172 from superannotateai/re-design-sdk
Re design sdk
2 parents 57d223e + 13e07d0 commit 3df7fac

File tree

11 files changed

+106
-36
lines changed

11 files changed

+106
-36
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

src/superannotate/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import os
12
import logging.config
23

4+
from version import __version__
35
from superannotate.lib.app.analytics.class_analytics import attribute_distribution
46
from superannotate.lib.app.analytics.class_analytics import class_distribution
57
from superannotate.lib.app.annotation_helpers import add_annotation_bbox_to_json
@@ -156,6 +158,7 @@
156158

157159

158160
__all__ = [
161+
"__version__",
159162
# Utils
160163
"AppException",
161164
#
@@ -287,7 +290,7 @@
287290
]
288291

289292
__author__ = "Superannotate"
290-
import os
293+
291294

292295
file_dir = os.path.split(os.path.realpath(__file__))[0]
293296

src/superannotate/lib/core/response.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from typing import Union
22

3-
from lib.core.exceptions import AppException
4-
53

64
class Response:
75
def __init__(self, status: str = None, data: Union[dict, list] = None):
@@ -27,13 +25,7 @@ def status(self, value):
2725

2826
@property
2927
def errors(self):
30-
message = ""
31-
for error in self._errors:
32-
if isinstance(error, AppException):
33-
message += error.message + "\n"
34-
else:
35-
message += str(error)
36-
return message
28+
return "\n".join([str(error) for error in self._errors])
3729

3830
@errors.setter
3931
def errors(self, error: list):

src/superannotate/lib/core/serviceproviders.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from abc import ABC
21
from abc import abstractmethod
32
from typing import Any
43
from typing import Dict
@@ -7,7 +6,16 @@
76
from typing import Tuple
87

98

10-
class SuerannotateServiceProvider(ABC):
9+
class SingleInstanceMetaClass(type):
10+
_instances = {}
11+
12+
def __call__(cls, *args, **kwargs):
13+
if cls not in SingleInstanceMetaClass._instances:
14+
SingleInstanceMetaClass._instances[cls] = super().__call__(*args, **kwargs)
15+
return SingleInstanceMetaClass._instances[cls]
16+
17+
18+
class SuerannotateServiceProvider(metaclass=SingleInstanceMetaClass):
1119
@abstractmethod
1220
def attach_files(
1321
self,

src/superannotate/lib/core/usecases.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,7 @@ def execute(self):
129129
if not projects:
130130
self._response.errors = AppException("Project not found.")
131131
else:
132-
for project in projects:
133-
if project.name == self._name:
134-
self._response.data = project
135-
break
132+
self._response.data = next(project for project in projects if project.name == self._name)
136133
return self._response
137134

138135

@@ -1359,7 +1356,9 @@ def __init__(
13591356

13601357
def validate_image_quality(self):
13611358
for setting in self._to_update:
1362-
if setting["attribute"].lower() == "imagequality" and isinstance(setting["value"], str):
1359+
if setting["attribute"].lower() == "imagequality" and isinstance(
1360+
setting["value"], str
1361+
):
13631362
setting["value"] = constances.ImageQuality.get_value(setting["value"])
13641363
return
13651364

@@ -1593,7 +1592,7 @@ def execute(self):
15931592
self._backend_service.delete_images(
15941593
project_id=self._project.uuid,
15951594
team_id=self._project.team_id,
1596-
image_ids=image_ids[i : i + self.CHUNK_SIZE],
1595+
image_ids=image_ids[i : i + self.CHUNK_SIZE], # noqa: E203
15971596
)
15981597
return self._response
15991598

@@ -1970,6 +1969,8 @@ def execute(self):
19701969

19711970
if self._include_contributors:
19721971
data["contributors"] = project.users
1972+
else:
1973+
project.users = []
19731974

19741975
self._response.data = data
19751976
return self._response
@@ -2678,7 +2679,11 @@ def execute(self):
26782679
for step in [step for step in self._steps if "className" in step]:
26792680
if step.get("id"):
26802681
del step["id"]
2681-
step["class_id"] = annotation_classes_map[step["className"]]
2682+
step["class_id"] = annotation_classes_map.get(step["className"], None)
2683+
if not step["class_id"]:
2684+
raise AppException(
2685+
"Annotation class not found in set_project_workflow."
2686+
)
26822687

26832688
self._service.set_project_workflow_bulk(
26842689
team_id=self._project.team_id,
@@ -2698,6 +2703,17 @@ def execute(self):
26982703
attribute_group_name = attribute["attribute"]["attribute_group"][
26992704
"name"
27002705
]
2706+
if not annotations_classes_attributes_map.get(
2707+
f"{annotation_class_name}__{attribute_group_name}__{attribute_name}",
2708+
None,
2709+
):
2710+
raise AppException(
2711+
"Attribute group name or attribute name not found in set_project_workflow."
2712+
)
2713+
2714+
if not existing_workflows_map.get(step["step"], None):
2715+
raise AppException("Couldn't find step in workflow")
2716+
27012717
req_data.append(
27022718
{
27032719
"workflow_id": existing_workflows_map[step["step"]],

src/superannotate/lib/infrastructure/repositories.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def update(self, entity: ProjectSettingEntity):
190190
res = self._service.set_project_settings(
191191
self._project.uuid, self._project.team_id, [entity.to_dict()]
192192
)
193-
return self.dict2entity(res[0])
193+
return entity
194194

195195
@staticmethod
196196
def dict2entity(data: dict):

src/superannotate/lib/infrastructure/services.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@
88

99
import lib.core as constance
1010
import requests.packages.urllib3
11+
import requests.adapters
1112
from lib.core.exceptions import AppException
1213
from lib.core.serviceproviders import SuerannotateServiceProvider
1314
from requests.exceptions import HTTPError
15+
from lib.infrastructure.helpers import timed_lru_cache
1416

1517
requests.packages.urllib3.disable_warnings()
1618

1719

1820
class BaseBackendService(SuerannotateServiceProvider):
1921
AUTH_TYPE = "sdk"
2022
PAGINATE_BY = 100
23+
MAX_RETRY = 3
2124

2225
"""
2326
Base service class
@@ -29,6 +32,14 @@ def __init__(self, api_url: str, auth_token: str, logger, paginate_by=None):
2932
self.logger = logger
3033
self._paginate_by = paginate_by
3134
self.team_id = auth_token.split("=")[-1]
35+
self._session = None
36+
37+
@timed_lru_cache(seconds=360)
38+
def get_session(self):
39+
if not self._session:
40+
self._session = requests.Session()
41+
self._session.headers.update(self.default_headers)
42+
return self._session
3243

3344
@property
3445
def default_headers(self):
@@ -69,16 +80,12 @@ def _request(
6980
) -> requests.Response:
7081
kwargs = {"json": data} if data else {}
7182
headers_dict = self.default_headers.copy()
72-
headers_dict.update(headers if headers else {})
73-
method = getattr(requests, method)
83+
session = self.get_session()
84+
session.headers.update(headers if headers else {})
85+
method = getattr(session, method)
7486
with self.safe_api():
7587
response = method(
76-
url,
77-
**kwargs,
78-
headers=headers_dict,
79-
params=params,
80-
timeout=60,
81-
verify=False,
88+
url, **kwargs, headers=headers_dict, params=params, timeout=60,
8289
)
8390
if response.status_code == 404 and retried < 3:
8491
return self._request(
@@ -239,7 +246,8 @@ def get_projects(self, query_string: str = None) -> list:
239246
url = urljoin(self.api_url, self.URL_LIST_PROJECTS)
240247
if query_string:
241248
url = f"{url}?{query_string}"
242-
return self._get_all_pages(url)
249+
data = self._get_all_pages(url)
250+
return data
243251

244252
def create_project(self, project_data: dict) -> dict:
245253
create_project_url = urljoin(self.api_url, self.URL_CREATE_PROJECT)

tests/integration/test_folders.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def test_project_folder_image_count(self):
182182
self.PROJECT_NAME, self.folder_path, annotation_status="InProgress"
183183
)
184184
num_images = sa.get_project_image_count(self.PROJECT_NAME)
185-
self.assertEqual(num_images, 0)
185+
self.assertEqual(num_images, 4)
186186

187187
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1)
188188
sa.upload_images_from_folder_to_project(
@@ -191,7 +191,7 @@ def test_project_folder_image_count(self):
191191
annotation_status="InProgress",
192192
)
193193
num_images = sa.get_project_image_count(self.PROJECT_NAME)
194-
self.assertEqual(num_images, 0)
194+
self.assertEqual(num_images, 4)
195195

196196
num_images = sa.get_project_image_count(
197197
self.PROJECT_NAME + f"/{self.TEST_FOLDER_NAME_1}"
@@ -201,7 +201,7 @@ def test_project_folder_image_count(self):
201201
num_images = sa.get_project_image_count(
202202
self.PROJECT_NAME, with_all_subfolders=True
203203
)
204-
self.assertEqual(num_images, 4)
204+
self.assertEqual(num_images, 8)
205205

206206
def test_delete_images(self):
207207
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1)
@@ -252,7 +252,7 @@ def test_copy_images3(self):
252252
)
253253

254254
num_images = sa.get_project_image_count(self.PROJECT_NAME)
255-
assert num_images == 0
255+
assert num_images == 4
256256

257257
def test_copy_images4(self):
258258
sa.upload_images_from_folder_to_project(
@@ -269,7 +269,7 @@ def test_copy_images4(self):
269269
self.assertEqual(num_images, 2)
270270

271271
num_images = sa.get_project_image_count(self.PROJECT_NAME)
272-
self.assertEqual(num_images, 0)
272+
self.assertEqual(num_images, 4)
273273

274274
def test_copy_images(self):
275275
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1)

tests/integration/test_interface.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
from os.path import dirname
33
import src.superannotate as sa
4+
from src.superannotate.lib.app.exceptions import AppException
45
from tests.integration.base import BaseTestCase
56

67

@@ -9,6 +10,10 @@ class TestInterface(BaseTestCase):
910
TEST_FOLDER_PATH = "data_set/sample_project_vector"
1011
PROJECT_DESCRIPTION = "desc"
1112
PROJECT_TYPE = "Vector"
13+
TEST_FOLDER_NAME = "folder"
14+
EXAMPLE_IMAGE_1 = "example_image_1.jpg"
15+
EXAMPLE_IMAGE_2 = "example_image_2.jpg"
16+
1217
@property
1318
def folder_path(self):
1419
return os.path.join(dirname(dirname(__file__)), self.TEST_FOLDER_PATH)
@@ -17,5 +22,33 @@ def test_get_project_default_image_quality_in_editor(self):
1722
sa.invite_contributor_to_team(2, 2)
1823
self.assertIsNotNone(sa.get_project_default_image_quality_in_editor(self.PROJECT_NAME))
1924

25+
def test_delete_images(self):
26+
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME)
27+
28+
sa.upload_images_from_folder_to_project(
29+
f"{self.PROJECT_NAME}/{self.TEST_FOLDER_NAME}",
30+
self.folder_path,
31+
annotation_status="InProgress",
32+
)
33+
num_images = sa.get_project_image_count(
34+
self.PROJECT_NAME, with_all_subfolders=True
35+
)
36+
self.assertEqual(num_images, 4)
37+
sa.delete_images(f"{self.PROJECT_NAME}/{self.TEST_FOLDER_NAME}")
38+
39+
num_images = sa.get_project_image_count(
40+
self.PROJECT_NAME, with_all_subfolders=True
41+
)
42+
self.assertEqual(num_images, 0)
43+
44+
def test_delete_folder(self):
45+
print(sa.search_folders(self.PROJECT_NAME))
46+
with self.assertRaises(AppException):
47+
sa.delete_folders(self.PROJECT_NAME, ["non-existing folder"])
48+
2049
def test_get_project_metadata(self):
21-
pass
50+
metadata = sa.get_project_metadata(self.PROJECT_NAME)
51+
self.assertIsNotNone(metadata["id"])
52+
self.assertListEqual(metadata.get("contributors", []), [])
53+
metadata_with_users = sa.get_project_metadata(self.PROJECT_NAME, include_contributors=True)
54+
self.assertIsNotNone(metadata_with_users.get("contributors"))

tests/profiling/profiling.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import src.superannotate as sa
2+
3+
import time
4+
5+
6+
stat = time.time()
7+
sa.search_annotation_classes("Vector Project")
8+
end = time.time()
9+
print(11, end-stat)

0 commit comments

Comments
 (0)