Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/superannotate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys


__version__ = "4.4.38"
__version__ = "4.4.39dev1"


os.environ.update({"sa_version": __version__})
Expand Down
49 changes: 44 additions & 5 deletions src/superannotate/lib/app/interface/sdk_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,12 @@ def get_folder_by_id(self, project_id: int, folder_id: int):
exclude={"completedCount", "is_root"}
)

def get_item_by_id(self, project_id: int, item_id: int):
def get_item_by_id(
self,
project_id: int,
item_id: int,
include: List[Literal["custom_metadata", "categories"]] = None,
):
"""Returns the item metadata

:param project_id: the id of the project
Expand All @@ -344,16 +349,50 @@ def get_item_by_id(self, project_id: int, item_id: int):
:param item_id: the id of the item
:type item_id: int

:param include: Specifies additional fields to include in the response.

Possible values are

- "custom_metadata": Includes custom metadata attached to the item.
- "categories": Includes categories attached to the item.
:type include: list of str, optional

:return: item metadata
:rtype: dict
"""
project_response = self.controller.get_project_by_id(project_id=project_id)
project_response.raise_for_status()
item = self.controller.get_item_by_id(
item_id=item_id, project=project_response.data

if (
include
and "categories" in include
and project_response.data.type != ProjectType.MULTIMODAL.value
):
raise AppException(
"The 'categories' option in the 'include' field is only supported for Multimodal projects."
)

# always join assignments for all project types
_include = {"assignments"}
if include:
_include.update(set(include))
include = list(_include)

include_custom_metadata = "custom_metadata" in include
if include_custom_metadata:
include.remove("custom_metadata")

item = self.controller.items.get_item_by_id(
item_id=item_id, project=project_response.data, include=include
)

return BaseSerializer(item).serialize(exclude={"url", "meta"})
if include_custom_metadata:
item_custom_fields = self.controller.custom_fields.list_fields(
project=project_response.data, item_ids=[item.id]
)
item.custom_metadata = item_custom_fields[item.id]

return BaseSerializer(item).serialize(exclude={"url", "meta"}, by_alias=False)

def get_team_metadata(self, include: List[Literal["scores"]] = None):
"""
Expand Down Expand Up @@ -5237,7 +5276,7 @@ def item_context(
"This function is only supported for Multimodal projects."
)
if isinstance(item, int):
_item = self.controller.get_item_by_id(item_id=item, project=project)
_item = self.controller.items.get_item_by_id(item_id=item, project=project)
else:
items = self.controller.items.list_items(project, folder, name=item)
if not items:
Expand Down
46 changes: 28 additions & 18 deletions src/superannotate/lib/infrastructure/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,6 @@
from lib.core.jsx_conditions import Query
from lib.core.reporter import Reporter
from lib.core.response import Response
from lib.core.service_types import PROJECT_TYPE_RESPONSE_MAP
from lib.core.usecases import serialize_item_entity
from lib.infrastructure.custom_entities import generate_schema
from lib.infrastructure.helpers import timed_lru_cache
from lib.infrastructure.query_builder import FieldValidationHandler
Expand Down Expand Up @@ -932,15 +930,16 @@ def process_response(
service_provider,
items: List[BaseItemEntity],
project: ProjectEntity,
folder: FolderEntity,
folder: Optional[FolderEntity] = None,
map_fields: bool = True,
) -> List[BaseItemEntity]:
"""Process the response data and return a list of serialized items."""
data = []
for item in items:
if map_fields:
item = usecases.serialize_item_entity(item, project)
item = usecases.add_item_path(project, folder, item)
if folder:
item = usecases.add_item_path(project, folder, item)
else:
item = usecases.serialize_item_entity(item, project, map_fields=False)
item.annotation_status = service_provider.get_annotation_status_name(
Expand Down Expand Up @@ -985,6 +984,30 @@ def list_items(
data = self.service_provider.item_service.list(project.id, folder.id, query)
return self.process_response(self.service_provider, data, project, folder)

def get_item_by_id(
self,
item_id: int,
project: ProjectEntity,
include: List[Literal["categories", "assignments"]] = None,
) -> BaseItemEntity:
query = EmptyQuery()

if include:
if "assignments" in include:
query &= Join("assignments")
if "categories" in include:
# join item categories for multimodal projects
query &= Join("categories")

response = self.service_provider.item_service.get(
project_id=project.id, item_id=item_id, query=query
)
if response.error:
raise AppException(response.error)

item = self.process_response(self.service_provider, [response.data], project)[0]
return item

def attach(
self,
project: ProjectEntity,
Expand Down Expand Up @@ -1631,19 +1654,6 @@ def get_project_by_id(self, project_id: int):
raise AppException("Project not found.")
return response

def get_item_by_id(self, item_id: int, project: ProjectEntity) -> BaseItemEntity:
response = self.service_provider.item_service.get(
project_id=project.id, item_id=item_id
)
if response.error:
raise AppException(response.error)
PROJECT_TYPE_RESPONSE_MAP[project.type] = response.data
item = serialize_item_entity(response.data, project)
item.annotation_status = self.service_provider.get_annotation_status_name(
project, item.annotation_status
)
return item

def get_project_folder_by_path(
self, path: Union[str, Path]
) -> Tuple[ProjectEntity, FolderEntity]:
Expand Down Expand Up @@ -2044,7 +2054,7 @@ def get_item(
self, project: ProjectEntity, folder: FolderEntity, item: Union[int, str]
) -> BaseItemEntity:
if isinstance(item, int):
return self.get_item_by_id(item_id=item, project=project)
return self.items.get_item_by_id(item_id=item, project=project)
else:
return self.items.get_by_name(project, folder, item)

Expand Down
4 changes: 2 additions & 2 deletions src/superannotate/lib/infrastructure/services/item_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ class ItemService(SuperannotateServiceProvider):
URL_LIST = "items"
URL_GET = "items/{item_id}"

def get(self, project_id: int, item_id: int):
def get(self, project_id: int, item_id: int, query: Query):
result = self.client.request(
url=self.URL_GET.format(item_id=item_id),
url=f"{self.URL_GET.format(item_id=item_id)}?{query.build_query()}",
method="GET",
content_type=BaseItemResponse,
headers={
Expand Down
5 changes: 5 additions & 0 deletions tests/integration/custom_fields/test_custom_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ def test_get_item_metadata(self):
item = sa.get_item_metadata(
self.PROJECT_NAME, item_name, include_custom_metadata=True
)
# test custom_metadata using get_item_by_id
assert item["custom_metadata"] == payload
item = sa.get_item_by_id(
self._project["id"], item["id"], include=["custom_metadata"]
)
assert item["custom_metadata"] == payload

def test_get_item_metadata_without_custom_metadata(self):
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/items/test_attach_category.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ def test_attache_category(self):
items = sa.list_items(self.PROJECT_NAME, include=["categories"])
assert all(i["categories"][0]["value"] == "category-1" for i in items)

# test get categories to use get_item_by_id
item = sa.get_item_by_id(
self._project["id"], items[0]["id"], include=["categories"]
)
assert item["categories"][0]["value"] == "category-1"

def test_remove_items_category(self):
self._attach_items(self.PROJECT_NAME, ["item-1", "item-2", "item-3"])
sa.create_categories(self.PROJECT_NAME, ["category-1", "category-2"])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ def test_pause_and_resume_user_activity(self):
[i["name"] for i in self.ATTACHMENT_LIST],
scapegoat["email"],
)
# test assignments use get_item_by_id
items_id = [i["id"] for i in sa.list_items(self.PROJECT_NAME)][0]
item = sa.get_item_by_id(project_id=self._project["id"], item_id=items_id)
assert len(item["assignments"]) == 1
assert item["assignments"][0]["user_role"] == "QA"

@skip("For not send real email")
def test_pause_resume_pending_user(self):
Expand Down