Skip to content

Commit 62613d8

Browse files
committed
Added create/get/delete cusotm fields
1 parent 7da46f8 commit 62613d8

File tree

8 files changed

+401
-1
lines changed

8 files changed

+401
-1
lines changed

docs/source/superannotate.sdk.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ ______
7979

8080
----------
8181

82+
Custom Metadata
83+
______
84+
85+
.. automethod:: superannotate.SAClient.create_custom_fields
86+
.. automethod:: superannotate.SAClient.get_custom_fields
87+
.. automethod:: superannotate.SAClient.delete_custom_fields
88+
89+
----------
90+
8291
Subsets
8392
______
8493

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

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import json
44
import os
55
import tempfile
6-
import warnings
76
from pathlib import Path
87
from typing import Callable
98
from typing import Iterable
@@ -2634,3 +2633,177 @@ def get_subsets(self, project: Union[NotEmptyStr, dict]):
26342633
if response.errors:
26352634
raise AppException(response.errors)
26362635
return BaseSerializer.serialize_iterable(response.data, ["name"])
2636+
2637+
def create_custom_fields(self, project: NotEmptyStr, fields: dict):
2638+
"""Create custom fields for items in a project in addition to built-in metadata.
2639+
Using this function again with a different schema won't override the existing fields, but add new ones.
2640+
Use the upload_custom_values() function to fill them with values for each item.
2641+
2642+
:param project: project name (e.g., “project1”)
2643+
:type project: str
2644+
2645+
:param fields: dictionary describing the fields and their specifications added to the project.
2646+
You can see the schema structure <here>.
2647+
:type fields: dict
2648+
2649+
:return: custom fields actual schema of the project
2650+
:rtype: dict
2651+
2652+
Supported Types:
2653+
2654+
============== ======================
2655+
number
2656+
--------------------------------------
2657+
field spec spec value
2658+
============== ======================
2659+
minimum any number (int or float)
2660+
maximum any number (int or float)
2661+
enum list of numbers (int or float)
2662+
============== ======================
2663+
2664+
============== ======================
2665+
string
2666+
--------------------------------------
2667+
field spec spec value
2668+
============== ======================
2669+
format “email” or “date”
2670+
enum list of strings
2671+
============== ======================
2672+
::
2673+
2674+
custom_fields = {
2675+
"study_date": {
2676+
"type": "string",
2677+
"format": "date"
2678+
},
2679+
"patient_id": {
2680+
"type": "string"
2681+
},
2682+
"patient_sex": {
2683+
"type": "string",
2684+
"enum": [
2685+
"male", "female"
2686+
]
2687+
},
2688+
"patient_age": {
2689+
"type": "number"
2690+
},
2691+
"medical_specialist": {
2692+
"type": "string",
2693+
"format": "email"
2694+
},
2695+
"duration": {
2696+
"type": "number",
2697+
"minimum": 10
2698+
}
2699+
}
2700+
2701+
client = SAClient()
2702+
client.create_custom_fields(
2703+
project="Medical Annotations",
2704+
fields=custom_fields
2705+
)
2706+
2707+
"""
2708+
project_name, _ = extract_project_folder(project)
2709+
response = self.controller.create_custom_schema(
2710+
project_name=project, schema=fields
2711+
)
2712+
if response.errors:
2713+
raise AppException(response.errors)
2714+
return response.data
2715+
2716+
def get_custom_fields(self, project: NotEmptyStr):
2717+
"""Get the schema of the custom fields defined for the project
2718+
2719+
:param project: project name (e.g., “project1”)
2720+
:type project: str
2721+
2722+
:return: custom fields actual schema of the project
2723+
:rtype: dict
2724+
2725+
Response Example:
2726+
::
2727+
{
2728+
"study_date": {
2729+
"type": "string",
2730+
"format": "date"
2731+
},
2732+
"patient_id": {
2733+
"type": "string"
2734+
},
2735+
"patient_sex": {
2736+
"type": "string",
2737+
"enum": [
2738+
"male", "female"
2739+
]
2740+
},
2741+
"patient_age": {
2742+
"type": "number"
2743+
},
2744+
"medical_specialist": {
2745+
"type": "string",
2746+
"format": "email"
2747+
},
2748+
"duration": {
2749+
"type": "number",
2750+
"minimum": 10
2751+
}
2752+
}
2753+
"""
2754+
project_name, _ = extract_project_folder(project)
2755+
response = self.controller.get_custom_schema(project_name=project)
2756+
if response.errors:
2757+
raise AppException(response.errors)
2758+
return response.data
2759+
2760+
def delete_custom_fields(self, project: NotEmptyStr, fields: list):
2761+
"""Remove custom fields from a project’s custom metadata schema.
2762+
2763+
:param project: project name (e.g., “project1”)
2764+
:type project: str
2765+
2766+
:param fields: list of field names to remove
2767+
:type fields: list of strs
2768+
2769+
:return: custom fields actual schema of the project
2770+
:rtype: dict
2771+
2772+
Request Example:
2773+
::
2774+
client = SAClient()
2775+
client.delete_custom_fields(
2776+
project = "Medical Annotations",
2777+
fields = ["duration", patient_age]
2778+
)
2779+
2780+
Response Example:
2781+
::
2782+
{
2783+
"study_date": {
2784+
"type": "string",
2785+
"format": "date"
2786+
},
2787+
"patient_id": {
2788+
"type": "string"
2789+
},
2790+
"patient_sex": {
2791+
"type": "string",
2792+
"enum": [
2793+
"male", "female"
2794+
]
2795+
},
2796+
"medical_specialist": {
2797+
"type": "string",
2798+
"format": "email"
2799+
}
2800+
}
2801+
2802+
"""
2803+
project_name, _ = extract_project_folder(project)
2804+
response = self.controller.delete_custom_schema(
2805+
project_name=project, fields=fields
2806+
)
2807+
if response.errors:
2808+
raise AppException(response.errors)
2809+
return response.data

src/superannotate/lib/core/serviceproviders.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,16 @@ def validate_saqul_query(self, team_id: int, project_id: int, query: str) -> dic
357357

358358
def list_sub_sets(self, team_id: int, project_id: int) -> ServiceResponse:
359359
raise NotImplementedError
360+
361+
def create_custom_schema(
362+
self, team_id: int, project_id: int, schema: dict
363+
) -> ServiceResponse:
364+
raise NotImplementedError
365+
366+
def get_custom_schema(self, team_id: int, project_id: int) -> ServiceResponse:
367+
raise NotImplementedError
368+
369+
def delete_custom_schema(
370+
self, team_id: int, project_id: int, fields: List[str]
371+
) -> ServiceResponse:
372+
raise NotImplementedError

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from lib.core.usecases.annotations import * # noqa: F403 F401
2+
from lib.core.usecases.custom_fields import * # noqa: F403 F401
23
from lib.core.usecases.folders import * # noqa: F403 F401
34
from lib.core.usecases.images import * # noqa: F403 F401
45
from lib.core.usecases.integrations import * # noqa: F403 F401
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from typing import List
2+
3+
from lib.core.entities import ProjectEntity
4+
from lib.core.reporter import Reporter
5+
from lib.core.response import Response
6+
from lib.core.serviceproviders import SuperannotateServiceProvider
7+
from lib.core.usecases import BaseReportableUseCase
8+
9+
10+
class CreateCustomSchemaUseCase(BaseReportableUseCase):
11+
def __init__(
12+
self,
13+
reporter: Reporter,
14+
project: ProjectEntity,
15+
schema: dict,
16+
backend_client: SuperannotateServiceProvider,
17+
):
18+
super().__init__(reporter)
19+
self._project = project
20+
self._schema = schema
21+
self._backend_client = backend_client
22+
23+
def execute(self) -> Response:
24+
response = self._backend_client.create_custom_schema(
25+
team_id=self._project.team_id,
26+
project_id=self._project.id,
27+
schema=self._schema,
28+
)
29+
if response.ok:
30+
self._response.data = response.data
31+
else:
32+
self._response.errors = response.error
33+
return self._response
34+
35+
36+
class GetCustomSchemaUseCase(BaseReportableUseCase):
37+
def __init__(
38+
self,
39+
reporter: Reporter,
40+
project: ProjectEntity,
41+
backend_client: SuperannotateServiceProvider,
42+
):
43+
super().__init__(reporter)
44+
self._project = project
45+
self._backend_client = backend_client
46+
47+
def execute(self) -> Response:
48+
response = self._backend_client.get_custom_schema(
49+
team_id=self._project.team_id,
50+
project_id=self._project.id,
51+
)
52+
if response.ok:
53+
self._response.data = response.data
54+
else:
55+
self._response.errors = response.error
56+
return self._response
57+
58+
59+
class DeleteCustomSchemaUseCase(BaseReportableUseCase):
60+
def __init__(
61+
self,
62+
reporter: Reporter,
63+
project: ProjectEntity,
64+
fields: List[str],
65+
backend_client: SuperannotateServiceProvider,
66+
):
67+
super().__init__(reporter)
68+
self._project = project
69+
self._fields = fields
70+
self._backend_client = backend_client
71+
72+
def execute(self) -> Response:
73+
response = self._backend_client.delete_custom_schema(
74+
team_id=self._project.team_id,
75+
project_id=self._project.id,
76+
fields=self._fields,
77+
)
78+
if response.ok:
79+
self._response.data = response.data
80+
else:
81+
self._response.errors = response.error
82+
return self._response

src/superannotate/lib/infrastructure/controller.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1658,3 +1658,33 @@ def list_subsets(self, project_name: str):
16581658
backend_client=self.backend_client,
16591659
)
16601660
return use_case.execute()
1661+
1662+
def create_custom_schema(self, project_name: str, schema: dict):
1663+
project = self._get_project(project_name)
1664+
1665+
use_case = usecases.CreateCustomSchemaUseCase(
1666+
reporter=self.get_default_reporter(),
1667+
project=project,
1668+
schema=schema,
1669+
backend_client=self.backend_client,
1670+
)
1671+
return use_case.execute()
1672+
1673+
def get_custom_schema(self, project_name: str):
1674+
project = self._get_project(project_name)
1675+
use_case = usecases.GetCustomSchemaUseCase(
1676+
reporter=self.get_default_reporter(),
1677+
project=project,
1678+
backend_client=self.backend_client,
1679+
)
1680+
return use_case.execute()
1681+
1682+
def delete_custom_schema(self, project_name: str, fields: List[str]):
1683+
project = self._get_project(project_name)
1684+
use_case = usecases.DeleteCustomSchemaUseCase(
1685+
reporter=self.get_default_reporter(),
1686+
project=project,
1687+
fields=fields,
1688+
backend_client=self.backend_client,
1689+
)
1690+
return use_case.execute()

src/superannotate/lib/infrastructure/services.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ class SuperannotateBackendService(BaseBackendService):
244244
URL_SAQUL_QUERY = "/images/search/advanced"
245245
URL_VALIDATE_SAQUL_QUERY = "/images/parse/query/advanced"
246246
URL_LIST_SUBSETS = "/project/{project_id}/subset"
247+
URL_CREATE_CUSTOM_SCHEMA = "/project/{project_id}/custom/metadata/schema"
248+
URL_GET_CUSTOM_SCHEMA = "/project/{project_id}/custom/metadata/schema"
247249

248250
def upload_priority_scores(
249251
self, team_id: int, project_id: int, folder_id: int, priorities: list
@@ -1191,3 +1193,42 @@ def list_sub_sets(self, team_id: int, project_id: int) -> ServiceResponse:
11911193
params=dict(team_id=team_id),
11921194
content_type=List[entities.SubSetEntity],
11931195
)
1196+
1197+
def create_custom_schema(
1198+
self, team_id: int, project_id: int, schema: dict
1199+
) -> ServiceResponse:
1200+
return self._request(
1201+
urljoin(
1202+
self.api_url,
1203+
self.URL_CREATE_CUSTOM_SCHEMA.format(project_id=project_id),
1204+
),
1205+
"post",
1206+
params=dict(team_id=team_id),
1207+
data=dict(data=schema),
1208+
content_type=ServiceResponse,
1209+
)
1210+
1211+
def get_custom_schema(self, team_id: int, project_id: int) -> ServiceResponse:
1212+
return self._request(
1213+
urljoin(
1214+
self.api_url,
1215+
self.URL_CREATE_CUSTOM_SCHEMA.format(project_id=project_id),
1216+
),
1217+
"get",
1218+
params=dict(team_id=team_id),
1219+
content_type=ServiceResponse,
1220+
)
1221+
1222+
def delete_custom_schema(
1223+
self, team_id: int, project_id: int, fields: List[str]
1224+
) -> ServiceResponse:
1225+
return self._request(
1226+
urljoin(
1227+
self.api_url,
1228+
self.URL_CREATE_CUSTOM_SCHEMA.format(project_id=project_id),
1229+
),
1230+
"delete",
1231+
params=dict(team_id=team_id),
1232+
data=dict(custom_fields=fields),
1233+
content_type=ServiceResponse,
1234+
)

0 commit comments

Comments
 (0)