Skip to content

Commit 01a3bac

Browse files
authored
Merge pull request #582 from superannotateai/1789_implementation
added set folder/project status functions
2 parents c5bc910 + 8217015 commit 01a3bac

File tree

7 files changed

+212
-10
lines changed

7 files changed

+212
-10
lines changed

docs/source/api_reference/api_project.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ Projects
1111
.. automethod:: superannotate.SAClient.rename_project
1212
.. _ref_get_project_metadata:
1313
.. automethod:: superannotate.SAClient.get_project_by_id
14+
.. automethod:: superannotate.SAClient.set_project_status
1415
.. automethod:: superannotate.SAClient.get_project_metadata
1516
.. automethod:: superannotate.SAClient.get_project_image_count
1617
.. automethod:: superannotate.SAClient.search_folders
1718
.. automethod:: superannotate.SAClient.assign_folder
1819
.. automethod:: superannotate.SAClient.unassign_folder
1920
.. automethod:: superannotate.SAClient.get_folder_by_id
21+
.. automethod:: superannotate.SAClient.set_folder_status
2022
.. automethod:: superannotate.SAClient.get_folder_metadata
2123
.. automethod:: superannotate.SAClient.create_folder
2224
.. automethod:: superannotate.SAClient.delete_folders

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

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
NotEmptyStr = TypeVar("NotEmptyStr", bound=constr(strict=True, min_length=1))
7272

7373

74-
PROJECT_STATUS = Literal["Undefined", "NotStarted", "InProgress", "Completed", "OnHold"]
74+
PROJECT_STATUS = Literal["NotStarted", "InProgress", "Completed", "OnHold"]
7575

7676
PROJECT_TYPE = Literal[
7777
"Vector", "Pixel", "Video", "Document", "Tiled", "Other", "PointCloud"
@@ -91,13 +91,7 @@
9191

9292
ANNOTATOR_ROLE = Literal["Admin", "Annotator", "QA"]
9393

94-
FOLDER_STATUS = Literal[
95-
"Undefined",
96-
"NotStarted",
97-
"InProgress",
98-
"Completed",
99-
"OnHold",
100-
]
94+
FOLDER_STATUS = Literal["NotStarted", "InProgress", "Completed", "OnHold"]
10195

10296

10397
class Setting(TypedDict):
@@ -782,6 +776,52 @@ def search_annotation_classes(
782776
for i in response.data
783777
]
784778

779+
def set_project_status(self, project: NotEmptyStr, status: PROJECT_STATUS):
780+
"""Set project status
781+
782+
:param project: project name
783+
:type project: str
784+
:param status: status to set, should be one of. \n
785+
♦ “NotStarted” \n
786+
♦ “InProgress” \n
787+
♦ “Completed” \n
788+
♦ “OnHold” \n
789+
:type status: str
790+
"""
791+
project = self.controller.get_project(name=project)
792+
project.status = constants.ProjectStatus.get_value(status)
793+
response = self.controller.projects.update(project)
794+
if response.errors:
795+
raise AppException(f"Failed to change {project.name} status.")
796+
logger.info(f"Successfully updated {project.name} status to {status}")
797+
798+
def set_folder_status(
799+
self, project: NotEmptyStr, folder: NotEmptyStr, status: FOLDER_STATUS
800+
):
801+
"""Set folder status
802+
803+
:param project: project name
804+
:type project: str
805+
:param folder: folder name
806+
:type folder: str
807+
:param status: status to set, should be one of. \n
808+
♦ “NotStarted” \n
809+
♦ “InProgress” \n
810+
♦ “Completed” \n
811+
♦ “OnHold” \n
812+
:type status: str
813+
"""
814+
project, folder = self.controller.get_project_folder(
815+
project_name=project, folder_name=folder
816+
)
817+
folder.status = constants.FolderStatus.get_value(status)
818+
response = self.controller.update(project, folder)
819+
if response.errors:
820+
raise AppException(f"Failed to change {project.name}/{folder.name} status.")
821+
logger.info(
822+
f"Successfully updated {project.name}/{folder.name} status to {status}"
823+
)
824+
785825
def set_project_default_image_quality_in_editor(
786826
self,
787827
project: Union[NotEmptyStr, dict],

src/superannotate/lib/core/entities/project.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ def __copy__(self):
137137
upload_state=self.upload_state,
138138
)
139139

140+
def __eq__(self, other):
141+
return self.id == other.id
142+
140143

141144
class MLModelEntity(TimedBaseModel):
142145
id: Optional[int]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ def execute(self):
197197
self._project, self._folder
198198
)
199199
if not response.ok:
200-
self._response.errors = AppException("Couldn't rename folder.")
200+
self._response.errors = AppException(response.error)
201201
self._response.data = response.data
202202
return self._response
203203

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ def validate_project_name(self):
359359
response = self._service_provider.projects.list(condition)
360360
if response.ok:
361361
for project in response.data:
362-
if project.name == self._project.name:
362+
if project.name == self._project.name and project != self._project:
363363
logger.error("There are duplicated names.")
364364
raise AppValidationException(
365365
f"Project name {self._project.name} is not unique. "
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
from unittest import TestCase
2+
from unittest.mock import patch
3+
4+
from src.superannotate import AppException
5+
from src.superannotate.lib.core.service_types import ServiceResponse
6+
from superannotate import SAClient
7+
8+
9+
sa = SAClient()
10+
11+
12+
class TestSetFolderStatus(TestCase):
13+
PROJECT_NAME = "test_set_folder_status"
14+
FOLDER_NAME = "test_folder"
15+
PROJECT_DESCRIPTION = "desc"
16+
PROJECT_TYPE = "Vector"
17+
FOLDER_STATUSES = ["NotStarted", "InProgress", "Completed", "OnHold"]
18+
19+
@classmethod
20+
def setUpClass(cls, *args, **kwargs):
21+
cls.tearDownClass()
22+
cls._project = sa.create_project(
23+
cls.PROJECT_NAME, cls.PROJECT_DESCRIPTION, cls.PROJECT_TYPE
24+
)
25+
sa.create_folder(cls.PROJECT_NAME, cls.FOLDER_NAME)
26+
folder = sa.get_folder_metadata(
27+
project=cls.PROJECT_NAME, folder_name=cls.FOLDER_NAME
28+
)
29+
assert folder["status"] == "NotStarted"
30+
31+
@classmethod
32+
def tearDownClass(cls) -> None:
33+
sa.delete_project(cls.PROJECT_NAME)
34+
35+
def test_set_folder_status(self):
36+
with self.assertLogs("sa", level="INFO") as cm:
37+
for index, status in enumerate(
38+
self.FOLDER_STATUSES
39+
):
40+
sa.set_folder_status(
41+
project=self.PROJECT_NAME, folder=self.FOLDER_NAME, status=status
42+
)
43+
folder = sa.get_folder_metadata(
44+
project=self.PROJECT_NAME, folder_name=self.FOLDER_NAME
45+
)
46+
assert (
47+
f"INFO:sa:Successfully updated {self.PROJECT_NAME}/{self.FOLDER_NAME} status to {status}"
48+
== cm.output[index]
49+
)
50+
self.assertEqual(status, folder["status"])
51+
self.assertEqual(len(cm.output), len(self.FOLDER_STATUSES))
52+
53+
@patch("lib.infrastructure.services.folder.FolderService.update")
54+
def test_set_folder_status_fail(self, update_function):
55+
update_function.return_value = ServiceResponse(_error="ERROR")
56+
with self.assertRaisesRegexp(
57+
AppException,
58+
f"Failed to change {self.PROJECT_NAME}/{self.FOLDER_NAME} status.",
59+
):
60+
sa.set_folder_status(
61+
project=self.PROJECT_NAME, folder=self.FOLDER_NAME, status="Completed"
62+
)
63+
64+
def test_set_folder_status_via_invalid_status(self):
65+
with self.assertRaisesRegexp(
66+
AppException,
67+
"Available values are 'NotStarted', 'InProgress', 'Completed', 'OnHold'.",
68+
):
69+
sa.set_folder_status(
70+
project=self.PROJECT_NAME,
71+
folder=self.FOLDER_NAME,
72+
status="InvalidStatus",
73+
)
74+
75+
def test_set_folder_status_via_invalid_project(self):
76+
with self.assertRaisesRegexp(
77+
AppException,
78+
"Project not found.",
79+
):
80+
sa.set_folder_status(
81+
project="Invalid Name", folder=self.FOLDER_NAME, status="Completed"
82+
)
83+
84+
def test_set_folder_status_via_invalid_folder(self):
85+
with self.assertRaisesRegexp(
86+
AppException,
87+
"Folder not found.",
88+
):
89+
sa.set_folder_status(
90+
project=self.PROJECT_NAME, folder="Invalid Name", status="Completed"
91+
)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from unittest import TestCase
2+
from unittest.mock import patch
3+
4+
from src.superannotate import AppException
5+
from src.superannotate.lib.core.service_types import ServiceResponse
6+
from superannotate import SAClient
7+
8+
9+
sa = SAClient()
10+
11+
12+
class TestSetProjectStatus(TestCase):
13+
PROJECT_NAME = "test_set_project_status"
14+
PROJECT_DESCRIPTION = "desc"
15+
PROJECT_TYPE = "Vector"
16+
PROJECT_STATUSES = ["NotStarted", "InProgress", "Completed", "OnHold"]
17+
18+
@classmethod
19+
def setUpClass(cls, *args, **kwargs):
20+
cls.tearDownClass()
21+
cls._project = sa.create_project(
22+
cls.PROJECT_NAME, cls.PROJECT_DESCRIPTION, cls.PROJECT_TYPE
23+
)
24+
project = sa.get_project_metadata(cls.PROJECT_NAME)
25+
assert project["status"] == "NotStarted"
26+
27+
@classmethod
28+
def tearDownClass(cls) -> None:
29+
sa.delete_project(cls.PROJECT_NAME)
30+
31+
def test_set_project_status(self):
32+
with self.assertLogs("sa", level="INFO") as cm:
33+
for index, status in enumerate(
34+
self.PROJECT_STATUSES
35+
):
36+
sa.set_project_status(project=self.PROJECT_NAME, status=status)
37+
project = sa.get_project_metadata(self.PROJECT_NAME)
38+
assert (
39+
f"INFO:sa:Successfully updated {self.PROJECT_NAME} status to {status}"
40+
== cm.output[index]
41+
)
42+
self.assertEqual(status, project["status"])
43+
self.assertEqual(len(cm.output), len(self.PROJECT_STATUSES))
44+
45+
@patch("lib.infrastructure.services.project.ProjectService.update")
46+
def test_set_project_status_fail(self, update_function):
47+
update_function.return_value = ServiceResponse(_error="ERROR")
48+
with self.assertRaisesRegexp(
49+
AppException,
50+
f"Failed to change {self.PROJECT_NAME} status.",
51+
):
52+
sa.set_project_status(project=self.PROJECT_NAME, status="Completed")
53+
54+
def test_set_project_status_via_invalid_status(self):
55+
with self.assertRaisesRegexp(
56+
AppException,
57+
"Available values are 'NotStarted', 'InProgress', 'Completed', 'OnHold'.",
58+
):
59+
sa.set_project_status(project=self.PROJECT_NAME, status="InvalidStatus")
60+
61+
def test_set_project_status_via_invalid_project(self):
62+
with self.assertRaisesRegexp(
63+
AppException,
64+
"Project not found.",
65+
):
66+
sa.set_project_status(project="Invalid name", status="Completed")

0 commit comments

Comments
 (0)