Skip to content

Commit 31d6c2f

Browse files
authored
Merge pull request #727 from superannotateai/develop
Develop
2 parents dc97f65 + 567aca3 commit 31d6c2f

File tree

12 files changed

+228
-135
lines changed

12 files changed

+228
-135
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@ History
66

77
All release highlights of this project will be documented in this file.
88

9+
4.4.27 - Nov 14, 2024
10+
________________________
11+
**Fixed**
12+
13+
- ``SAClient.attach_items`` fixed chunks handling.
14+
15+
916
4.4.26 - Oct 29, 2024
1017
________________________
1118

1219
**Added**
1320

1421
- ``SAClient.copy_items/move_items`` method, added the ability to copy/move categories and duplicate strategies ("skip", "replace", "replace_annotations_only").
22+
1523
**Updated**
1624

1725
- Fixed `SAClient.get_annotations() To handle annotations that contain all UTF-8 characters.`

pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ minversion = 3.7
33
log_cli=true
44
python_files = test_*.py
55
;pytest_plugins = ['pytest_profiling']
6-
addopts = -n 2 --dist loadscope
6+
addopts = -n 4 --dist loadscope

src/superannotate/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55

6-
__version__ = "4.4.26"
6+
__version__ = "4.4.27"
77

88
os.environ.update({"sa_version": __version__})
99
sys.path.append(os.path.split(os.path.realpath(__file__))[0])
@@ -15,9 +15,9 @@
1515
from lib.core import PACKAGE_VERSION_INFO_MESSAGE
1616
from lib.core import PACKAGE_VERSION_MAJOR_UPGRADE
1717
from lib.core.exceptions import AppException
18-
from lib.app.input_converters import convert_project_type
19-
from lib.app.input_converters import export_annotation
20-
from lib.app.input_converters import import_annotation
18+
from superannotate.lib.app.input_converters import convert_project_type
19+
from superannotate.lib.app.input_converters import export_annotation
20+
from superannotate.lib.app.input_converters import import_annotation
2121
from superannotate.lib.app.interface.sdk_interface import SAClient
2222

2323

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

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2556,19 +2556,13 @@ def search_items(
25562556
If recursive=False=True, then only the project name is required.
25572557
:type project: str
25582558
2559-
:param name_contains: Returns those items, where the given string is found anywhere within an item’s name.
2559+
:param name_contains: returns those items, where the given string is found anywhere within an item’s name.
25602560
If None, all items returned, in accordance with the recursive=False parameter.
25612561
:type name_contains: str
25622562
2563-
:param annotation_status: if not None, filters items by annotation status. \n
2564-
Available statuses are::
2563+
:param annotation_status: returns items with the specified annotation status, which must match a predefined
2564+
status in the project workflow. If None, all items are returned.
25652565
2566-
* NotStarted
2567-
* InProgress
2568-
* QualityCheck
2569-
* Returned
2570-
* Completed
2571-
* Skipped
25722566
:type annotation_status: str
25732567
25742568
:param annotator_email: returns those items’ names that are assigned to the specified annotator.
@@ -3049,20 +3043,13 @@ def set_annotation_statuses(
30493043
annotation_status: NotEmptyStr,
30503044
items: Optional[List[NotEmptyStr]] = None,
30513045
):
3052-
"""Sets annotation statuses of items
3046+
"""Sets annotation statuses of items.
30533047
30543048
:param project: project name or folder path (e.g., “project1/folder1”).
30553049
:type project: str
30563050
3057-
:param annotation_status: annotation status to set. \n
3058-
Available statuses are::
3059-
3060-
* NotStarted
3061-
* InProgress
3062-
* QualityCheck
3063-
* Returned
3064-
* Completed
3065-
* Skipped
3051+
:param annotation_status: The desired status to set for the annotation.
3052+
This status should match one of the predefined statuses available in the project workflow.
30663053
:type annotation_status: str
30673054
30683055
:param items: item names. If None, all the items in the specified directory will be used.

src/superannotate/lib/core/jsx_conditions.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ def __init__(self, key: str, value: Any, operator: OperatorEnum):
5454
self._operator = operator
5555
self.condition_set = [self]
5656

57+
@property
58+
def key(self) -> str:
59+
return self._key
60+
61+
@property
62+
def operator(self) -> OperatorEnum:
63+
return self._operator
64+
65+
@property
66+
def value(self) -> Any:
67+
return self._value
68+
5769
def _build(self):
5870
if isinstance(self._value, (list, set, tuple)):
5971
return f"{self._key}||{self._operator.value}||{','.join(map(urllib.parse.quote, map(str, self._value)))}"

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

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -334,14 +334,12 @@ def list_existing_items(self, item_names: List[str]) -> List[BaseItemEntity]:
334334
existing_items = []
335335
for i in range(0, len(item_names), self.CHUNK_SIZE):
336336
items_to_check = item_names[i : i + self.CHUNK_SIZE] # noqa: E203
337-
res = self._service_provider.item_service.list(
337+
data = self._service_provider.item_service.list(
338338
self._project.id,
339339
self._folder.id,
340340
Filter("name", items_to_check, OperatorEnum.IN),
341341
)
342-
if not res.ok:
343-
raise AppException(res.error)
344-
existing_items.extend(res.data)
342+
existing_items.extend(data)
345343
return existing_items
346344

347345
async def distribute_queues(self, items_to_upload: List[ItemToUpload]):
@@ -681,13 +679,12 @@ def get_existing_name_item_mapping(
681679
existing_name_item_mapping = {}
682680
for i in range(0, len(item_names), self.CHUNK_SIZE):
683681
items_to_check = item_names[i : i + self.CHUNK_SIZE] # noqa: E203
684-
res = self._service_provider.item_service.list(
682+
data = self._service_provider.item_service.list(
685683
self._project.id,
686684
self._folder.id,
687685
Filter("name", items_to_check, OperatorEnum.IN),
688686
)
689-
if res.ok:
690-
existing_name_item_mapping.update({i.name: i for i in res.data})
687+
existing_name_item_mapping.update({i.name: i for i in data})
691688
return existing_name_item_mapping
692689

693690
@property
@@ -1593,44 +1590,37 @@ async def run_workers(
15931590

15941591
def execute(self):
15951592
if self.is_valid():
1593+
items = []
15961594
if self._items:
15971595
if isinstance(self._items[0], str):
15981596
items = []
15991597
for names in divide_to_chunks(self._items, 1000):
1600-
response = self._service_provider.item_service.list(
1598+
data = self._service_provider.item_service.list(
16011599
self._project.id,
16021600
self._folder.id,
16031601
Filter("name", names, OperatorEnum.IN),
16041602
)
1605-
if not response.ok:
1606-
raise AppException(response.error)
1607-
items.extend(response.data)
1603+
items.extend(data)
16081604
else:
16091605
items: List[BaseItemEntity] = []
16101606
for i in range(0, len(self._items), self.CHUNK_SIZE):
16111607
search_ids = self._items[i : i + self.CHUNK_SIZE] # noqa
1612-
response = self._service_provider.item_service.list(
1608+
data = self._service_provider.item_service.list(
16131609
self._project.id,
16141610
None,
16151611
Filter("id", search_ids, OperatorEnum.IN),
16161612
)
1617-
if not response.ok:
1618-
raise AppException(response.error)
1619-
items.extend(response.data)
1613+
items.extend(data)
16201614
self._item_id_name_map = {i.id: i.name for i in items}
16211615
len_items, len_provided_items = len(items), len(self._items)
16221616
if len_items != len_provided_items:
16231617
self.reporter.log_warning(
16241618
f"Could not find annotations for {len_provided_items - len_items}/{len_provided_items} items."
16251619
)
16261620
elif self._items is None:
1627-
items = get_or_raise(
1628-
self._service_provider.item_service.list(
1629-
self._project.id, self._folder.id, EmptyQuery()
1630-
)
1621+
items = self._service_provider.item_service.list(
1622+
self._project.id, self._folder.id, EmptyQuery()
16311623
)
1632-
else:
1633-
items = []
16341624
if not items:
16351625
logger.info("No annotations to download.")
16361626
self._response.data = []
@@ -1818,21 +1808,16 @@ def execute(self):
18181808
if self._item_names:
18191809
items = []
18201810
for chunk in divide_to_chunks(self._item_names, 500):
1821-
response = self._service_provider.item_service.list(
1811+
data = self._service_provider.item_service.list(
18221812
self._project.id,
18231813
folder.id,
18241814
Filter("name", chunk, OperatorEnum.IN),
18251815
)
1826-
if response.error:
1827-
raise AppException(response.error)
1828-
items.extend(response.data)
1816+
items.extend(data)
18291817
else:
1830-
response = self._service_provider.item_service.list(
1818+
items = self._service_provider.item_service.list(
18311819
self._project.id, folder.id, EmptyQuery()
18321820
)
1833-
if not response.ok:
1834-
raise AppException(response.error)
1835-
items = response.data
18361821
if not items:
18371822
continue
18381823
new_export_path = self.destination

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

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,14 @@ def __init__(
6868
self._service_provider = service_provider
6969

7070
def execute(self):
71-
response = self._service_provider.item_service.list(
71+
data = self._service_provider.item_service.list(
7272
self._project.id,
7373
self._folder.id,
7474
Filter("name", self._image_name, OperatorEnum.EQ),
7575
)
76-
error = AppException("Image not found.")
77-
if not response.ok:
78-
raise error
79-
image = next(iter(response.data), None)
80-
if not image:
81-
raise error
76+
if not data:
77+
raise AppException("Image not found.")
78+
image = next(iter(data), None)
8279
self._response.data = image
8380
return self._response
8481

@@ -125,14 +122,12 @@ def execute(self):
125122
attachment_names = [image.name for image in self._attachments]
126123
duplications = []
127124
for names in divide_to_chunks(attachment_names, 500):
128-
response = self._service_provider.item_service.list(
125+
data = self._service_provider.item_service.list(
129126
self._project.id,
130127
self._folder.id,
131128
Filter("name", names, OperatorEnum.IN),
132129
)
133-
if not response.ok:
134-
raise AppException(response.error)
135-
duplications.extend([image.name for image in response.data])
130+
duplications.extend([image.name for image in data])
136131
meta = {}
137132
to_upload = []
138133
for image in self._attachments:
@@ -810,7 +805,7 @@ def validate_image_name_uniqueness(self):
810805
self._image_name if self._image_name else Path(self._image_path).name,
811806
OperatorEnum.EQ,
812807
),
813-
).data
808+
)
814809
if image_entities:
815810
raise AppValidationException("Image with this name already exists.")
816811

@@ -1040,7 +1035,7 @@ def filter_paths(self, paths: List[str]):
10401035

10411036
image_list = []
10421037
for i in range(0, len(filtered_paths), CHUNK_SIZE):
1043-
response = self._service_provider.item_service.list(
1038+
data = self._service_provider.item_service.list(
10441039
self._project.id,
10451040
self._folder.id,
10461041
Filter(
@@ -1052,10 +1047,7 @@ def filter_paths(self, paths: List[str]):
10521047
OperatorEnum.IN,
10531048
),
10541049
)
1055-
1056-
if not response.ok:
1057-
raise AppException(response.error)
1058-
image_list.extend([image.name for image in response.data])
1050+
image_list.extend([image.name for image in data])
10591051

10601052
image_list = set(image_list)
10611053
images_to_upload = []
@@ -1782,14 +1774,12 @@ def execute(self) -> Response:
17821774
)
17831775
duplicate_images = []
17841776
for names in divide_to_chunks(frame_names, 500):
1785-
response = self._service_provider.item_service.list(
1777+
_items = self._service_provider.item_service.list(
17861778
self._project.id,
17871779
self._folder.id,
17881780
Filter("name", names, OperatorEnum.IN),
17891781
)
1790-
if not response.ok:
1791-
raise AppException(response.error)
1792-
duplicate_images.extend(response.data)
1782+
duplicate_images.extend(_items)
17931783
duplicate_images = [image.name for image in duplicate_images]
17941784
frames_generator_use_case = ExtractFramesUseCase(
17951785
service_provider=self._service_provider,
@@ -1826,7 +1816,6 @@ def execute(self) -> Response:
18261816
)
18271817
if set(duplicate_images) == set(frame_names):
18281818
continue
1829-
uploaded_paths = []
18301819
with Progress(
18311820
total_frames_count, f"Uploading {Path(path).name}"
18321821
) as progress:
@@ -1849,7 +1838,7 @@ def execute(self) -> Response:
18491838
progress.update()
18501839

18511840
uploaded, failed_images, _ = use_case.response.data
1852-
uploaded_paths.extend(uploaded)
1841+
data.extend(uploaded)
18531842
if failed_images:
18541843
self.reporter.log_warning(
18551844
f"Failed {len(failed_images)}."
@@ -1860,6 +1849,5 @@ def execute(self) -> Response:
18601849
os.remove(image_path)
18611850
else:
18621851
raise AppException(use_case.response.errors)
1863-
data.extend(uploaded_paths)
18641852
self._response.data = data
18651853
return self._response

0 commit comments

Comments
 (0)