Skip to content

Commit af9058e

Browse files
authored
Merge pull request #237 from superannotateai/sdk_init_fix
Fixed init flow
2 parents 147ab88 + f1950c6 commit af9058e

File tree

8 files changed

+253
-44
lines changed

8 files changed

+253
-44
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def init():
5555
config = repo.get_one(uuid=constances.TOKEN_UUID)
5656
if config:
5757
if not input(
58-
f"File {config} exists. Do you want to overwrite? [y/n] : "
58+
f"File {repo.config_path} exists. Do you want to overwrite? [y/n] : "
5959
).lower() in ("y", "yes"):
6060
return
6161
token = input(

src/superannotate/lib/core/types.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class ClassesJson(BaseModel):
2929

3030
class Metadata(BaseModel):
3131
name: Optional[NotEmptyStr]
32+
width: Optional[int]
33+
height: Optional[int]
3234

3335

3436
class BaseInstance(BaseModel):
@@ -102,11 +104,6 @@ class Cuboid(BaseInstance):
102104
points: CuboidPoint
103105

104106

105-
class VectorAnnotation(BaseModel):
106-
metadata: Metadata
107-
instances: List[Union[Template, Cuboid, Point, PolyLine, Polygon, Bbox, Ellipse]]
108-
109-
110107
class PixelAnnotationPart(BaseModel):
111108
color: NotEmptyStr
112109

@@ -118,6 +115,11 @@ class PixelAnnotationInstance(BaseModel):
118115
attributes: List[Attribute]
119116

120117

118+
class VectorAnnotation(BaseModel):
119+
metadata: Metadata
120+
instances: List[Union[Template, Cuboid, Point, PolyLine, Polygon, Bbox, Ellipse]]
121+
122+
121123
class PixelAnnotation(BaseModel):
122124
metadata: Metadata
123125
instances: List[PixelAnnotationInstance]

src/superannotate/lib/core/usecases/images.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2018,32 +2018,23 @@ def validate_limitations(self):
20182018
if not response.ok:
20192019
raise AppValidationException(response.error)
20202020

2021-
if self._move:
2022-
if self._from_project.uuid == self._to_project.uuid:
2023-
if self._from_folder.uuid == self._to_folder.uuid:
2024-
raise AppValidationException(
2025-
"Cannot move image if source_project == destination_project."
2026-
)
2027-
elif response.data.folder_limit.remaining_image_count < 1:
2028-
raise AppValidationException(
2029-
constances.MOVE_FOLDER_LIMIT_ERROR_MESSAGE
2030-
)
2031-
elif response.data.project_limit.remaining_image_count < 1:
2032-
raise AppValidationException(
2033-
constances.MOVE_PROJECT_LIMIT_ERROR_MESSAGE
2034-
)
2035-
else:
2036-
if response.data.folder_limit.remaining_image_count < 1:
2037-
raise AppValidationException(constances.COPY_FOLDER_LIMIT_ERROR_MESSAGE)
2038-
if response.data.project_limit.remaining_image_count < 1:
2021+
if self._move and self._from_project.uuid == self._to_project.uuid:
2022+
if self._from_folder.uuid == self._to_folder.uuid:
20392023
raise AppValidationException(
2040-
constances.COPY_PROJECT_LIMIT_ERROR_MESSAGE
2024+
"Cannot move image if source_project == destination_project."
20412025
)
2042-
if (
2043-
response.data.user_limit
2044-
and response.data.user_limit.remaining_image_count < 1
2045-
):
2046-
raise AppValidationException(constances.COPY_SUPER_LIMIT_ERROR_MESSAGE)
2026+
2027+
if response.data.folder_limit.remaining_image_count < 1:
2028+
raise AppValidationException(constances.COPY_FOLDER_LIMIT_ERROR_MESSAGE)
2029+
if response.data.project_limit.remaining_image_count < 1:
2030+
raise AppValidationException(
2031+
constances.COPY_PROJECT_LIMIT_ERROR_MESSAGE
2032+
)
2033+
if (
2034+
response.data.user_limit
2035+
and response.data.user_limit.remaining_image_count < 1
2036+
):
2037+
raise AppValidationException(constances.COPY_SUPER_LIMIT_ERROR_MESSAGE)
20472038

20482039
@property
20492040
def s3_repo(self):
@@ -2102,7 +2093,7 @@ def execute(self) -> Response:
21022093
image_entity = s3_response.data
21032094
del image_bytes
21042095

2105-
AttachFileUrlsUseCase(
2096+
attach_response = AttachFileUrlsUseCase(
21062097
project=self._to_project,
21072098
folder=self._to_folder,
21082099
attachments=[image_entity],
@@ -2112,6 +2103,8 @@ def execute(self) -> Response:
21122103
else None,
21132104
upload_state_code=constances.UploadState.BASIC.value,
21142105
).execute()
2106+
if attach_response.errors:
2107+
raise AppException(attach_response.errors)
21152108
self._response.data = image_entity
21162109
return self._response
21172110

src/superannotate/lib/infrastructure/repositories.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ class ConfigRepository(BaseManageableRepository):
3030
def __init__(self, config_path: str = constance.CONFIG_FILE_LOCATION):
3131
self._config_path = f"{config_path}"
3232

33+
@property
34+
def config_path(self):
35+
return self._config_path
36+
3337
def _create_config(self):
3438
"""
3539
Create a config file

tests/integration/test_image_quality.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,25 @@ def test_big_image(self):
8282
sa.upload_image_to_project(self.PROJECT_NAME, f"{self.folder_path}/{self.BIG_IMAGE}")
8383
except AppException as e:
8484
self.assertEqual(str(e), self.MESSAGE)
85+
86+
87+
88+
class TestImageQualitySetting(BaseTestCase):
89+
PROJECT_NAME = "TestImageQualitySetting Test"
90+
PROJECT_DESCRIPTION = "Desc"
91+
PROJECT_TYPE = "Vector"
92+
TEST_FOLDER_PTH = "data_set"
93+
TEST_FOLDER_PATH = "data_set/sample_project_vector"
94+
EXAMPLE_IMAGE_1 = "example_image_1.jpg"
95+
96+
@property
97+
def folder_path(self):
98+
return os.path.join(dirname(dirname(__file__)), self.TEST_FOLDER_PATH)
99+
100+
def test_image_quality_setting1(self):
101+
uploaded, _, __ = sa.upload_images_from_folder_to_project(
102+
project=self._project["name"], folder_path=self.folder_path
103+
)
104+
uploaded, _, __ = sa.upload_images_from_folder_to_project(
105+
project=self._project["name"], folder_path=os.path.join(dirname(dirname(__file__)), "data_set")
106+
)
Lines changed: 113 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,57 @@
1-
import filecmp
21
import os
3-
import tempfile
2+
from unittest.mock import patch
43
from os.path import dirname
5-
import pytest
64

75
import src.superannotate as sa
8-
from tests.integration.base import BaseTestCase
96
from src.superannotate import AppException
7+
from src.superannotate.lib.core import UPLOAD_FOLDER_LIMIT_ERROR_MESSAGE
8+
from src.superannotate.lib.core import UPLOAD_PROJECT_LIMIT_ERROR_MESSAGE
9+
from src.superannotate.lib.core import UPLOAD_USER_LIMIT_ERROR_MESSAGE
10+
from src.superannotate.lib.core import COPY_FOLDER_LIMIT_ERROR_MESSAGE
11+
from src.superannotate.lib.core import COPY_PROJECT_LIMIT_ERROR_MESSAGE
12+
from src.superannotate.lib.core import COPY_SUPER_LIMIT_ERROR_MESSAGE
13+
from tests.integration.base import BaseTestCase
14+
from tests.moks.limitatoins import folder_limit_response
15+
from tests.moks.limitatoins import project_limit_response
16+
from tests.moks.limitatoins import user_limit_response
17+
18+
19+
class TestLimitsUploadImagesFromFolderToProject(BaseTestCase):
20+
PROJECT_NAME = "TestLimitsUploadImagesFromFolderToProject"
21+
PROJECT_DESCRIPTION = "Desc"
22+
PROJECT_TYPE = "Vector"
23+
TEST_FOLDER_PTH = "data_set"
24+
TEST_FOLDER_PATH = "data_set/sample_project_vector"
25+
EXAMPLE_IMAGE_1 = "example_image_1.jpg"
26+
27+
@property
28+
def folder_path(self):
29+
return os.path.join(dirname(dirname(__file__)), self.TEST_FOLDER_PATH)
30+
31+
@patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations", return_value=folder_limit_response)
32+
def test_folder_limitations(self, *_):
33+
with self.assertRaisesRegexp(AppException, UPLOAD_FOLDER_LIMIT_ERROR_MESSAGE):
34+
_, _, __ = sa.upload_images_from_folder_to_project(
35+
project=self._project["name"], folder_path=self.folder_path
36+
)
37+
38+
@patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations", return_value=project_limit_response)
39+
def test_project_limitations(self, *_):
40+
with self.assertRaisesRegexp(AppException, UPLOAD_PROJECT_LIMIT_ERROR_MESSAGE):
41+
_, _, __ = sa.upload_images_from_folder_to_project(
42+
project=self._project["name"], folder_path=self.folder_path
43+
)
44+
45+
@patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations", return_value=user_limit_response)
46+
def test_user_limitations(self, *_):
47+
with self.assertRaisesRegexp(AppException, UPLOAD_USER_LIMIT_ERROR_MESSAGE):
48+
_, _, __ = sa.upload_images_from_folder_to_project(
49+
project=self._project["name"], folder_path=self.folder_path
50+
)
1051

1152

12-
class TestImageQuality(BaseTestCase):
13-
PROJECT_NAME = "Limitation Test"
53+
class TestLimitsMoveImage(BaseTestCase):
54+
PROJECT_NAME = "TestLimitsUploadImagesFromFolderToProject"
1455
PROJECT_DESCRIPTION = "Desc"
1556
PROJECT_TYPE = "Vector"
1657
TEST_FOLDER_PTH = "data_set"
@@ -21,10 +62,69 @@ class TestImageQuality(BaseTestCase):
2162
def folder_path(self):
2263
return os.path.join(dirname(dirname(__file__)), self.TEST_FOLDER_PATH)
2364

24-
def test_image_quality_setting1(self):
25-
uploaded, _, __ = sa.upload_images_from_folder_to_project(
26-
project=self._project["name"], folder_path=self.folder_path
27-
)
28-
uploaded, _, __ = sa.upload_images_from_folder_to_project(
29-
project=self._project["name"], folder_path=os.path.join(dirname(dirname(__file__)), "data_set")
30-
)
65+
def test_folder_limitations(self):
66+
sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1))
67+
sa.create_folder(self._project["name"], self._project["name"])
68+
with patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations") as limit_response:
69+
limit_response.return_value = folder_limit_response
70+
with self.assertRaisesRegexp(AppException, COPY_FOLDER_LIMIT_ERROR_MESSAGE):
71+
_, _, __ = sa.move_image(
72+
self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}")
73+
74+
def test_project_limitations(self, ):
75+
sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1))
76+
sa.create_folder(self._project["name"], self._project["name"])
77+
with patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations") as limit_response:
78+
limit_response.return_value = project_limit_response
79+
with self.assertRaisesRegexp(AppException, COPY_PROJECT_LIMIT_ERROR_MESSAGE):
80+
_, _, __ = sa.move_image(
81+
self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}")
82+
83+
def test_user_limitations(self, ):
84+
sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1))
85+
sa.create_folder(self._project["name"], self._project["name"])
86+
with patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations") as limit_response:
87+
limit_response.return_value = user_limit_response
88+
with self.assertRaisesRegexp(AppException, COPY_SUPER_LIMIT_ERROR_MESSAGE):
89+
_, _, __ = sa.move_image(
90+
self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}")
91+
92+
93+
class TestLimitsCopyImage(BaseTestCase):
94+
PROJECT_NAME = "TestLimitsUploadImagesFromFolderToProject"
95+
PROJECT_DESCRIPTION = "Desc"
96+
PROJECT_TYPE = "Vector"
97+
TEST_FOLDER_PTH = "data_set"
98+
TEST_FOLDER_PATH = "data_set/sample_project_vector"
99+
EXAMPLE_IMAGE_1 = "example_image_1.jpg"
100+
101+
@property
102+
def folder_path(self):
103+
return os.path.join(dirname(dirname(__file__)), self.TEST_FOLDER_PATH)
104+
105+
def test_folder_limitations(self):
106+
sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1))
107+
sa.create_folder(self._project["name"], self._project["name"])
108+
with patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations") as limit_response:
109+
limit_response.return_value = folder_limit_response
110+
with self.assertRaisesRegexp(AppException, COPY_FOLDER_LIMIT_ERROR_MESSAGE):
111+
_, _, __ = sa.copy_image(
112+
self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}")
113+
114+
def test_project_limitations(self, ):
115+
sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1))
116+
sa.create_folder(self._project["name"], self._project["name"])
117+
with patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations") as limit_response:
118+
limit_response.return_value = project_limit_response
119+
with self.assertRaisesRegexp(AppException, COPY_PROJECT_LIMIT_ERROR_MESSAGE):
120+
_, _, __ = sa.copy_image(
121+
self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}")
122+
123+
def test_user_limitations(self, ):
124+
sa.upload_image_to_project(self._project["name"], os.path.join(self.folder_path, self.EXAMPLE_IMAGE_1))
125+
sa.create_folder(self._project["name"], self._project["name"])
126+
with patch("lib.infrastructure.services.SuperannotateBackendService.get_limitations") as limit_response:
127+
limit_response.return_value = user_limit_response
128+
with self.assertRaisesRegexp(AppException, COPY_SUPER_LIMIT_ERROR_MESSAGE):
129+
_, _, __ = sa.copy_image(
130+
self._project["name"], self.folder_path, f"{self.PROJECT_NAME}/{self.PROJECT_NAME}")

tests/moks/limitatoins.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from unittest import mock
2+
3+
folder_limit_response = mock.MagicMock()
4+
folder_limit_response.ok = True
5+
folder_limit_response.data.user_limit.remaining_image_count = 0
6+
folder_limit_response.data.project_limit.remaining_image_count = 0
7+
folder_limit_response.data.folder_limit.remaining_image_count = 0
8+
9+
10+
project_limit_response = mock.MagicMock()
11+
project_limit_response.ok = True
12+
project_limit_response.data.user_limit.remaining_image_count = 0
13+
project_limit_response.data.project_limit.remaining_image_count = 0
14+
project_limit_response.data.folder_limit.remaining_image_count = 50000
15+
16+
17+
user_limit_response = mock.MagicMock()
18+
user_limit_response.ok = True
19+
user_limit_response.data.user_limit.remaining_image_count = 0
20+
user_limit_response.data.project_limit.remaining_image_count = 500000
21+
user_limit_response.data.folder_limit.remaining_image_count = 50000

tests/unit/test_controller_init.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import sys
2+
from io import StringIO
3+
import json
4+
from contextlib import contextmanager
5+
import pkg_resources
6+
from unittest import TestCase
7+
from unittest.mock import mock_open
8+
from unittest.mock import patch
9+
10+
from src.superannotate.lib.app.interface.cli_interface import CLIFacade
11+
from src.superannotate.lib.core import CONFIG_FILE_LOCATION
12+
13+
14+
try:
15+
CLI_VERSION = pkg_resources.get_distribution("superannotate").version
16+
except Exception:
17+
CLI_VERSION = None
18+
19+
20+
@contextmanager
21+
def catch_prints():
22+
out = StringIO()
23+
sys.stdout = out
24+
yield out
25+
26+
27+
class CLITest(TestCase):
28+
29+
@patch('builtins.input')
30+
def test_init_update(self, input_mock):
31+
input_mock.side_effect = ["y", "token"]
32+
with open(CONFIG_FILE_LOCATION) as f:
33+
config_data = f.read()
34+
with patch('builtins.open', mock_open(read_data=config_data)) as config_file:
35+
try:
36+
with catch_prints() as out:
37+
cli = CLIFacade()
38+
cli.init()
39+
except SystemExit:
40+
input_args = [i[0][0] for i in input_mock.call_args_list]
41+
self.assertIn(f"File {CONFIG_FILE_LOCATION} exists. Do you want to overwrite? [y/n] : ", input_args)
42+
input_mock.assert_called_with("Input the team SDK token from https://app.superannotate.com/team : ")
43+
config_file().write.assert_called_once_with(
44+
json.dumps(
45+
{"main_endpoint": "https://api.devsuperannotate.com", "ssl_verify": False, "token": "token"},
46+
indent=4
47+
)
48+
)
49+
self.assertEqual(out.getvalue().strip(), "Configuration file successfully updated.")
50+
51+
@patch('builtins.input')
52+
def test_init_create(self, input_mock):
53+
input_mock.side_effect = ["token"]
54+
with patch('builtins.open', mock_open(read_data="{}")) as config_file:
55+
try:
56+
with catch_prints() as out:
57+
cli = CLIFacade()
58+
cli.init()
59+
except SystemExit:
60+
input_mock.assert_called_with("Input the team SDK token from https://app.superannotate.com/team : ")
61+
config_file().write.assert_called_once_with(
62+
json.dumps(
63+
{"token": "token"},
64+
indent=4
65+
)
66+
)
67+
self.assertEqual(out.getvalue().strip(), "Configuration file successfully created.")

0 commit comments

Comments
 (0)