Skip to content

Commit 1cef3a0

Browse files
authored
Merge branch 'friday' into 1028_delete_items
2 parents 4e25c50 + 307784e commit 1cef3a0

File tree

13 files changed

+200
-25
lines changed

13 files changed

+200
-25
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,20 @@ ______
7373
.. automethod:: superannotate.SAClient.attach_items
7474
.. automethod:: superannotate.SAClient.copy_items
7575
.. automethod:: superannotate.SAClient.move_items
76+
.. automethod:: superannotate.SAClient.assign_items
77+
.. automethod:: superannotate.SAClient.unassign_items
7678
.. automethod:: superannotate.SAClient.get_item_metadata
7779
.. automethod:: superannotate.SAClient.set_annotation_statuses
7880

7981
----------
8082

83+
Subsets
84+
______
85+
86+
.. automethod:: superannotate.SAClient.get_subsets
87+
88+
----------
89+
8190
Images
8291
______
8392

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,7 +2450,12 @@ def attach_items_from_integrated_storage(
24502450
if response.errors:
24512451
raise AppException(response.errors)
24522452

2453-
def query(self, project: NotEmptyStr, query: Optional[NotEmptyStr]):
2453+
def query(
2454+
self,
2455+
project: NotEmptyStr,
2456+
query: Optional[NotEmptyStr] = None,
2457+
subset: Optional[NotEmptyStr] = None,
2458+
):
24542459
"""Return items that satisfy the given query.
24552460
Query syntax should be in SuperAnnotate query language(https://doc.superannotate.com/docs/query-search-1).
24562461
@@ -2460,11 +2465,17 @@ def query(self, project: NotEmptyStr, query: Optional[NotEmptyStr]):
24602465
:param query: SAQuL query string.
24612466
:type query: str
24622467
2468+
:param subset: subset name. Allows you to query items in a specific subset.
2469+
To return all the items in the specified subset, set the value of query param to None.
2470+
:type subset: str
2471+
24632472
:return: queried items’ metadata list
24642473
:rtype: list of dicts
24652474
"""
24662475
project_name, folder_name = extract_project_folder(project)
2467-
response = self.controller.query_entities(project_name, folder_name, query)
2476+
response = self.controller.query_entities(
2477+
project_name, folder_name, query, subset
2478+
)
24682479
if response.errors:
24692480
raise AppException(response.errors)
24702481
return BaseSerializer.serialize_iterable(response.data)
@@ -2771,3 +2782,19 @@ def download_annotations(
27712782
if response.errors:
27722783
raise AppException(response.errors)
27732784
return response.data
2785+
2786+
def get_subsets(self, project: Union[NotEmptyStr, dict]):
2787+
"""Get Subsets
2788+
2789+
:param project: project name (e.g., “project1”)
2790+
:type project: str
2791+
2792+
:return: subsets’ metadata
2793+
:rtype: list of dicts
2794+
"""
2795+
project_name, _ = extract_project_folder(project)
2796+
2797+
response = self.controller.list_subsets(project_name)
2798+
if response.errors:
2799+
raise AppException(response.errors)
2800+
return BaseSerializer.serialize_iterable(response.data, ["name"])

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from lib.core.entities.base import BaseEntity as TmpBaseEntity
33
from lib.core.entities.base import ProjectEntity
44
from lib.core.entities.base import SettingEntity
5+
from lib.core.entities.base import SubSetEntity
56
from lib.core.entities.integrations import IntegrationEntity
67
from lib.core.entities.items import DocumentEntity
78
from lib.core.entities.items import Entity
@@ -31,6 +32,7 @@
3132
__all__ = [
3233
# base
3334
"SettingEntity",
35+
"SubSetEntity",
3436
# items
3537
"TmpImageEntity",
3638
"BaseEntity",

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ def validate(cls, v: datetime):
2727
return v.isoformat()
2828

2929

30+
class SubSetEntity(BaseModel):
31+
id: Optional[int]
32+
name: str
33+
34+
class Config:
35+
extra = Extra.ignore
36+
37+
3038
class TimedBaseModel(BaseModel):
3139
createdAt: StringDate = Field(None, alias="createdAt")
3240
updatedAt: StringDate = Field(None, alias="updatedAt")

src/superannotate/lib/core/service_types.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from pydantic import BaseModel
88
from pydantic import Extra
9+
from pydantic import parse_obj_as
910

1011

1112
class Limit(BaseModel):
@@ -81,7 +82,7 @@ def __init__(self, response, content_type=None):
8182
}
8283
try:
8384
if content_type and content_type is not self.__class__:
84-
data["data"] = content_type(**response.json())
85+
data["data"] = parse_obj_as(content_type, response.json())
8586
else:
8687
data["data"] = response.json()
8788
except Exception as e:

src/superannotate/lib/core/serviceproviders.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,17 @@ def attach_integrations(
365365
raise NotImplementedError
366366

367367
def saqul_query(
368-
self, team_id: int, project_id: int, query: str, folder_id: int
368+
self,
369+
team_id: int,
370+
project_id: int,
371+
folder_id: int,
372+
query: str = None,
373+
subset_id: int = None,
369374
) -> ServiceResponse:
370375
raise NotImplementedError
371376

372377
def validate_saqul_query(self, team_id: int, project_id: int, query: str) -> dict:
373378
raise NotImplementedError
379+
380+
def list_sub_sets(self, team_id: int, project_id: int) -> ServiceResponse:
381+
raise NotImplementedError

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

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import copy
22
from typing import List
3+
from typing import Optional
34

45
import superannotate.lib.core as constants
56
from lib.core.conditions import Condition
@@ -9,6 +10,7 @@
910
from lib.core.entities import Entity
1011
from lib.core.entities import FolderEntity
1112
from lib.core.entities import ProjectEntity
13+
from lib.core.entities import SubSetEntity
1214
from lib.core.entities import TmpImageEntity
1315
from lib.core.entities import VideoEntity
1416
from lib.core.exceptions import AppException
@@ -112,41 +114,78 @@ def execute(self) -> Response:
112114
return self._response
113115

114116

115-
class QueryEntities(BaseReportableUseCase):
117+
class QueryEntitiesUseCase(BaseReportableUseCase):
116118
def __init__(
117119
self,
118120
reporter: Reporter,
119121
project: ProjectEntity,
120122
folder: FolderEntity,
121123
backend_service_provider: SuperannotateServiceProvider,
122124
query: str,
125+
subset: str,
123126
):
124127
super().__init__(reporter)
125128
self._project = project
126129
self._folder = folder
127130
self._backend_client = backend_service_provider
128131
self._query = query
132+
self._subset = subset
129133

130134
def validate_query(self):
131-
response = self._backend_client.validate_saqul_query(
132-
self._project.team_id, self._project.id, self._query
133-
)
134-
if response.get("error"):
135-
raise AppException(response["error"])
136-
if response["isValidQuery"]:
137-
self._query = response["parsedQuery"]
138-
else:
139-
raise AppException("Incorrect query.")
140-
if self._project.sync_status != constants.ProjectState.SYNCED.value:
135+
if self._project.sync_status != constances.ProjectState.SYNCED.value:
141136
raise AppException("Data is not synced.")
137+
if self._query:
138+
response = self._backend_client.validate_saqul_query(
139+
self._project.team_id, self._project.id, self._query
140+
)
141+
if response.get("error"):
142+
raise AppException(response["error"])
143+
if response["isValidQuery"]:
144+
self._query = response["parsedQuery"]
145+
else:
146+
raise AppException("Incorrect query.")
147+
else:
148+
response = self._backend_client.validate_saqul_query(
149+
self._project.team_id, self._project.id, "-"
150+
)
151+
if response.get("error"):
152+
raise AppException(response["error"])
142153

143154
def execute(self) -> Response:
155+
if not any([self._query, self._subset]):
156+
self._response.errors = AppException(
157+
"AppException: The query and subset params cannot have the value None at the same time."
158+
)
159+
return self._response
160+
144161
if self.is_valid():
162+
query_kwargs = {}
163+
if self._subset:
164+
subset: Optional[SubSetEntity] = None
165+
response = self._backend_client.list_sub_sets(
166+
team_id=self._project.team_id, project_id=self._project.id
167+
)
168+
if response.ok:
169+
subset = next(
170+
(_sub for _sub in response.data if _sub.name == self._subset),
171+
None,
172+
)
173+
if not subset:
174+
self._response.errors = AppException(
175+
"Subset not found. Use the superannotate."
176+
"get_subsets() function to get a list of the available subsets."
177+
)
178+
return self._response
179+
query_kwargs["subset_id"] = subset.id
180+
if self._query:
181+
query_kwargs["query"] = self._query
182+
query_kwargs["folder_id"] = (
183+
None if self._folder.name == "root" else self._folder.uuid
184+
)
145185
service_response = self._backend_client.saqul_query(
146186
self._project.team_id,
147187
self._project.id,
148-
self._query,
149-
folder_id=None if self._folder.name == "root" else self._folder.uuid,
188+
**query_kwargs,
150189
)
151190
if service_response.ok:
152191
data = []

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from lib.core.reporter import Reporter
2020
from lib.core.repositories import BaseManageableRepository
2121
from lib.core.repositories import BaseReadOnlyRepository
22+
from lib.core.response import Response
2223
from lib.core.serviceproviders import SuperannotateServiceProvider
2324
from lib.core.usecases.base import BaseReportableUseCase
2425
from lib.core.usecases.base import BaseUseCase
@@ -1079,3 +1080,38 @@ def execute(self):
10791080
)
10801081
self._response.data = invited, list(to_skip)
10811082
return self._response
1083+
1084+
1085+
class ListSubsetsUseCase(BaseReportableUseCase):
1086+
def __init__(
1087+
self,
1088+
reporter: Reporter,
1089+
project: ProjectEntity,
1090+
backend_client: SuperannotateServiceProvider,
1091+
):
1092+
super().__init__(reporter)
1093+
self._project = project
1094+
self._backend_client = backend_client
1095+
1096+
def validate_project(self):
1097+
if self._project.sync_status != constances.ProjectState.SYNCED.value:
1098+
raise AppException("Data is not synced.")
1099+
1100+
response = self._backend_client.validate_saqul_query(
1101+
self._project.team_id, self._project.id, "_"
1102+
)
1103+
error = response.get("error")
1104+
if error:
1105+
raise AppException(response["error"])
1106+
1107+
def execute(self) -> Response:
1108+
if self.is_valid():
1109+
sub_sets_response = self._backend_client.list_sub_sets(
1110+
team_id=self._project.team_id, project_id=self._project.id
1111+
)
1112+
if sub_sets_response.ok:
1113+
self._response.data = sub_sets_response.data
1114+
else:
1115+
self._response.data = []
1116+
1117+
return self._response

src/superannotate/lib/infrastructure/controller.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,15 +1527,18 @@ def attach_integrations(
15271527
)
15281528
return use_case.execute()
15291529

1530-
def query_entities(self, project_name: str, folder_name: str, query: str = None):
1530+
def query_entities(
1531+
self, project_name: str, folder_name: str, query: str = None, subset: str = None
1532+
):
15311533
project = self._get_project(project_name)
15321534
folder = self._get_folder(project, folder_name)
15331535

1534-
use_case = usecases.QueryEntities(
1536+
use_case = usecases.QueryEntitiesUseCase(
15351537
reporter=self.get_default_reporter(),
15361538
project=project,
15371539
folder=folder,
15381540
query=query,
1541+
subset=subset,
15391542
backend_service_provider=self.backend_client,
15401543
)
15411544
return use_case.execute()
@@ -1707,3 +1710,12 @@ def download_annotations(
17071710
callback=callback,
17081711
)
17091712
return use_case.execute()
1713+
1714+
def list_subsets(self, project_name: str):
1715+
project = self._get_project(project_name)
1716+
use_case = usecases.ListSubsetsUseCase(
1717+
reporter=self.get_default_reporter(),
1718+
project=project,
1719+
backend_client=self.backend_client,
1720+
)
1721+
return use_case.execute()

0 commit comments

Comments
 (0)