Skip to content

Commit 5d77c1f

Browse files
authored
Merge pull request #424 from superannotateai/friday_search_items
Added search/get items
2 parents 739a1c9 + 0faffd5 commit 5d77c1f

27 files changed

+605
-584
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,22 @@ _______
7070

7171
----------
7272

73+
Items
74+
______
75+
76+
.. autofunction:: superannotate.query
77+
.. autofunction:: superannotate.search_items
78+
.. autofunction:: superannotate.get_item_metadata
79+
80+
----------
81+
7382
Images
7483
______
7584

7685

7786
.. _ref_search_images:
7887
.. autofunction:: superannotate.search_images
7988
.. autofunction:: superannotate.search_images_all_folders
80-
.. autofunction:: superannotate.query
8189
.. autofunction:: superannotate.get_image_metadata
8290
.. autofunction:: superannotate.download_image
8391
.. autofunction:: superannotate.set_image_annotation_status
@@ -176,6 +184,28 @@ Export metadata example:
176184
}
177185
178186
187+
----------
188+
189+
Item metadata
190+
_______________
191+
192+
Item metadata example:
193+
194+
.. code-block:: python
195+
196+
{
197+
"name": "example.jpeg",
198+
"path": "project/folder_1/meow.jpeg", // <proj>/<folder>/<item name>
199+
"url": "https://sa-public-files.s3.../text_file_example_1.jpeg",
200+
"annotation_status": "NotStarted",
201+
"annotator_name": None,
202+
"qa_name": None,
203+
"entropy_value": None,
204+
"createdAt": "2022-02-15T20:46:44.000Z",
205+
"updatedAt": "2022-02-15T20:46:44.000Z"
206+
}
207+
208+
179209
----------
180210

181211
Image metadata
@@ -189,8 +219,8 @@ Image metadata example:
189219
{
190220
"name": "000000000001.jpg",
191221
"annotation_status": "Completed",
192-
"prediction_status": 1,
193-
"segmentation_status": 1,
222+
"prediction_status": "NotStarted",
223+
"segmentation_status": "NotStarted",
194224
"annotator_id": None,
195225
"annotator_name": None,
196226
"qa_id": None,

src/superannotate/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
from superannotate.lib.app.interface.sdk_interface import get_image_annotations
5959
from superannotate.lib.app.interface.sdk_interface import get_image_metadata
6060
from superannotate.lib.app.interface.sdk_interface import get_integrations
61+
from superannotate.lib.app.interface.sdk_interface import get_item_metadata
6162
from superannotate.lib.app.interface.sdk_interface import (
6263
get_project_and_folder_metadata,
6364
)
@@ -78,6 +79,7 @@
7879
from superannotate.lib.app.interface.sdk_interface import search_folders
7980
from superannotate.lib.app.interface.sdk_interface import search_images
8081
from superannotate.lib.app.interface.sdk_interface import search_images_all_folders
82+
from superannotate.lib.app.interface.sdk_interface import search_items
8183
from superannotate.lib.app.interface.sdk_interface import search_models
8284
from superannotate.lib.app.interface.sdk_interface import search_projects
8385
from superannotate.lib.app.interface.sdk_interface import search_team_contributors
@@ -172,7 +174,9 @@
172174
"search_folders",
173175
"assign_folder",
174176
"unassign_folder",
175-
# Entities Section
177+
# Items Section
178+
"get_item_metadata",
179+
"search_items",
176180
"query",
177181
# Image Section
178182
"copy_images",

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

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from lib.app.interface.types import ProjectTypes
2929
from lib.app.interface.types import validate_arguments
3030
from lib.app.mixp.decorators import Trackable
31-
from lib.app.serializers import BaseSerializers
31+
from lib.app.serializers import BaseSerializer
3232
from lib.app.serializers import ImageSerializer
3333
from lib.app.serializers import ProjectSerializer
3434
from lib.app.serializers import SettingsSerializer
@@ -435,7 +435,7 @@ def search_folders(
435435
raise AppException(response.errors)
436436
data = response.data
437437
if return_metadata:
438-
return [BaseSerializers(folder).serialize() for folder in data]
438+
return [BaseSerializer(folder).serialize() for folder in data]
439439
return [folder.name for folder in data]
440440

441441

@@ -763,7 +763,7 @@ def get_project_metadata(
763763
for elem in "classes", "workflows", "contributors":
764764
if response.get(elem):
765765
metadata[elem] = [
766-
BaseSerializers(attribute).serialize() for attribute in response[elem]
766+
BaseSerializer(attribute).serialize() for attribute in response[elem]
767767
]
768768
else:
769769
metadata[elem] = []
@@ -829,7 +829,7 @@ def search_annotation_classes(
829829
"""
830830
project_name, folder_name = extract_project_folder(project)
831831
classes = Controller.get_default().search_annotation_classes(project_name, name_prefix)
832-
classes = [BaseSerializers(attribute).serialize() for attribute in classes.data]
832+
classes = [BaseSerializer(attribute).serialize() for attribute in classes.data]
833833
return classes
834834

835835

@@ -895,6 +895,11 @@ def get_image_metadata(
895895
:return: metadata of image
896896
:rtype: dict
897897
"""
898+
warning_msg = (
899+
# TODO add link
900+
"We're deprecating the get_image_metadata function. Please use get_item_metadata instead. Learn more. <link>."
901+
)
902+
logger.warning(warning_msg)
898903
project_name, folder_name = extract_project_folder(project)
899904
project = Controller.get_default()._get_project(project_name)
900905
response = Controller.get_default().get_image_metadata(project_name, folder_name, image_name)
@@ -1576,7 +1581,7 @@ def create_annotation_class(
15761581
)
15771582
if response.errors:
15781583
raise AppException(response.errors)
1579-
return BaseSerializers(response.data).serialize()
1584+
return BaseSerializer(response.data).serialize()
15801585

15811586

15821587
@Trackable
@@ -1659,7 +1664,7 @@ def create_annotation_classes_from_classes_json(
16591664
)
16601665
if response.errors:
16611666
raise AppException(response.errors)
1662-
return [BaseSerializers(i).serialize() for i in response.data]
1667+
return [BaseSerializer(i).serialize() for i in response.data]
16631668

16641669

16651670
@Trackable
@@ -2157,7 +2162,7 @@ def download_model(model: MLModel, output_dir: Union[str, Path]):
21572162
if res.errors:
21582163
logger.error("\n".join([str(error) for error in res.errors]))
21592164
else:
2160-
return BaseSerializers(res.data).serialize()
2165+
return BaseSerializer(res.data).serialize()
21612166

21622167

21632168
@Trackable
@@ -2940,7 +2945,7 @@ def get_integrations():
29402945
if response.errors:
29412946
raise AppException(response.errors)
29422947
integrations = response.data
2943-
return BaseSerializers.serialize_iterable(integrations, ("name", "type", "root"))
2948+
return BaseSerializer.serialize_iterable(integrations, ("name", "type", "root"))
29442949

29452950

29462951
@Trackable
@@ -2989,4 +2994,90 @@ def query(project: NotEmptyStr, query: Optional[NotEmptyStr]):
29892994
response = Controller.get_default().query_entities(project_name, folder_name, query)
29902995
if response.errors:
29912996
raise AppException(response.errors)
2992-
return BaseSerializers.serialize_iterable(response.data)
2997+
return BaseSerializer.serialize_iterable(response.data)
2998+
2999+
3000+
@Trackable
3001+
@validate_arguments
3002+
def get_item_metadata(
3003+
project: NotEmptyStr,
3004+
item_name: NotEmptyStr,
3005+
):
3006+
"""Returns item metadata
3007+
3008+
:param project: project name or folder path (e.g., “project1/folder1”)
3009+
:type project: str
3010+
3011+
:param item_name: item name
3012+
:type item_name: str
3013+
3014+
:return: metadata of item
3015+
:rtype: dict
3016+
"""
3017+
project_name, folder_name = extract_project_folder(project)
3018+
response = Controller.get_default().get_item(project_name, folder_name, item_name)
3019+
if response.errors:
3020+
raise AppException(response.errors)
3021+
return BaseSerializer(response.data).serialize()
3022+
3023+
3024+
@Trackable
3025+
@validate_arguments
3026+
def search_items(
3027+
project: NotEmptyStr,
3028+
name_contains: NotEmptyStr = None,
3029+
annotation_status: Optional[AnnotationStatuses] = None,
3030+
annotator_email: Optional[NotEmptyStr] = None,
3031+
qa_email: Optional[NotEmptyStr] = None,
3032+
recursive: bool = False
3033+
3034+
):
3035+
"""Search items by filtering criteria.
3036+
3037+
3038+
:param project: project name or folder path (e.g., “project1/folder1”).
3039+
If recursive=False=True, then only the project name is required.
3040+
:type project: str
3041+
3042+
:param name_contains: Returns those items, where the given string is found anywhere within an item’s name.
3043+
If None, all items returned, in accordance with the recursive=False parameter.
3044+
:type name_contains: str
3045+
3046+
:param annotation_status: if not None, filters items by annotation status.
3047+
Values are:
3048+
“NotStarted”
3049+
“InProgress”
3050+
“QualityCheck”
3051+
“Returned”
3052+
“Completed”
3053+
“Skipped”
3054+
:type annotation_status: str
3055+
3056+
3057+
:param annotator_email: returns those items’ names that are assigned to the specified annotator.
3058+
If None, all items are returned. Strict equal.
3059+
:type annotator_email: str
3060+
3061+
:param qa_email: returns those items’ names that are assigned to the specified QA.
3062+
If None, all items are returned. Strict equal.
3063+
:type qa_email: str
3064+
3065+
:param recursive: search in the project’s root and all of its folders.
3066+
If False search only in the project’s root or given directory.
3067+
:type recursive: bool
3068+
3069+
:return: items metadata
3070+
:rtype: list of dicts
3071+
"""
3072+
project_name, folder_name = extract_project_folder(project)
3073+
response = Controller.get_default().list_items(
3074+
project_name, folder_name,
3075+
name_contains=name_contains,
3076+
annotation_status=annotation_status,
3077+
annotator_email=annotator_email,
3078+
qa_email=qa_email,
3079+
recursive=recursive
3080+
)
3081+
if response.errors:
3082+
raise AppException(response.errors)
3083+
return BaseSerializer.serialize_iterable(response.data)

src/superannotate/lib/app/mixp/decorators.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import functools
22
import sys
3+
from inspect import signature
34

45
from lib import get_default_controller
56
from mixpanel import Mixpanel
@@ -9,19 +10,17 @@
910
from .utils import parsers
1011

1112

12-
# TODO:
13-
try:
14-
if "api.annotate.online" in get_default_controller()._backend_client.api_url:
15-
TOKEN = "ca95ed96f80e8ec3be791e2d3097cf51"
16-
else:
17-
TOKEN = "e741d4863e7e05b1a45833d01865ef0d"
18-
except AttributeError as e:
19-
TOKEN = "e741d4863e7e05b1a45833d01865ef0d"
20-
mp = Mixpanel(TOKEN)
21-
2213
logger = get_default_logger()
2314

2415

16+
def get_mp_instance() -> Mixpanel:
17+
try:
18+
if "api.annotate.online" in get_default_controller()._backend_client.api_url:
19+
return Mixpanel("ca95ed96f80e8ec3be791e2d3097cf51")
20+
finally:
21+
return Mixpanel("e741d4863e7e05b1a45833d01865ef0d")
22+
23+
2524
def get_default(team_name, user_id, project_name=None):
2625
return {
2726
"SDK": True,
@@ -47,6 +46,12 @@ def __init__(self, function, initial=False):
4746
self.track()
4847
functools.update_wrapper(self, function)
4948

49+
@staticmethod
50+
def extract_arguments(function, *args, **kwargs) -> dict:
51+
bound_arguments = signature(function).bind(*args, **kwargs)
52+
bound_arguments.apply_defaults()
53+
return dict(bound_arguments.arguments)
54+
5055
@property
5156
def team(self):
5257
return get_default_controller().get_team()
@@ -58,7 +63,8 @@ def track(self, *args, **kwargs):
5863
Trackable.INITIAL_LOGGED = True
5964
self._success = True
6065
else:
61-
data = getattr(parsers, self.function.__name__)(*args, **kwargs)
66+
arguments = self.extract_arguments(self.function, *args, **kwargs)
67+
data = getattr(parsers, self.function.__name__)(**arguments)
6268
event_name = data["event_name"]
6369
properties = data["properties"]
6470
team_data = self.team.data
@@ -74,7 +80,7 @@ def track(self, *args, **kwargs):
7480
properties = {**default, **properties}
7581

7682
if "pytest" not in sys.modules:
77-
mp.track(user_id, event_name, properties)
83+
get_mp_instance().track(user_id, event_name, properties)
7884
except Exception as _:
7985
pass
8086

src/superannotate/lib/app/mixp/utils/parsers.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1069,7 +1069,6 @@ def search_folders(*args, **kwargs):
10691069

10701070

10711071
def aggregate_annotations_as_df(*args, **kwargs):
1072-
10731072
folder_names = kwargs.get("folder_names", "empty")
10741073
if folder_names == "empty":
10751074
folder_names = args[5:6]
@@ -1265,3 +1264,54 @@ def attach_items_from_integrated_storage(*args, **kwargs):
12651264
"folder_path": bool(folder_path)
12661265
},
12671266
}
1267+
1268+
1269+
def query(**kwargs):
1270+
project = kwargs["project"]
1271+
query_str = kwargs["project"]
1272+
project_name, folder_name = extract_project_folder(project)
1273+
Controller.get_default().get_project_metadata(project_name)
1274+
return {
1275+
"event_name": "attach_items_from_integrated_storage",
1276+
"properties": {
1277+
"project_type": ProjectType.get_name(project.project_type),
1278+
"query": query_str
1279+
1280+
},
1281+
}
1282+
1283+
1284+
def get_item_metadata(**kwargs):
1285+
project = kwargs["project"]
1286+
project_name, _ = extract_project_folder(project)
1287+
Controller.get_default().get_project_metadata(project_name)
1288+
return {
1289+
"event_name": "attach_items_from_integrated_storage",
1290+
"properties": {
1291+
"project_type": ProjectType.get_name(project.project_type),
1292+
},
1293+
}
1294+
1295+
1296+
def search_items(**kwargs):
1297+
project = kwargs["project"]
1298+
name_contains = kwargs["name_contains"]
1299+
annotation_status = kwargs["annotation_status"]
1300+
annotator_email = kwargs["annotator_email"]
1301+
qa_email = kwargs["qa_email"]
1302+
recursive = kwargs["recursive"]
1303+
project_name, folder_name = extract_project_folder(project)
1304+
Controller.get_default().get_project_metadata(project_name)
1305+
return {
1306+
"event_name": "attach_items_from_integrated_storage",
1307+
"properties": {
1308+
"project_type": ProjectType.get_name(project.project_type),
1309+
"query": query,
1310+
"name_contains": len(name_contains) if name_contains else False,
1311+
"annotation_status": annotation_status if annotation_status else False,
1312+
"annotator_email": bool(annotator_email),
1313+
"qa_email": bool(qa_email),
1314+
"recursive": bool(recursive),
1315+
1316+
},
1317+
}

0 commit comments

Comments
 (0)