Skip to content

Commit b5a5e3d

Browse files
committed
switching branches to deal with id presesnce
1 parent 21eedd0 commit b5a5e3d

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

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

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3044,3 +3044,76 @@ def delete_custom_values(
30443044
)
30453045
if response.errors:
30463046
raise AppException(response.errors)
3047+
3048+
def add_items_to_subset(
3049+
project: NotEmptyStr, subset: NotEmptyStr, items: List[dict]
3050+
):
3051+
"""
3052+
3053+
Associates selected items with a given subset. Non-existing subset will be automatically created.
3054+
3055+
:param project: – project name (e.g., “project1”)
3056+
:type project: str
3057+
3058+
:param subset: a name of an existing/new subset to associate items with. New subsets will be automatically created.
3059+
:type subset: str
3060+
3061+
:param items: – list of items metadata. Required keys are 'name' and 'path'
3062+
:type items: list of dicts
3063+
3064+
Request Example:
3065+
3066+
::
3067+
client = SAClient()
3068+
3069+
# option 1
3070+
queried_items = client.query(
3071+
project="Image Project",
3072+
query="instance(error = true)"
3073+
)
3074+
3075+
client.add_items_to_subset(
3076+
project="Medical Annotations",
3077+
subset="Brain Study - Disapproved",
3078+
items=queried_items
3079+
)
3080+
3081+
items_list = [
3082+
{
3083+
'name': 'image_1.jpeg',
3084+
'path': 'Image Project'
3085+
},
3086+
{
3087+
'name': 'image_2.jpeg',
3088+
'path': 'Image Project/Subfolder A'
3089+
}
3090+
]
3091+
3092+
client.add_items_to_subset(
3093+
project="Image Project",
3094+
subset="Subset Name",
3095+
items=items_list
3096+
3097+
)
3098+
3099+
3100+
Response Example:
3101+
::
3102+
{
3103+
"succeeded": [{}, {}],
3104+
"failed": [],
3105+
"skipped": []
3106+
}
3107+
3108+
"""
3109+
3110+
project_name, _ = extract_project_folder(project)
3111+
3112+
response = self.controller.add_items_to_subset(
3113+
project,
3114+
subset,
3115+
items
3116+
)
3117+
3118+
if response.errors:
3119+
raise AppException(response.errors)

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

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import copy
22
from typing import List
33
from typing import Optional
4+
from collections import defaultdict
45

56
import superannotate.lib.core as constants
67
from lib.core.conditions import Condition
@@ -23,6 +24,7 @@
2324
from lib.core.serviceproviders import SuperannotateServiceProvider
2425
from lib.core.usecases.base import BaseReportableUseCase
2526
from lib.core.usecases.base import BaseUseCase
27+
2628
from superannotate.logger import get_default_logger
2729

2830
logger = get_default_logger()
@@ -809,3 +811,113 @@ def execute(self):
809811
)
810812

811813
return self._response
814+
815+
class AddItemsToSubsetUseCase(BaseUseCase):
816+
CHUNK_SIZE = 5000
817+
818+
def __init__(self, reporter, project, subset_name, items ):
819+
self.reporter = reporter
820+
self.project = project
821+
self.subset_name = subset_name
822+
self.items=items
823+
self.results = {
824+
"failed":[],
825+
"skipped":[],
826+
"succeded": []
827+
}
828+
self.item_ids = []
829+
self.path_separated = defaultdict(list)
830+
831+
def __filter_duplicates():
832+
def uniqueQ(item, seen):
833+
result = True
834+
if "id" in item:
835+
if item["id"] in seen:
836+
result = False
837+
else:
838+
seen.add(item["id"])
839+
840+
if "name" in item and "path" in item:
841+
unique_str = f"{item['path']}/item['name']"
842+
if unique_str in seen:
843+
result = False
844+
else:
845+
seen.add(unique_str)
846+
return result
847+
848+
seen = set()
849+
uniques = [x for x in self.items if uniqueQ(x, seen)]
850+
return uniques
851+
852+
def __filter_invalid_items():
853+
def validQ(item):
854+
if "id" in items:
855+
return True
856+
if "name" in item and "path" in item:
857+
return True
858+
self.results['skipped'].append(item)
859+
return False
860+
861+
filtered_items = [x for x in self.items if validQ(x)]
862+
863+
864+
return filtered_items
865+
866+
def __separate_to_paths():
867+
for item in self.items:
868+
if 'id' in item:
869+
self.item_ids.append(item['id'])
870+
else:
871+
self.path_separated[item['path']].append(item["name"])
872+
873+
874+
def __query(path, item_names):
875+
folder = None
876+
query_use_case = QueryEntitiesUseCase(
877+
reporter = self.reporter,
878+
project = self.project,
879+
folder = folder,
880+
backend_service_provider = self._backend_client,
881+
query = query
882+
)
883+
884+
queried_items = query_use_case.execute()
885+
return [x["id"] for x in queried_items]
886+
887+
def validate_items():
888+
filtered_items = self.__filter_duplicates()
889+
if len(filtered_items) != len(self.items):
890+
self.reporter.log_info(
891+
f"Dropping duplicates found {len(filtered_items)} / {len(self.items)} unique items"
892+
)
893+
self.items = filtered_items
894+
self.items = self.__filter_invalid_items()
895+
self.__separate_to_paths()
896+
897+
def execute():
898+
if self.is_valid():
899+
900+
futures = []
901+
with ThreadPoolExecutor(max_workers=4) as executor:
902+
for path, item_names in self.path_separated.items():
903+
future = executor.submit(self.__query, path, item_names)
904+
futures.append(future)
905+
906+
for future in as_completed(futures):
907+
ids = future.result
908+
self.item_ids.exend(ids)
909+
910+
911+
for i in range(0, len(self.item_ids), self.CHUNK_SIZE):
912+
response = self._backend_client.attach_items_to_subset(
913+
project = self.project.id,
914+
team_id = self.project.team,
915+
item_ids = self.item_ids[i:i+self.CHUNK_SIZE]
916+
)
917+
918+
if response.ok:
919+
...
920+
else:
921+
...
922+
923+

src/superannotate/lib/infrastructure/controller.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,3 +1598,16 @@ def delete_custom_values(
15981598
backend_client=self.backend_client,
15991599
)
16001600
return use_case.execute()
1601+
1602+
def add_items_to_subset(project_name: str, subset: str, items: List[dict]):
1603+
1604+
project = self._get_project(project_name)
1605+
1606+
use_case = usecases.AddItemsToSubsetUseCase(
1607+
reporter = self.get_default_reporter(),
1608+
project = project,
1609+
subset = subset,
1610+
items = items
1611+
)
1612+
1613+
return use_case.execute()

0 commit comments

Comments
 (0)