Skip to content

Commit 143d30f

Browse files
committed
added subset to query function
1 parent 1c74f5c commit 143d30f

File tree

9 files changed

+107
-25
lines changed

9 files changed

+107
-25
lines changed

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2423,7 +2423,12 @@ def attach_items_from_integrated_storage(
24232423
if response.errors:
24242424
raise AppException(response.errors)
24252425

2426-
def query(self, project: NotEmptyStr, query: Optional[NotEmptyStr]):
2426+
def query(
2427+
self,
2428+
project: NotEmptyStr,
2429+
query: Optional[NotEmptyStr] = None,
2430+
subset: Optional[NotEmptyStr] = None,
2431+
):
24272432
"""Return items that satisfy the given query.
24282433
Query syntax should be in SuperAnnotate query language(https://doc.superannotate.com/docs/query-search-1).
24292434
@@ -2433,11 +2438,17 @@ def query(self, project: NotEmptyStr, query: Optional[NotEmptyStr]):
24332438
:param query: SAQuL query string.
24342439
:type query: str
24352440
2441+
:param subset: subset name. Allows you to query items in a specific subset.
2442+
To return all the items in the specified subset, set the value of query param to None.
2443+
:type subset: str
2444+
24362445
:return: queried items’ metadata list
24372446
:rtype: list of dicts
24382447
"""
24392448
project_name, folder_name = extract_project_folder(project)
2440-
response = self.controller.query_entities(project_name, folder_name, query)
2449+
response = self.controller.query_entities(
2450+
project_name, folder_name, query, subset
2451+
)
24412452
if response.errors:
24422453
raise AppException(response.errors)
24432454
return BaseSerializer.serialize_iterable(response.data)
@@ -2759,4 +2770,4 @@ def get_subsets(self, project: Union[NotEmptyStr, dict]):
27592770
response = self.controller.list_subsets(project_name)
27602771
if response.errors:
27612772
raise AppException(response.errors)
2762-
return BaseSerializer.serialize_iterable(response.data)
2773+
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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def validate(cls, v: datetime):
2828

2929

3030
class SubSetEntity(BaseModel):
31+
id: Optional[int]
3132
name: str
3233

3334
class Config:

src/superannotate/lib/core/serviceproviders.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,12 @@ def attach_integrations(
362362
raise NotImplementedError
363363

364364
def saqul_query(
365-
self, team_id: int, project_id: int, query: str, folder_id: int
365+
self,
366+
team_id: int,
367+
project_id: int,
368+
folder_id: int,
369+
query: str = None,
370+
subset_id: int = None,
366371
) -> ServiceResponse:
367372
raise NotImplementedError
368373

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

Lines changed: 51 additions & 12 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 constances
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
@@ -78,41 +80,78 @@ def execute(self) -> Response:
7880
return self._response
7981

8082

81-
class QueryEntities(BaseReportableUseCase):
83+
class QueryEntitiesUseCase(BaseReportableUseCase):
8284
def __init__(
8385
self,
8486
reporter: Reporter,
8587
project: ProjectEntity,
8688
folder: FolderEntity,
8789
backend_service_provider: SuperannotateServiceProvider,
8890
query: str,
91+
subset: str,
8992
):
9093
super().__init__(reporter)
9194
self._project = project
9295
self._folder = folder
9396
self._backend_client = backend_service_provider
9497
self._query = query
98+
self._subset = subset
9599

96100
def validate_query(self):
97-
response = self._backend_client.validate_saqul_query(
98-
self._project.team_id, self._project.id, self._query
99-
)
100-
if response.get("error"):
101-
raise AppException(response["error"])
102-
if response["isValidQuery"]:
103-
self._query = response["parsedQuery"]
104-
else:
105-
raise AppException("Incorrect query.")
106101
if self._project.sync_status != constances.ProjectState.SYNCED.value:
107102
raise AppException("Data is not synced.")
103+
if self._query:
104+
response = self._backend_client.validate_saqul_query(
105+
self._project.team_id, self._project.id, self._query
106+
)
107+
if response.get("error"):
108+
raise AppException(response["error"])
109+
if response["isValidQuery"]:
110+
self._query = response["parsedQuery"]
111+
else:
112+
raise AppException("Incorrect query.")
113+
else:
114+
response = self._backend_client.validate_saqul_query(
115+
self._project.team_id, self._project.id, "-"
116+
)
117+
if response.get("error"):
118+
raise AppException(response["error"])
108119

109120
def execute(self) -> Response:
121+
if not any([self._query, self._subset]):
122+
self._response.errors = AppException(
123+
"AppException: The query and subset params cannot have the value None at the same time."
124+
)
125+
return self._response
126+
110127
if self.is_valid():
128+
query_kwargs = {}
129+
if self._subset:
130+
subset: Optional[SubSetEntity] = None
131+
response = self._backend_client.list_sub_sets(
132+
team_id=self._project.team_id, project_id=self._project.id
133+
)
134+
if response.ok:
135+
subset = next(
136+
(_sub for _sub in response.data if _sub.name == self._subset),
137+
None,
138+
)
139+
if not subset:
140+
self._response.errors = AppException(
141+
"Subset not found. Use the superannotate."
142+
"get_subsets() function to get a list of the available subsets."
143+
)
144+
return self._response
145+
query_kwargs["subset_id"] = subset.id
146+
if self._query:
147+
query_kwargs["query"] = self._query
148+
query_kwargs["folder_id"] = (
149+
None if self._folder.name == "root" else self._folder.uuid
150+
)
111151
service_response = self._backend_client.saqul_query(
112152
self._project.team_id,
113153
self._project.id,
114-
self._query,
115-
folder_id=None if self._folder.name == "root" else self._folder.uuid,
154+
**query_kwargs,
116155
)
117156
if service_response.ok:
118157
data = []

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,7 @@ def execute(self) -> Response:
11101110
team_id=self._project.team_id, project_id=self._project.id
11111111
)
11121112
if sub_sets_response.ok:
1113-
self._response.data = []
1113+
self._response.data = sub_sets_response.data
11141114
else:
11151115
self._response.data = []
11161116

src/superannotate/lib/infrastructure/controller.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1509,15 +1509,18 @@ def attach_integrations(
15091509
)
15101510
return use_case.execute()
15111511

1512-
def query_entities(self, project_name: str, folder_name: str, query: str = None):
1512+
def query_entities(
1513+
self, project_name: str, folder_name: str, query: str = None, subset: str = None
1514+
):
15131515
project = self._get_project(project_name)
15141516
folder = self._get_folder(project, folder_name)
15151517

1516-
use_case = usecases.QueryEntities(
1518+
use_case = usecases.QueryEntitiesUseCase(
15171519
reporter=self.get_default_reporter(),
15181520
project=project,
15191521
folder=folder,
15201522
query=query,
1523+
subset=subset,
15211524
backend_service_provider=self.backend_client,
15221525
)
15231526
return use_case.execute()

src/superannotate/lib/infrastructure/services.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ class SuperannotateBackendService(BaseBackendService):
189189
"""
190190

191191
DEFAULT_CHUNK_SIZE = 5000
192+
SAQUL_CHUNK_SIZE = 50
192193

193194
URL_USERS = "users"
194195
URL_LIST_PROJECTS = "projects"
@@ -1155,9 +1156,14 @@ def attach_integrations(
11551156
return response.ok
11561157

11571158
def saqul_query(
1158-
self, team_id: int, project_id: int, query: str, folder_id: int
1159+
self,
1160+
team_id: int,
1161+
project_id: int,
1162+
folder_id: int,
1163+
query: str = None,
1164+
subset_id: int = None,
11591165
) -> ServiceResponse:
1160-
CHUNK_SIZE = 50
1166+
11611167
query_url = urljoin(self.api_url, self.URL_SAQUL_QUERY)
11621168
params = {
11631169
"team_id": team_id,
@@ -1166,18 +1172,22 @@ def saqul_query(
11661172
}
11671173
if folder_id:
11681174
params["folder_id"] = folder_id
1169-
data = {"query": query, "image_index": 0}
1175+
if subset_id:
1176+
params["subset_id"] = subset_id
1177+
data = {"image_index": 0}
1178+
if query:
1179+
data["query"] = query
11701180
items = []
11711181
for _ in range(self.MAX_ITEMS_COUNT):
11721182
response = self._request(query_url, "post", params=params, data=data)
11731183
if response.ok:
11741184
response_items = response.json()
11751185
items.extend(response_items)
1176-
if len(response_items) < CHUNK_SIZE:
1186+
if len(response_items) < self.SAQUL_CHUNK_SIZE:
11771187
service_response = ServiceResponse(response)
11781188
service_response.data = items
11791189
return service_response
1180-
data["image_index"] += CHUNK_SIZE
1190+
data["image_index"] += self.SAQUL_CHUNK_SIZE
11811191
return ServiceResponse(response)
11821192

11831193
def validate_saqul_query(self, team_id: int, project_id: int, query: str) -> dict:

tests/integration/items/test_saqul_query.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from pathlib import Path
33

44
from src.superannotate import SAClient
5-
sa = SAClient()
65
from tests.integration.base import BaseTestCase
76

7+
sa = SAClient()
8+
89

910
class TestEntitiesSearchVector(BaseTestCase):
1011
PROJECT_NAME = "TestEntitiesSearchVector"
@@ -35,6 +36,16 @@ def test_query(self):
3536
self.assertEqual(len(entities), 1)
3637
assert all([entity["path"] == f"{self.PROJECT_NAME}/{self.FOLDER_NAME}" for entity in entities])
3738

39+
try:
40+
self.assertRaises(
41+
Exception, sa.query(f"{self.PROJECT_NAME}/{self.FOLDER_NAME}", self.TEST_QUERY, subset="something")
42+
)
43+
except Exception as e:
44+
self.assertEqual(
45+
str(e),
46+
"Subset not found. Use the superannotate.get_subsets() function to get a list of the available subsets."
47+
)
48+
3849
def test_validate_saqul_query(self):
3950
try:
4051
self.assertRaises(Exception, sa.query(self.PROJECT_NAME, self.TEST_INVALID_QUERY))

0 commit comments

Comments
 (0)