Skip to content

Commit b0b77e5

Browse files
Narek MkhitaryanNarek Mkhitaryan
authored andcommitted
added list_projects method
1 parent 4508e1e commit b0b77e5

File tree

14 files changed

+667
-313
lines changed

14 files changed

+667
-313
lines changed

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
from lib.infrastructure.annotation_adapter import MultimodalLargeAnnotationAdapter
7070
from lib.infrastructure.utils import extract_project_folder
7171
from lib.infrastructure.validators import wrap_error
72+
from lib.app.serializers import WMProjectSerializer
7273

7374
logger = logging.getLogger("sa")
7475

@@ -2967,6 +2968,62 @@ def list_items(
29672968
exclude.add("custom_metadata")
29682969
return BaseSerializer.serialize_iterable(res, exclude=exclude)
29692970

2971+
def list_projects(
2972+
self,
2973+
*,
2974+
include: List[Literal["custom_fields"]] = None,
2975+
**filters,
2976+
):
2977+
# TODO finalize doc
2978+
"""
2979+
Search projects by filtering criteria.
2980+
2981+
:param include: Specifies additional fields to include in the response.
2982+
2983+
Possible values are
2984+
2985+
- "custom_fields": Includes custom field added to the project.
2986+
:type include: list of str, optional
2987+
2988+
:param filters: Specifies filtering criteria (e.g., name, ID, status), with all conditions combined using
2989+
logical AND. Only projects matching all criteria are returned. If no operation is specified,
2990+
an exact match is applied.
2991+
2992+
2993+
Supported operations:
2994+
- __ne: Value is not equal.
2995+
- __in: Value is in the list.
2996+
- __notin: Value is not in the list.
2997+
- __contains: Value has the substring.
2998+
- __starts: Value starts with the prefix.
2999+
- __ends: Value ends with the suffix.
3000+
3001+
Filter params::
3002+
3003+
- id: int
3004+
- id__in: list[int]
3005+
- name: str
3006+
- name__in: list[str]
3007+
- name__contains: str
3008+
- name__starts: str
3009+
- name__ends: str
3010+
- status: Literal[“NotStarted”, “InProgress”, “Completed”, “OnHold”]
3011+
- status__ne: Literal[“NotStarted”, “InProgress”, “Completed”, “OnHold”]
3012+
- status__in: List[Literal[“NotStarted”, “InProgress”, “Completed”, “OnHold”]]
3013+
- status__notin: List[Literal[“NotStarted”, “InProgress”, “Completed”, “OnHold”]]
3014+
- custom_field: Optional[dict] – Specifies custom fields attributes to filter projects by.
3015+
Custom fields can be accessed using the `custom_field__` prefix followed by the attribute name.
3016+
3017+
:type filters: ProjectFilters
3018+
3019+
:return: A list of project metadata that matches the filtering criteria.
3020+
:rtype: list of dicts
3021+
"""
3022+
return [
3023+
WMProjectSerializer(p).serialize()
3024+
for p in self.controller.projects.list_projects(include=include, **filters)
3025+
]
3026+
29703027
def attach_items(
29713028
self,
29723029
project: Union[NotEmptyStr, dict],

src/superannotate/lib/app/serializers.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from abc import ABC
2+
from enum import Enum
23
from typing import Any
34
from typing import List
45
from typing import Set
56
from typing import Union
67

78
import lib.core as constance
89
from lib.core.entities import BaseEntity
9-
from lib.core.enums import BaseTitledEnum
1010
from lib.core.pydantic_v1 import BaseModel
1111

1212

@@ -18,7 +18,7 @@ def __init__(self, entity: BaseEntity):
1818
def _fill_enum_values(data: dict):
1919
if isinstance(data, dict):
2020
for key, value in data.items():
21-
if isinstance(value, BaseTitledEnum):
21+
if isinstance(value, Enum):
2222
data[key] = value.name
2323
return data
2424

@@ -133,6 +133,26 @@ def serialize(
133133
return data
134134

135135

136+
class WMProjectSerializer(BaseSerializer):
137+
def serialize(
138+
self,
139+
fields: List[str] = None,
140+
by_alias: bool = False,
141+
flat: bool = False,
142+
exclude: Set[str] = None,
143+
exclude_unset=False,
144+
):
145+
146+
to_exclude = {"sync_status": True, "unverified_users": True, "classes": True}
147+
if exclude:
148+
for field in exclude:
149+
to_exclude[field] = True
150+
data = super().serialize(fields, by_alias, flat, to_exclude)
151+
if not data.get("status"):
152+
data["status"] = "Undefined"
153+
return data
154+
155+
136156
class FolderSerializer(BaseSerializer):
137157
def serialize(
138158
self,

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

Lines changed: 0 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -25,202 +25,6 @@
2525
_missing = object()
2626

2727

28-
class BaseModel(BaseModel):
29-
...
30-
# """
31-
# Added new extra keys
32-
# - use_enum_names: that's for BaseTitledEnum to use names instead of enum objects
33-
# """
34-
#
35-
# def _iter(
36-
# self,
37-
# to_dict: bool = False,
38-
# by_alias: bool = False,
39-
# include: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None,
40-
# exclude: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None,
41-
# exclude_unset: bool = False,
42-
# exclude_defaults: bool = False,
43-
# exclude_none: bool = False,
44-
# ) -> "TupleGenerator": # noqa
45-
#
46-
# # Merge field set excludes with explicit exclude parameter with explicit overriding field set options.
47-
# # The extra "is not None" guards are not logically necessary but optimizes performance for the simple case.
48-
# if exclude is not None or self.__exclude_fields__ is not None:
49-
# exclude = ValueItems.merge(self.__exclude_fields__, exclude)
50-
#
51-
# if include is not None or self.__include_fields__ is not None:
52-
# include = ValueItems.merge(self.__include_fields__, include, intersect=True)
53-
#
54-
# allowed_keys = self._calculate_keys(
55-
# include=include, exclude=exclude, exclude_unset=exclude_unset # type: ignore
56-
# )
57-
# if allowed_keys is None and not (
58-
# by_alias or exclude_unset or exclude_defaults or exclude_none
59-
# ):
60-
# # huge boost for plain _iter()
61-
# yield from self.__dict__.items()
62-
# return
63-
#
64-
# value_exclude = ValueItems(self, exclude) if exclude is not None else None
65-
# value_include = ValueItems(self, include) if include is not None else None
66-
#
67-
# for field_key, v in self.__dict__.items():
68-
# if (allowed_keys is not None and field_key not in allowed_keys) or (
69-
# exclude_none and v is None
70-
# ):
71-
# continue
72-
#
73-
# if exclude_defaults:
74-
# model_field = self.__fields__.get(field_key)
75-
# if (
76-
# not getattr(model_field, "required", True)
77-
# and getattr(model_field, "default", _missing) == v
78-
# ):
79-
# continue
80-
#
81-
# if by_alias and field_key in self.__fields__:
82-
# dict_key = self.__fields__[field_key].alias
83-
# else:
84-
# dict_key = field_key
85-
#
86-
# # if to_dict or value_include or value_exclude:
87-
# v = self._get_value(
88-
# v,
89-
# to_dict=to_dict,
90-
# by_alias=by_alias,
91-
# include=value_include and value_include.for_element(field_key),
92-
# exclude=value_exclude and value_exclude.for_element(field_key),
93-
# exclude_unset=exclude_unset,
94-
# exclude_defaults=exclude_defaults,
95-
# exclude_none=exclude_none,
96-
# )
97-
# yield dict_key, v
98-
#
99-
# @classmethod
100-
# @no_type_check
101-
# def _get_value(
102-
# cls,
103-
# v: Any,
104-
# to_dict: bool,
105-
# by_alias: bool,
106-
# include: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]],
107-
# exclude: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]],
108-
# exclude_unset: bool,
109-
# exclude_defaults: bool,
110-
# exclude_none: bool,
111-
# ) -> Any:
112-
#
113-
# if isinstance(v, BaseModel):
114-
# v_dict = v.dict(
115-
# by_alias=by_alias,
116-
# exclude_unset=exclude_unset,
117-
# exclude_defaults=exclude_defaults,
118-
# include=include,
119-
# exclude=exclude,
120-
# exclude_none=exclude_none,
121-
# )
122-
# if ROOT_KEY in v_dict:
123-
# return v_dict[ROOT_KEY]
124-
# return v_dict
125-
# value_exclude = ValueItems(v, exclude) if exclude else None
126-
# value_include = ValueItems(v, include) if include else None
127-
#
128-
# if isinstance(v, dict):
129-
# return {
130-
# k_: cls._get_value(
131-
# v_,
132-
# to_dict=to_dict,
133-
# by_alias=by_alias,
134-
# exclude_unset=exclude_unset,
135-
# exclude_defaults=exclude_defaults,
136-
# include=value_include and value_include.for_element(k_),
137-
# exclude=value_exclude and value_exclude.for_element(k_),
138-
# exclude_none=exclude_none,
139-
# )
140-
# for k_, v_ in v.items()
141-
# if (not value_exclude or not value_exclude.is_excluded(k_))
142-
# and (not value_include or value_include.is_included(k_))
143-
# }
144-
#
145-
# elif sequence_like(v):
146-
# seq_args = (
147-
# cls._get_value(
148-
# v_,
149-
# to_dict=to_dict,
150-
# by_alias=by_alias,
151-
# exclude_unset=exclude_unset,
152-
# exclude_defaults=exclude_defaults,
153-
# include=value_include and value_include.for_element(i),
154-
# exclude=value_exclude and value_exclude.for_element(i),
155-
# exclude_none=exclude_none,
156-
# )
157-
# for i, v_ in enumerate(v)
158-
# if (not value_exclude or not value_exclude.is_excluded(i))
159-
# and (not value_include or value_include.is_included(i))
160-
# )
161-
#
162-
# return (
163-
# v.__class__(*seq_args)
164-
# if is_namedtuple(v.__class__)
165-
# else v.__class__(seq_args)
166-
# )
167-
# elif (
168-
# isinstance(v, BaseTitledEnum)
169-
# and getattr(cls.Config, "use_enum_names", False)
170-
# and to_dict
171-
# ):
172-
# return v.name
173-
# elif isinstance(v, Enum) and getattr(cls.Config, "use_enum_values", False):
174-
# return v.name
175-
# else:
176-
# return v
177-
#
178-
# def json(
179-
# self,
180-
# *,
181-
# include: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None,
182-
# exclude: Optional[Union["AbstractSetIntStr", "MappingIntStrAny"]] = None,
183-
# by_alias: bool = False,
184-
# skip_defaults: Optional[bool] = None,
185-
# exclude_unset: bool = False,
186-
# exclude_defaults: bool = False,
187-
# exclude_none: bool = False,
188-
# encoder: Optional[Callable[[Any], Any]] = None,
189-
# models_as_dict: bool = True,
190-
# **dumps_kwargs: Any,
191-
# ) -> str:
192-
# """
193-
# Generate a JSON representation of the model, `include` and `exclude` arguments as per `dict()`.
194-
#
195-
# `encoder` is an optional function to supply as `default` to json.dumps(), other arguments as per `json.dumps()`.
196-
# """
197-
# if skip_defaults is not None:
198-
# warnings.warn(
199-
# f'{self.__class__.__name__}.json(): "skip_defaults" is deprecated and replaced by "exclude_unset"',
200-
# DeprecationWarning,
201-
# )
202-
# exclude_unset = skip_defaults
203-
# encoder = cast(Callable[[Any], Any], encoder or self.__json_encoder__)
204-
#
205-
# # We don't directly call `self.dict()`, which does exactly this with `to_dict=True`
206-
# # because we want to be able to keep raw `BaseModel` instances and not as `dict`.
207-
# # This allows users to write custom JSON encoders for given `BaseModel` classes.
208-
# data = dict(
209-
# self._iter(
210-
# to_dict=False,
211-
# by_alias=by_alias,
212-
# include=include,
213-
# exclude=exclude,
214-
# exclude_unset=exclude_unset,
215-
# exclude_defaults=exclude_defaults,
216-
# exclude_none=exclude_none,
217-
# )
218-
# )
219-
# if self.__custom_root_type__:
220-
# data = data[ROOT_KEY]
221-
# return self.__config__.json_dumps(data, default=encoder, **dumps_kwargs)
222-
223-
22428
class StringDate(datetime):
22529
@classmethod
22630
def __get_validators__(cls):
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from typing import List
2+
from typing import Literal
3+
from typing import Optional
4+
5+
from typing_extensions import TypedDict
6+
7+
8+
class BaseFilters(TypedDict, total=False):
9+
id: Optional[int]
10+
id__in: Optional[List[int]]
11+
name: Optional[str]
12+
name__in: Optional[List[str]]
13+
name__contains: Optional[str]
14+
name__starts: Optional[str]
15+
name__ends: Optional[str]
16+
17+
18+
class ItemFilters(BaseFilters):
19+
annotation_status: Optional[str]
20+
annotation_status__in: Optional[List[str]]
21+
annotation_status__ne: Optional[List[str]]
22+
approval_status: Optional[str]
23+
approval_status__in: Optional[List[str]]
24+
approval_status__ne: Optional[str]
25+
assignments__user_id: Optional[str]
26+
assignments__user_id__in: Optional[List[str]]
27+
assignments__user_id__ne: Optional[str]
28+
assignments__user_role: Optional[str]
29+
assignments__user_role__in: Optional[List[str]]
30+
assignments__user_role__ne: Optional[str]
31+
assignments__user_role__notin: Optional[List[str]]
32+
33+
34+
class ProjectFilters(BaseFilters):
35+
# TODO finalize fields
36+
status: Literal["NotStarted", "InProgress", "Completed", "OnHold"]
37+
status__ne: Literal["NotStarted", "InProgress", "Completed", "OnHold"]
38+
status__in: List[Literal["NotStarted", "InProgress", "Completed", "OnHold"]]
39+
status__notin: List[Literal["NotStarted", "InProgress", "Completed", "OnHold"]]

0 commit comments

Comments
 (0)