Skip to content

Commit cccef36

Browse files
committed
Folder tests update
1 parent 7c520ba commit cccef36

File tree

11 files changed

+181
-314
lines changed

11 files changed

+181
-314
lines changed

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

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from typing import TypedDict, NotRequired, Required # noqa
2525

2626
import boto3
27-
from pydantic import StrictBool
2827
from pydantic import conlist
2928
from pydantic import constr
3029
from pydantic import parse_obj_as
@@ -388,10 +387,10 @@ def clone_project(
388387
project_name: Union[NotEmptyStr, dict],
389388
from_project: Union[NotEmptyStr, dict],
390389
project_description: Optional[NotEmptyStr] = None,
391-
copy_annotation_classes: Optional[StrictBool] = True,
392-
copy_settings: Optional[StrictBool] = True,
393-
copy_workflow: Optional[StrictBool] = False,
394-
copy_contributors: Optional[StrictBool] = False,
390+
copy_annotation_classes: Optional[bool] = True,
391+
copy_settings: Optional[bool] = True,
392+
copy_workflow: Optional[bool] = False,
393+
copy_contributors: Optional[bool] = False,
395394
):
396395
"""Create a new project in the team using annotation classes and settings from from_project.
397396
@@ -578,7 +577,7 @@ def search_folders(
578577
project: NotEmptyStr,
579578
folder_name: Optional[NotEmptyStr] = None,
580579
status: Optional[Union[FOLDER_STATUS, List[FOLDER_STATUS]]] = None,
581-
return_metadata: Optional[StrictBool] = False,
580+
return_metadata: Optional[bool] = False,
582581
):
583582
"""Folder name based case-insensitive search for folders in project.
584583
@@ -625,11 +624,11 @@ def search_folders(
625624
def get_project_metadata(
626625
self,
627626
project: Union[NotEmptyStr, dict],
628-
include_annotation_classes: Optional[StrictBool] = False,
629-
include_settings: Optional[StrictBool] = False,
630-
include_workflow: Optional[StrictBool] = False,
631-
include_contributors: Optional[StrictBool] = False,
632-
include_complete_item_count: Optional[StrictBool] = False,
627+
include_annotation_classes: Optional[bool] = False,
628+
include_settings: Optional[bool] = False,
629+
include_workflow: Optional[bool] = False,
630+
include_contributors: Optional[bool] = False,
631+
include_complete_item_count: Optional[bool] = False,
633632
):
634633
"""Returns project metadata
635634
@@ -809,7 +808,7 @@ def pin_image(
809808
self,
810809
project: Union[NotEmptyStr, dict],
811810
image_name: str,
812-
pin: Optional[StrictBool] = True,
811+
pin: Optional[bool] = True,
813812
):
814813
"""Pins (or unpins) image
815814
@@ -966,7 +965,7 @@ def upload_images_from_folder_to_project(
966965
exclude_file_patterns: Optional[
967966
Iterable[NotEmptyStr]
968967
] = constants.DEFAULT_FILE_EXCLUDE_PATTERNS,
969-
recursive_subfolders: Optional[StrictBool] = False,
968+
recursive_subfolders: Optional[bool] = False,
970969
image_quality_in_editor: Optional[str] = None,
971970
):
972971
"""Uploads all images with given extensions from folder_path to the project.
@@ -1103,7 +1102,7 @@ def download_image_annotations(
11031102
return res.data
11041103

11051104
def get_exports(
1106-
self, project: NotEmptyStr, return_metadata: Optional[StrictBool] = False
1105+
self, project: NotEmptyStr, return_metadata: Optional[bool] = False
11071106
):
11081107
"""Get all prepared exports of the project.
11091108
@@ -1125,7 +1124,7 @@ def prepare_export(
11251124
project: Union[NotEmptyStr, dict],
11261125
folder_names: Optional[List[NotEmptyStr]] = None,
11271126
annotation_statuses: Optional[List[ANNOTATION_STATUS]] = None,
1128-
include_fuse: Optional[StrictBool] = False,
1127+
include_fuse: Optional[bool] = False,
11291128
only_pinned=False,
11301129
):
11311130
"""Prepare annotations and classes.json for export. Original and fused images for images with
@@ -1180,7 +1179,7 @@ def upload_videos_from_folder_to_project(
11801179
Union[Tuple[NotEmptyStr], List[NotEmptyStr]]
11811180
] = constants.DEFAULT_VIDEO_EXTENSIONS,
11821181
exclude_file_patterns: Optional[List[NotEmptyStr]] = (),
1183-
recursive_subfolders: Optional[StrictBool] = False,
1182+
recursive_subfolders: Optional[bool] = False,
11841183
target_fps: Optional[int] = None,
11851184
start_time: Optional[float] = 0.0,
11861185
end_time: Optional[float] = None,
@@ -1513,7 +1512,7 @@ def download_export(
15131512
project: Union[NotEmptyStr, dict],
15141513
export: Union[NotEmptyStr, dict],
15151514
folder_path: Union[str, Path],
1516-
extract_zip_contents: Optional[StrictBool] = True,
1515+
extract_zip_contents: Optional[bool] = True,
15171516
to_s3_bucket=None,
15181517
):
15191518
"""Download prepared export.
@@ -1572,9 +1571,9 @@ def download_image(
15721571
project: Union[NotEmptyStr, dict],
15731572
image_name: NotEmptyStr,
15741573
local_dir_path: Optional[Union[str, Path]] = "./",
1575-
include_annotations: Optional[StrictBool] = False,
1576-
include_fuse: Optional[StrictBool] = False,
1577-
include_overlay: Optional[StrictBool] = False,
1574+
include_annotations: Optional[bool] = False,
1575+
include_fuse: Optional[bool] = False,
1576+
include_overlay: Optional[bool] = False,
15781577
variant: Optional[str] = "original",
15791578
):
15801579
"""Downloads the image (and annotation if not None) to local_dir_path
@@ -1659,7 +1658,7 @@ def upload_annotations_from_folder_to_project(
16591658
project: Union[NotEmptyStr, dict],
16601659
folder_path: Union[str, Path],
16611660
from_s3_bucket=None,
1662-
recursive_subfolders: Optional[StrictBool] = False,
1661+
recursive_subfolders: Optional[bool] = False,
16631662
keep_status=False,
16641663
):
16651664
"""Finds and uploads all JSON files in the folder_path as annotations to the project.
@@ -1733,7 +1732,7 @@ def upload_image_annotations(
17331732
image_name: str,
17341733
annotation_json: Union[str, Path, dict],
17351734
mask: Optional[Union[str, Path, bytes]] = None,
1736-
verbose: Optional[StrictBool] = True,
1735+
verbose: Optional[bool] = True,
17371736
keep_status: bool = False,
17381737
):
17391738
"""Upload annotations from JSON (also mask for pixel annotations)
@@ -1948,7 +1947,7 @@ def search_models(
19481947
type_: Optional[NotEmptyStr] = None, # noqa
19491948
project_id: Optional[int] = None,
19501949
task: Optional[NotEmptyStr] = None,
1951-
include_global: Optional[StrictBool] = True,
1950+
include_global: Optional[bool] = True,
19521951
):
19531952
r"""Search for ML models.
19541953
@@ -2164,7 +2163,7 @@ def add_contributors_to_project(
21642163
return response.data
21652164

21662165
def invite_contributors_to_team(
2167-
self, emails: conlist(EmailStr, min_items=1), admin: StrictBool = False
2166+
self, emails: conlist(EmailStr, min_items=1), admin: bool = False
21682167
) -> Tuple[List[str], List[str]]:
21692168
"""Invites contributors to the team.
21702169
@@ -2575,7 +2574,7 @@ def copy_items(
25752574
source: Union[NotEmptyStr, dict],
25762575
destination: Union[NotEmptyStr, dict],
25772576
items: Optional[List[NotEmptyStr]] = None,
2578-
include_annotations: Optional[StrictBool] = True,
2577+
include_annotations: Optional[bool] = True,
25792578
):
25802579
"""Copy images in bulk between folders in a project
25812580

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ class FolderEntity(TimedBaseModel):
1111
name: Optional[str]
1212
status: Optional[FolderStatus]
1313
project_id: Optional[int]
14-
parent_id: Optional[int]
1514
team_id: Optional[int]
1615
is_root: Optional[bool] = (False,)
1716
folder_users: Optional[List[dict]]
1817
completedCount: Optional[int]
1918

2019
class Config:
21-
extra = Extra.allow
20+
extra = Extra.ignore

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,6 @@ class TeamEntity(BaseModel):
185185
users: Optional[List[UserEntity]]
186186
pending_invitations: Optional[List[Any]]
187187
creator_id: Optional[str]
188+
189+
class Config:
190+
extra = Extra.ignore
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FOLDER_KEYS = [
2+
"createdAt",
3+
"updatedAt",
4+
"id",
5+
"name",
6+
"status",
7+
"project_id",
8+
"team_id",
9+
"folder_users",
10+
]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from src.superannotate import AppException
2+
from src.superannotate import SAClient
3+
from tests.integration.base import BaseTestCase
4+
5+
sa = SAClient()
6+
7+
8+
class TestCreateFolder(BaseTestCase):
9+
PROJECT_NAME = "test TestCreateFolder"
10+
PROJECT_DESCRIPTION = "desc"
11+
PROJECT_TYPE = "Vector"
12+
SPECIAL_CHARS = r"/\:*?“<>|"
13+
TEST_FOLDER_NAME = "folder_"
14+
15+
def test_create_long_name(self):
16+
err_msg = "The folder name is too long. The maximum length for this field is 80 characters."
17+
with self.assertRaisesRegexp(AppException, err_msg):
18+
sa.create_folder(
19+
self.PROJECT_NAME,
20+
"A while back I needed to count the amount of letters that "
21+
"a piece of text in an email template had (to avoid passing any)",
22+
)
23+
24+
def test_create_folder_with_special_chars(self):
25+
sa.create_folder(self.PROJECT_NAME, self.SPECIAL_CHARS)
26+
folder = sa.get_folder_metadata(
27+
self.PROJECT_NAME, "_" * len(self.SPECIAL_CHARS)
28+
)
29+
self.assertIsNotNone(folder)
30+
assert "completedCount" not in folder.keys()
31+
assert "is_root" not in folder.keys()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from src.superannotate import AppException
2+
from src.superannotate import SAClient
3+
from tests.integration.base import BaseTestCase
4+
5+
6+
sa = SAClient()
7+
8+
9+
class TestDeleteFolders(BaseTestCase):
10+
PROJECT_NAME = "test TestDeleteFolders"
11+
PROJECT_DESCRIPTION = "desc"
12+
PROJECT_TYPE = "Vector"
13+
SPECIAL_CHARS = r"/\:*?“<>|"
14+
TEST_FOLDER_NAME_1 = "folder_1"
15+
TEST_FOLDER_NAME_2 = "folder_2"
16+
17+
def test_search_folders(self):
18+
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_1)
19+
sa.create_folder(self.PROJECT_NAME, self.TEST_FOLDER_NAME_2)
20+
sa.delete_folders(self.PROJECT_NAME, folder_names=[self.TEST_FOLDER_NAME_1])
21+
folders = sa.search_folders(self.PROJECT_NAME)
22+
assert len(folders) == 1
23+
24+
sa.delete_folders(self.PROJECT_NAME, folder_names=[self.TEST_FOLDER_NAME_2])
25+
folders = sa.search_folders(self.PROJECT_NAME)
26+
assert len(folders) == 0
27+
28+
# test delete multiple
29+
folder_names = [f"folder_{i}" for i in range(5)]
30+
[
31+
sa.create_folder(self.PROJECT_NAME, folder_name)
32+
for folder_name in folder_names
33+
]
34+
35+
with self.assertRaisesRegexp(AppException, "There is no folder to delete."):
36+
sa.delete_folders(self.PROJECT_NAME, [])
37+
pattern = r"(\s+)folder_names(\s+)none is not an allowed value"
38+
39+
with self.assertRaisesRegexp(AppException, pattern):
40+
sa.delete_folders(self.PROJECT_NAME, None) # noqa
41+
42+
sa.delete_folders(self.PROJECT_NAME, folder_names)
43+
folders = sa.search_folders(self.PROJECT_NAME)
44+
assert len(folders) == 0

0 commit comments

Comments
 (0)