Skip to content

Commit 6c4c488

Browse files
authored
Merge pull request #814 from superannotateai/item_categories
added include param in get_item_by_id
2 parents 101f898 + 0a07642 commit 6c4c488

File tree

7 files changed

+91
-26
lines changed

7 files changed

+91
-26
lines changed

src/superannotate/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55

6-
__version__ = "4.4.38"
6+
__version__ = "4.4.39dev1"
77

88

99
os.environ.update({"sa_version": __version__})

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

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,12 @@ def get_folder_by_id(self, project_id: int, folder_id: int):
335335
exclude={"completedCount", "is_root"}
336336
)
337337

338-
def get_item_by_id(self, project_id: int, item_id: int):
338+
def get_item_by_id(
339+
self,
340+
project_id: int,
341+
item_id: int,
342+
include: List[Literal["custom_metadata", "categories"]] = None,
343+
):
339344
"""Returns the item metadata
340345
341346
:param project_id: the id of the project
@@ -344,16 +349,50 @@ def get_item_by_id(self, project_id: int, item_id: int):
344349
:param item_id: the id of the item
345350
:type item_id: int
346351
352+
:param include: Specifies additional fields to include in the response.
353+
354+
Possible values are
355+
356+
- "custom_metadata": Includes custom metadata attached to the item.
357+
- "categories": Includes categories attached to the item.
358+
:type include: list of str, optional
359+
347360
:return: item metadata
348361
:rtype: dict
349362
"""
350363
project_response = self.controller.get_project_by_id(project_id=project_id)
351364
project_response.raise_for_status()
352-
item = self.controller.get_item_by_id(
353-
item_id=item_id, project=project_response.data
365+
366+
if (
367+
include
368+
and "categories" in include
369+
and project_response.data.type != ProjectType.MULTIMODAL.value
370+
):
371+
raise AppException(
372+
"The 'categories' option in the 'include' field is only supported for Multimodal projects."
373+
)
374+
375+
# always join assignments for all project types
376+
_include = {"assignments"}
377+
if include:
378+
_include.update(set(include))
379+
include = list(_include)
380+
381+
include_custom_metadata = "custom_metadata" in include
382+
if include_custom_metadata:
383+
include.remove("custom_metadata")
384+
385+
item = self.controller.items.get_item_by_id(
386+
item_id=item_id, project=project_response.data, include=include
354387
)
355388

356-
return BaseSerializer(item).serialize(exclude={"url", "meta"})
389+
if include_custom_metadata:
390+
item_custom_fields = self.controller.custom_fields.list_fields(
391+
project=project_response.data, item_ids=[item.id]
392+
)
393+
item.custom_metadata = item_custom_fields[item.id]
394+
395+
return BaseSerializer(item).serialize(exclude={"url", "meta"}, by_alias=False)
357396

358397
def get_team_metadata(self, include: List[Literal["scores"]] = None):
359398
"""
@@ -5237,7 +5276,7 @@ def item_context(
52375276
"This function is only supported for Multimodal projects."
52385277
)
52395278
if isinstance(item, int):
5240-
_item = self.controller.get_item_by_id(item_id=item, project=project)
5279+
_item = self.controller.items.get_item_by_id(item_id=item, project=project)
52415280
else:
52425281
items = self.controller.items.list_items(project, folder, name=item)
52435282
if not items:

src/superannotate/lib/infrastructure/controller.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@
5050
from lib.core.jsx_conditions import Query
5151
from lib.core.reporter import Reporter
5252
from lib.core.response import Response
53-
from lib.core.service_types import PROJECT_TYPE_RESPONSE_MAP
54-
from lib.core.usecases import serialize_item_entity
5553
from lib.infrastructure.custom_entities import generate_schema
5654
from lib.infrastructure.helpers import timed_lru_cache
5755
from lib.infrastructure.query_builder import FieldValidationHandler
@@ -932,15 +930,16 @@ def process_response(
932930
service_provider,
933931
items: List[BaseItemEntity],
934932
project: ProjectEntity,
935-
folder: FolderEntity,
933+
folder: Optional[FolderEntity] = None,
936934
map_fields: bool = True,
937935
) -> List[BaseItemEntity]:
938936
"""Process the response data and return a list of serialized items."""
939937
data = []
940938
for item in items:
941939
if map_fields:
942940
item = usecases.serialize_item_entity(item, project)
943-
item = usecases.add_item_path(project, folder, item)
941+
if folder:
942+
item = usecases.add_item_path(project, folder, item)
944943
else:
945944
item = usecases.serialize_item_entity(item, project, map_fields=False)
946945
item.annotation_status = service_provider.get_annotation_status_name(
@@ -985,6 +984,30 @@ def list_items(
985984
data = self.service_provider.item_service.list(project.id, folder.id, query)
986985
return self.process_response(self.service_provider, data, project, folder)
987986

987+
def get_item_by_id(
988+
self,
989+
item_id: int,
990+
project: ProjectEntity,
991+
include: List[Literal["categories", "assignments"]] = None,
992+
) -> BaseItemEntity:
993+
query = EmptyQuery()
994+
995+
if include:
996+
if "assignments" in include:
997+
query &= Join("assignments")
998+
if "categories" in include:
999+
# join item categories for multimodal projects
1000+
query &= Join("categories")
1001+
1002+
response = self.service_provider.item_service.get(
1003+
project_id=project.id, item_id=item_id, query=query
1004+
)
1005+
if response.error:
1006+
raise AppException(response.error)
1007+
1008+
item = self.process_response(self.service_provider, [response.data], project)[0]
1009+
return item
1010+
9881011
def attach(
9891012
self,
9901013
project: ProjectEntity,
@@ -1631,19 +1654,6 @@ def get_project_by_id(self, project_id: int):
16311654
raise AppException("Project not found.")
16321655
return response
16331656

1634-
def get_item_by_id(self, item_id: int, project: ProjectEntity) -> BaseItemEntity:
1635-
response = self.service_provider.item_service.get(
1636-
project_id=project.id, item_id=item_id
1637-
)
1638-
if response.error:
1639-
raise AppException(response.error)
1640-
PROJECT_TYPE_RESPONSE_MAP[project.type] = response.data
1641-
item = serialize_item_entity(response.data, project)
1642-
item.annotation_status = self.service_provider.get_annotation_status_name(
1643-
project, item.annotation_status
1644-
)
1645-
return item
1646-
16471657
def get_project_folder_by_path(
16481658
self, path: Union[str, Path]
16491659
) -> Tuple[ProjectEntity, FolderEntity]:
@@ -2044,7 +2054,7 @@ def get_item(
20442054
self, project: ProjectEntity, folder: FolderEntity, item: Union[int, str]
20452055
) -> BaseItemEntity:
20462056
if isinstance(item, int):
2047-
return self.get_item_by_id(item_id=item, project=project)
2057+
return self.items.get_item_by_id(item_id=item, project=project)
20482058
else:
20492059
return self.items.get_by_name(project, folder, item)
20502060

src/superannotate/lib/infrastructure/services/item_service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class ItemService(SuperannotateServiceProvider):
1818
URL_LIST = "items"
1919
URL_GET = "items/{item_id}"
2020

21-
def get(self, project_id: int, item_id: int):
21+
def get(self, project_id: int, item_id: int, query: Query):
2222
result = self.client.request(
23-
url=self.URL_GET.format(item_id=item_id),
23+
url=f"{self.URL_GET.format(item_id=item_id)}?{query.build_query()}",
2424
method="GET",
2525
content_type=BaseItemResponse,
2626
headers={

tests/integration/custom_fields/test_custom_schema.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ def test_get_item_metadata(self):
136136
item = sa.get_item_metadata(
137137
self.PROJECT_NAME, item_name, include_custom_metadata=True
138138
)
139+
# test custom_metadata using get_item_by_id
140+
assert item["custom_metadata"] == payload
141+
item = sa.get_item_by_id(
142+
self._project["id"], item["id"], include=["custom_metadata"]
143+
)
139144
assert item["custom_metadata"] == payload
140145

141146
def test_get_item_metadata_without_custom_metadata(self):

tests/integration/items/test_attach_category.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ def test_attache_category(self):
9292
items = sa.list_items(self.PROJECT_NAME, include=["categories"])
9393
assert all(i["categories"][0]["value"] == "category-1" for i in items)
9494

95+
# test get categories to use get_item_by_id
96+
item = sa.get_item_by_id(
97+
self._project["id"], items[0]["id"], include=["categories"]
98+
)
99+
assert item["categories"][0]["value"] == "category-1"
100+
95101
def test_remove_items_category(self):
96102
self._attach_items(self.PROJECT_NAME, ["item-1", "item-2", "item-3"])
97103
sa.create_categories(self.PROJECT_NAME, ["category-1", "category-2"])

tests/integration/work_management/test_pause_resume_user_activity.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ def test_pause_and_resume_user_activity(self):
6767
[i["name"] for i in self.ATTACHMENT_LIST],
6868
scapegoat["email"],
6969
)
70+
# test assignments use get_item_by_id
71+
items_id = [i["id"] for i in sa.list_items(self.PROJECT_NAME)][0]
72+
item = sa.get_item_by_id(project_id=self._project["id"], item_id=items_id)
73+
assert len(item["assignments"]) == 1
74+
assert item["assignments"][0]["user_role"] == "QA"
7075

7176
@skip("For not send real email")
7277
def test_pause_resume_pending_user(self):

0 commit comments

Comments
 (0)