Skip to content

Commit 96b1bb1

Browse files
committed
added user custom fields support
1 parent 1476262 commit 96b1bb1

File tree

19 files changed

+1051
-359
lines changed

19 files changed

+1051
-359
lines changed

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

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from lib.core.entities.integrations import IntegrationEntity
5656
from lib.core.entities.integrations import IntegrationTypeEnum
5757
from lib.core.enums import ImageQuality
58+
from lib.core.enums import CustomFieldEntityEnum
5859
from lib.core.enums import ProjectType
5960
from lib.core.enums import ClassTypeEnum
6061
from lib.core.exceptions import AppException
@@ -280,10 +281,10 @@ def get_team_metadata(self):
280281
return TeamSerializer(response.data).serialize()
281282

282283
def get_user_metadata(
283-
self, pk: Union[str, int], include: List[Literal["custom_fields"]] = None
284+
self, pk: Union[int, str], include: List[Literal["custom_fields"]] = None
284285
):
285286
"""
286-
Returns team contributor metadata
287+
Returns user metadata
287288
288289
:param pk: The email address or ID of the team contributor.
289290
:type pk: str or int
@@ -299,8 +300,58 @@ def get_user_metadata(
299300
:return: metadata of team contributor
300301
:rtype: dict
301302
"""
302-
response = self.controller.get_user_metadata()
303-
return TeamSerializer(response.data).serialize()
303+
user = self.controller.work_management.get_user_metadata(pk=pk, include=include)
304+
return BaseSerializer(user).serialize(by_alias=False)
305+
306+
def set_user_custom_field(
307+
self, pk: Union[int, str], custom_field_name: str, value: Any
308+
):
309+
"""
310+
Set the custom field for team contributors.
311+
312+
:param pk: The email address or ID of the team contributor.
313+
:type pk: str or int
314+
315+
:param custom_field_name: the name of the custom field created for the team contributor,
316+
used to set or update its value.
317+
318+
:type custom_field_name: str
319+
320+
:param value: The value
321+
:type value: Any
322+
323+
Request Example:
324+
::
325+
from superannotate import SAClient
326+
327+
328+
sa = SAClient()
329+
330+
client.set_user_custom_field(
331+
"example@email.com",
332+
custom_field_name="Due date",
333+
value="Dec 20, 2024"
334+
)
335+
"""
336+
user_id = self.controller.work_management.get_user_metadata(pk=pk).id
337+
self.controller.work_management.set_custom_field_value(
338+
entity_id=user_id,
339+
field_name=custom_field_name,
340+
value=value,
341+
entity=CustomFieldEntityEnum.CONTRIBUTOR,
342+
parent_entity=CustomFieldEntityEnum.TEAM,
343+
)
344+
345+
def list_users(self, *, include: List[Literal["custom_fields"]] = None, **filters):
346+
"""
347+
348+
@param include:
349+
@param filters:
350+
@return:
351+
"""
352+
return BaseSerializer.serialize_iterable(
353+
self.controller.work_management.list_users(include=include, **filters)
354+
)
304355

305356
def get_component_config(self, project: Union[NotEmptyStr, int], component_id: str):
306357
"""

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from lib.core.entities.project import AttachmentEntity
1515
from lib.core.entities.project import CategoryEntity
1616
from lib.core.entities.project import ContributorEntity
17+
from lib.core.entities.project import CustomFieldEntity
1718
from lib.core.entities.project import ProjectEntity
1819
from lib.core.entities.project import SettingEntity
1920
from lib.core.entities.project import StepEntity
@@ -28,6 +29,7 @@
2829
"ConfigEntity",
2930
"SettingEntity",
3031
"SubSetEntity",
32+
"CustomFieldEntity",
3133
# items
3234
"BaseEntity",
3335
"ImageEntity",

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,19 @@ class ProjectFilters(BaseFilters):
3737
status__ne: Literal["NotStarted", "InProgress", "Completed", "OnHold"]
3838
status__in: List[Literal["NotStarted", "InProgress", "Completed", "OnHold"]]
3939
status__notin: List[Literal["NotStarted", "InProgress", "Completed", "OnHold"]]
40+
custom_field: Optional[str] # dummy field to pass first level validation
41+
42+
43+
class UserFilters(TypedDict, total=False):
44+
id: Optional[int]
45+
id__in: Optional[List[int]]
46+
email: Optional[str]
47+
email__in: Optional[List[str]]
48+
email__contains: Optional[str]
49+
email__starts: Optional[str]
50+
email__ends: Optional[str]
51+
state: Optional[str]
52+
state__in: Optional[List[str]]
53+
role: Optional[str]
54+
role__in: Optional[List[str]]
55+
custom_field: Optional[str] # dummy field to pass first level validation

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,13 @@ class Config:
170170
extra = Extra.ignore
171171

172172

173+
class CustomFieldEntity(BaseModel):
174+
...
175+
176+
class Config:
177+
extra = Extra.allow
178+
179+
173180
class WorkflowEntity(BaseModel):
174181
id: Optional[int]
175182
name: Optional[str]

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

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
from enum import auto
23
from enum import Enum
34
from typing import Optional
45

@@ -9,14 +10,6 @@
910
from lib.core.pydantic_v1 import validator
1011

1112

12-
class ProjectCustomFieldType(Enum):
13-
Text = 1
14-
MULTI_SELECT = 2
15-
SINGLE_SELECT = 3
16-
DATE_PICKER = 4
17-
NUMERIC = 5
18-
19-
2013
class ProjectType(str, Enum):
2114
Vector = "VECTOR"
2215
Pixel = "PIXEL"
@@ -28,6 +21,25 @@ class ProjectType(str, Enum):
2821
Multimodal = "CUSTOM_LLM"
2922

3023

24+
class WMUserStateEnum(str, Enum):
25+
Pending = "PENDING"
26+
Confirmed = "CONFIRMED"
27+
Video = "PUBLIC_VIDEO"
28+
Document = "PUBLIC_TEXT"
29+
Tiled = "TILED"
30+
Other = "CLASSIFICATION"
31+
PointCloud = "POINT_CLOUD"
32+
Multimodal = "CUSTOM_LLM"
33+
34+
35+
class WMUserTypeEnum(int, Enum):
36+
Contributor = 4
37+
TeamAdmin = 7
38+
TeamOwner = 12
39+
OrganizationAdmin = 15
40+
other = auto()
41+
42+
3143
class ProjectStatus(str, Enum):
3244
Undefined = "undefined"
3345
NotStarted = "notStarted"
@@ -89,12 +101,31 @@ def json(self, **kwargs):
89101
return super().json(**kwargs)
90102

91103

92-
class UserEntity(TimedBaseModel):
93-
id: Optional[str]
94-
first_name: Optional[str]
95-
last_name: Optional[str]
104+
class WMUserEntity(TimedBaseModel):
105+
id: Optional[int]
106+
team_id: Optional[int]
107+
role: WMUserTypeEnum
96108
email: Optional[str]
97-
user_role: Optional[int]
109+
state: Optional[WMUserStateEnum]
110+
custom_fields: Optional[dict] = Field(dict(), alias="customField")
98111

99112
class Config:
100113
extra = Extra.ignore
114+
use_enum_names = True
115+
116+
json_encoders = {
117+
Enum: lambda v: v.value,
118+
datetime.date: lambda v: v.isoformat(),
119+
datetime.datetime: lambda v: v.isoformat(),
120+
}
121+
122+
@validator("custom_fields")
123+
def custom_fields_transformer(cls, v):
124+
if v and "custom_field_values" in v:
125+
return v.get("custom_field_values", {})
126+
return {}
127+
128+
def json(self, **kwargs):
129+
if "exclude" not in kwargs:
130+
kwargs["exclude"] = {"custom_fields"}
131+
return super().json(**kwargs)

src/superannotate/lib/core/enums.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,17 @@ class TrainingStatus(BaseTitledEnum):
185185
FAILED_BEFORE_EVALUATION = "FailedBeforeEvaluation", 4
186186
FAILED_AFTER_EVALUATION = "FailedAfterEvaluation", 5
187187
FAILED_AFTER_EVALUATION_WITH_SAVE_MODEL = "FailedAfterEvaluationWithSavedModel", 6
188+
189+
190+
class CustomFieldEntityEnum(str, Enum):
191+
CONTRIBUTOR = "Contributor"
192+
TEAM = "Team"
193+
PROJECT = "Project"
194+
195+
196+
class CustomFieldType(Enum):
197+
Text = 1
198+
MULTI_SELECT = 2
199+
SINGLE_SELECT = 3
200+
DATE_PICKER = 4
201+
NUMERIC = 5

src/superannotate/lib/core/service_types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ class WMUserListResponse(ServiceResponse):
252252
res_data: List[WMUserEntity] = None
253253

254254

255+
class WMCustomFieldResponse(ServiceResponse):
256+
res_data: List[entities.CustomFieldEntity] = None
257+
258+
255259
class SettingsListResponse(ServiceResponse):
256260
res_data: List[entities.SettingEntity] = None
257261

src/superannotate/lib/core/serviceproviders.py

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
from typing import Dict
77
from typing import List
88
from typing import Literal
9+
from typing import Optional
910

1011
from lib.core import entities
1112
from lib.core.conditions import Condition
13+
from lib.core.enums import CustomFieldEntityEnum
1214
from lib.core.jsx_conditions import Query
1315
from lib.core.reporter import Reporter
1416
from lib.core.service_types import AnnotationClassListResponse
@@ -25,7 +27,9 @@
2527
from lib.core.service_types import UploadAnnotationsResponse
2628
from lib.core.service_types import UserLimitsResponse
2729
from lib.core.service_types import UserResponse
30+
from lib.core.service_types import WMCustomFieldResponse
2831
from lib.core.service_types import WMProjectListResponse
32+
from lib.core.service_types import WMUserListResponse
2933
from lib.core.service_types import WorkflowListResponse
3034
from lib.core.types import Attachment
3135
from lib.core.types import AttachmentMeta
@@ -103,15 +107,17 @@ def list_workflow_roles(self, project_id: int, workflow_id: int):
103107
raise NotImplementedError
104108

105109
@abstractmethod
106-
def list_project_custom_field_templates(self):
110+
def list_custom_field_templates(
111+
self,
112+
entity: CustomFieldEntityEnum,
113+
parent_entity: CustomFieldEntityEnum,
114+
context: dict = None,
115+
):
107116
raise NotImplementedError
108117

109118
def create_project_custom_field_template(self, data: dict):
110119
raise NotImplementedError
111120

112-
def delete_project_custom_field_template(self, pk: int):
113-
raise NotImplementedError
114-
115121
@abstractmethod
116122
def list_project_custom_entities(self, project_id: int):
117123
raise NotImplementedError
@@ -136,6 +142,47 @@ def create_project_categories(
136142
) -> ServiceResponse:
137143
raise NotImplementedError
138144

145+
@abstractmethod
146+
def list_users(
147+
self, body_query: Query, chunk_size=100, include_custom_fields=False
148+
) -> WMUserListResponse:
149+
raise NotImplementedError
150+
151+
@abstractmethod
152+
def create_custom_field_template(
153+
self,
154+
name: str,
155+
component_id: int,
156+
entity: CustomFieldEntityEnum,
157+
parent_entity: CustomFieldEntityEnum,
158+
component_payload: Optional[dict] = None,
159+
access: Optional[dict] = None,
160+
entity_context: Optional[dict] = None,
161+
) -> WMCustomFieldResponse:
162+
raise NotImplementedError
163+
164+
@abstractmethod
165+
def delete_custom_field_template(
166+
self,
167+
pk: int,
168+
entity: CustomFieldEntityEnum,
169+
parent_entity: CustomFieldEntityEnum,
170+
entity_context: Optional[dict] = None,
171+
):
172+
raise NotImplementedError
173+
174+
@abstractmethod
175+
def set_custom_field_value(
176+
self,
177+
entity_id: int,
178+
template_id: int,
179+
data: dict,
180+
entity: CustomFieldEntityEnum,
181+
parent_entity: CustomFieldEntityEnum,
182+
context: Optional[dict] = None,
183+
):
184+
raise NotImplementedError
185+
139186

140187
class BaseProjectService(SuperannotateServiceProvider):
141188
@abstractmethod
@@ -738,17 +785,23 @@ def invite_contributors(
738785
raise NotImplementedError
739786

740787
@abstractmethod
741-
def list_project_custom_field_names(self) -> List[str]:
788+
def list_custom_field_names(self, entity: CustomFieldEntityEnum) -> List[str]:
742789
raise NotImplementedError
743790

744791
@abstractmethod
745-
def get_project_custom_field_id(self, field_name: str) -> int:
792+
def get_custom_field_id(
793+
self, field_name: str, entity: CustomFieldEntityEnum
794+
) -> int:
746795
raise NotImplementedError
747796

748797
@abstractmethod
749-
def get_project_custom_field_name(self, field_id: int) -> str:
798+
def get_custom_field_name(
799+
self, field_id: int, entity: CustomFieldEntityEnum
800+
) -> str:
750801
raise NotImplementedError
751802

752803
@abstractmethod
753-
def get_project_custom_field_component_id(self, field_id: int) -> str:
804+
def get_custom_field_component_id(
805+
self, field_id: int, entity: CustomFieldEntityEnum
806+
) -> str:
754807
raise NotImplementedError

0 commit comments

Comments
 (0)