label-studio-sdk 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. label_studio_sdk/__init__.py +46 -4
  2. label_studio_sdk/_extensions/pager_ext.py +49 -0
  3. label_studio_sdk/_legacy/schema/label_config_schema.json +14 -14
  4. label_studio_sdk/actions/__init__.py +27 -0
  5. label_studio_sdk/actions/client.py +129 -8
  6. label_studio_sdk/actions/types/__init__.py +27 -0
  7. label_studio_sdk/actions/types/actions_create_request_filters.py +43 -0
  8. label_studio_sdk/actions/types/actions_create_request_filters_conjunction.py +5 -0
  9. label_studio_sdk/actions/types/actions_create_request_filters_items_item.py +50 -0
  10. label_studio_sdk/actions/types/actions_create_request_filters_items_item_filter.py +31 -0
  11. label_studio_sdk/actions/types/actions_create_request_filters_items_item_operator.py +23 -0
  12. label_studio_sdk/actions/types/actions_create_request_filters_items_item_value.py +5 -0
  13. label_studio_sdk/actions/types/actions_create_request_id.py +19 -0
  14. label_studio_sdk/actions/types/actions_create_request_ordering_item.py +31 -0
  15. label_studio_sdk/actions/types/actions_create_request_selected_items.py +10 -0
  16. label_studio_sdk/actions/types/actions_create_request_selected_items_excluded.py +39 -0
  17. label_studio_sdk/actions/types/actions_create_request_selected_items_included.py +39 -0
  18. label_studio_sdk/base_client.py +183 -0
  19. label_studio_sdk/client.py +17 -175
  20. label_studio_sdk/core/client_wrapper.py +1 -1
  21. label_studio_sdk/core/http_client.py +5 -1
  22. label_studio_sdk/errors/bad_request_error.py +3 -1
  23. label_studio_sdk/export_storage/azure/client.py +176 -10
  24. label_studio_sdk/export_storage/azure/types/azure_create_response.py +15 -0
  25. label_studio_sdk/export_storage/azure/types/azure_update_response.py +15 -0
  26. label_studio_sdk/export_storage/gcs/client.py +180 -14
  27. label_studio_sdk/export_storage/gcs/types/gcs_create_response.py +16 -1
  28. label_studio_sdk/export_storage/gcs/types/gcs_update_response.py +16 -1
  29. label_studio_sdk/export_storage/local/client.py +168 -22
  30. label_studio_sdk/export_storage/local/types/local_create_response.py +12 -2
  31. label_studio_sdk/export_storage/local/types/local_update_response.py +12 -2
  32. label_studio_sdk/export_storage/redis/client.py +234 -30
  33. label_studio_sdk/export_storage/redis/types/redis_create_response.py +20 -5
  34. label_studio_sdk/export_storage/redis/types/redis_update_response.py +20 -5
  35. label_studio_sdk/export_storage/s3/client.py +214 -26
  36. label_studio_sdk/export_storage/s3/types/s3create_response.py +15 -0
  37. label_studio_sdk/export_storage/s3/types/s3update_response.py +15 -0
  38. label_studio_sdk/import_storage/azure/client.py +266 -90
  39. label_studio_sdk/import_storage/azure/types/azure_create_response.py +28 -18
  40. label_studio_sdk/import_storage/azure/types/azure_update_response.py +28 -18
  41. label_studio_sdk/import_storage/gcs/client.py +270 -94
  42. label_studio_sdk/import_storage/gcs/types/gcs_create_response.py +28 -18
  43. label_studio_sdk/import_storage/gcs/types/gcs_update_response.py +28 -18
  44. label_studio_sdk/import_storage/local/client.py +168 -22
  45. label_studio_sdk/import_storage/local/types/local_create_response.py +12 -2
  46. label_studio_sdk/import_storage/local/types/local_update_response.py +12 -2
  47. label_studio_sdk/import_storage/redis/client.py +206 -50
  48. label_studio_sdk/import_storage/redis/types/redis_create_response.py +20 -10
  49. label_studio_sdk/import_storage/redis/types/redis_update_response.py +20 -10
  50. label_studio_sdk/import_storage/s3/client.py +336 -110
  51. label_studio_sdk/import_storage/s3/types/s3create_response.py +35 -25
  52. label_studio_sdk/import_storage/s3/types/s3update_response.py +35 -25
  53. label_studio_sdk/{_legacy/label_interface → label_interface}/base.py +10 -0
  54. label_studio_sdk/{_legacy/label_interface → label_interface}/control_tags.py +109 -71
  55. label_studio_sdk/{_legacy/label_interface → label_interface}/interface.py +97 -51
  56. label_studio_sdk/{_legacy/label_interface → label_interface}/object_tags.py +8 -13
  57. label_studio_sdk/label_interface/objects.py +60 -0
  58. label_studio_sdk/label_interface/region.py +75 -0
  59. label_studio_sdk/projects/client.py +6 -4
  60. label_studio_sdk/projects/client_ext.py +19 -0
  61. label_studio_sdk/tasks/client.py +35 -8
  62. label_studio_sdk/tasks/client_ext.py +18 -0
  63. label_studio_sdk/types/__init__.py +10 -0
  64. label_studio_sdk/types/annotation.py +5 -5
  65. label_studio_sdk/types/annotations_dm_field.py +120 -0
  66. label_studio_sdk/types/annotations_dm_field_last_action.py +19 -0
  67. label_studio_sdk/types/data_manager_task_serializer.py +123 -0
  68. label_studio_sdk/types/data_manager_task_serializer_drafts_item.py +31 -0
  69. label_studio_sdk/types/data_manager_task_serializer_predictions_item.py +37 -0
  70. label_studio_sdk/types/task.py +1 -1
  71. label_studio_sdk/views/__init__.py +12 -4
  72. label_studio_sdk/views/types/__init__.py +12 -4
  73. label_studio_sdk/views/types/views_create_request_data.py +2 -2
  74. label_studio_sdk/views/types/views_create_request_data_filters.py +5 -5
  75. label_studio_sdk/views/types/views_create_request_data_filters_conjunction.py +1 -1
  76. label_studio_sdk/views/types/views_create_request_data_filters_items_item.py +11 -8
  77. label_studio_sdk/views/types/views_create_request_data_filters_items_item_filter.py +31 -0
  78. label_studio_sdk/views/types/views_create_request_data_filters_items_item_operator.py +23 -0
  79. label_studio_sdk/views/types/views_create_request_data_filters_items_item_value.py +5 -0
  80. label_studio_sdk/views/types/views_create_request_data_ordering_item.py +27 -34
  81. label_studio_sdk/views/types/views_update_request_data.py +2 -2
  82. label_studio_sdk/views/types/views_update_request_data_filters.py +5 -5
  83. label_studio_sdk/views/types/views_update_request_data_filters_conjunction.py +1 -1
  84. label_studio_sdk/views/types/views_update_request_data_filters_items_item.py +11 -8
  85. label_studio_sdk/views/types/views_update_request_data_filters_items_item_filter.py +31 -0
  86. label_studio_sdk/views/types/views_update_request_data_filters_items_item_operator.py +23 -0
  87. label_studio_sdk/views/types/views_update_request_data_filters_items_item_value.py +5 -0
  88. label_studio_sdk/views/types/views_update_request_data_ordering_item.py +27 -34
  89. label_studio_sdk-1.0.2.dist-info/METADATA +195 -0
  90. {label_studio_sdk-1.0.0.dist-info → label_studio_sdk-1.0.2.dist-info}/RECORD +94 -69
  91. label_studio_sdk/_legacy/label_interface/region.py +0 -43
  92. label_studio_sdk/_legacy/objects.py +0 -35
  93. label_studio_sdk/views/types/views_create_request_data_ordering_item_direction.py +0 -5
  94. label_studio_sdk/views/types/views_update_request_data_ordering_item_direction.py +0 -5
  95. label_studio_sdk-1.0.0.dist-info/METADATA +0 -307
  96. /label_studio_sdk/{_legacy/label_interface → label_interface}/__init__.py +0 -0
  97. /label_studio_sdk/{_legacy/label_interface → label_interface}/data_examples.json +0 -0
  98. /label_studio_sdk/{_legacy/label_interface → label_interface}/label_tags.py +0 -0
  99. {label_studio_sdk-1.0.0.dist-info → label_studio_sdk-1.0.2.dist-info}/WHEEL +0 -0
@@ -12,7 +12,7 @@ from typing import Dict, Optional, List, Tuple, Any, Callable, Union
12
12
  from pydantic import BaseModel
13
13
 
14
14
  # from typing import Dict, Optional, List, Tuple, Any
15
- from collections import defaultdict
15
+ from collections import defaultdict, OrderedDict
16
16
  from lxml import etree
17
17
  import xmljson
18
18
 
@@ -22,18 +22,18 @@ from label_studio_sdk._legacy.exceptions import (
22
22
  LabelStudioValidationErrorSentryIgnored,
23
23
  )
24
24
 
25
- from label_studio_sdk._legacy.label_interface.control_tags import (
25
+ from .control_tags import (
26
26
  ControlTag,
27
27
  ChoicesTag,
28
28
  LabelsTag,
29
29
  )
30
- from label_studio_sdk._legacy.label_interface.object_tags import ObjectTag
31
- from label_studio_sdk._legacy.label_interface.label_tags import LabelTag
32
- from label_studio_sdk._legacy.objects import AnnotationValue, TaskValue, PredictionValue
30
+ from .object_tags import ObjectTag
31
+ from .label_tags import LabelTag
32
+ from .objects import AnnotationValue, TaskValue, PredictionValue
33
33
 
34
34
 
35
35
  dir_path = os.path.dirname(os.path.realpath(__file__))
36
- file_path = os.path.join(dir_path, "..", "schema", "label_config_schema.json")
36
+ file_path = os.path.join(dir_path, "..", "_legacy", "schema", "label_config_schema.json")
37
37
 
38
38
  with open(file_path) as f:
39
39
  _LABEL_CONFIG_SCHEMA_DATA = json.load(f)
@@ -189,23 +189,34 @@ class LabelInterface:
189
189
  ```
190
190
  """
191
191
 
192
- def __init__(self, config: str, *args, **kwargs):
192
+ def __init__(self, config: str, tags_mapping=None, *args, **kwargs):
193
193
  """
194
- Create LabelInterface instance from the config string
195
- Example:
194
+ Initialize a LabelInterface instance using a config string.
195
+
196
+ Args:
197
+ config (str): Configuration string.
198
+ tags_mapping: Provide your own implementation of any tag, this is helpful in cases where you want to override one of the control tags, and have your custom .label() method implemented.
199
+
200
+ The configuration string should be formatted as follows:
201
+
196
202
  ```
197
- label_config = LabelInterface('''
198
203
  <View>
199
- <Choices name="sentiment" toName="txt">
204
+ <Choices name="sentiment" toName="txt">
200
205
  <Choice value="Positive" />
201
206
  <Choice value="Negative" />
202
207
  <Choice value="Neutral" />
203
- </Choices>
204
- <Text name="txt" value="$text" />
205
- ''')
208
+ </Choices>
209
+ <Text name="txt" value="$text" />
210
+ </View>
211
+ ```
212
+ This method will extract the predefined task from the configuration and
213
+ parse the controls, objects, and labels used in it.
206
214
  """
215
+ self.task_loaded = False
216
+
207
217
  self._config = config
208
-
218
+ self._tags_mapping = tags_mapping
219
+
209
220
  # extract predefined task from the config
210
221
  _task_data, _ann, _pred = LabelInterface.get_task_from_labeling_config(config)
211
222
  self._sample_config_task = _task_data
@@ -225,6 +236,8 @@ class LabelInterface:
225
236
  self._labels = labels
226
237
  self._tree = tree
227
238
 
239
+
240
+
228
241
  ##### NEW API
229
242
 
230
243
  @property
@@ -391,7 +404,38 @@ class LabelInterface:
391
404
  lst = list(filter(match_fn, lst))
392
405
 
393
406
  return lst
407
+
408
+ def load_task(self, task):
409
+ """Loads a task and substitutes the value in each object tag
410
+ with actual data from the task, returning a copy of the
411
+ LabelConfig object.
412
+
413
+ If the `value` field in an object tag is designed to take
414
+ variable input (i.e., `value_is_variable` is True), the
415
+ function replaces this value with the corresponding value from
416
+ the task dictionary.
417
+
418
+ Args:
419
+ task (dict): Dictionary representing the task, where
420
+ each key-value pair denotes an attribute-value of the
421
+ task.
422
+
423
+ Returns:
424
+ LabelInterface: A deep copy of the current LabelIntreface
425
+ instance with the object tags' value fields populated with
426
+ data from the task.
427
+
428
+ """
429
+ tree = copy.deepcopy(self)
430
+ tree.task_loaded = True
431
+
432
+ for obj in tree.objects:
433
+ print(obj.value_is_variable, obj.value_name)
434
+ if obj.value_is_variable and obj.value_name in task:
435
+ obj.value = task.get(obj.value_name)
394
436
 
437
+ return tree
438
+
395
439
  def parse(self, config_string: str) -> Tuple[Dict, Dict, Dict, etree._Element]:
396
440
  """Parses the received configuration string into dictionaries
397
441
  of ControlTags, ObjectTags, and Labels, along with an XML tree
@@ -422,10 +466,10 @@ class LabelInterface:
422
466
  variables.append(tag.attrib["indexFlag"])
423
467
 
424
468
  if ControlTag.validate_node(tag):
425
- controls[tag.attrib["name"]] = ControlTag.parse_node(tag)
469
+ controls[tag.attrib["name"]] = ControlTag.parse_node(tag, tags_mapping=self._tags_mapping)
426
470
 
427
471
  elif ObjectTag.validate_node(tag):
428
- objects[tag.attrib["name"]] = ObjectTag.parse_node(tag)
472
+ objects[tag.attrib["name"]] = ObjectTag.parse_node(tag, tags_mapping=self._tags_mapping)
429
473
 
430
474
  elif LabelTag.validate_node(tag):
431
475
  lb = LabelTag.parse_node(tag, controls)
@@ -488,34 +532,6 @@ class LabelInterface:
488
532
  "Label config contains non-unique names"
489
533
  )
490
534
 
491
- def load_task(self, task):
492
- """Loads a task and substitutes the value in each object tag
493
- with actual data from the task, returning a copy of the
494
- LabelConfig object.
495
-
496
- If the `value` field in an object tag is designed to take
497
- variable input (i.e., `value_is_variable` is True), the
498
- function replaces this value with the corresponding value from
499
- the task dictionary.
500
-
501
- Args:
502
- task (dict): Dictionary representing the task, where
503
- each key-value pair denotes an attribute-value of the
504
- task.
505
-
506
- Returns:
507
- LabelInterface: A deep copy of the current LabelIntreface
508
- instance with the object tags' value fields populated with
509
- data from the task.
510
-
511
- """
512
- tree = copy.deepcopy(self)
513
- for obj in tree.objects:
514
- if obj.value_is_variable and obj.value_name in task:
515
- obj.value = task.get(obj.value_name)
516
-
517
- return tree
518
-
519
535
  @property
520
536
  def is_valid(self):
521
537
  """ """
@@ -568,6 +584,23 @@ class LabelInterface:
568
584
 
569
585
  return True
570
586
 
587
+ def _validate_object(self, obj):
588
+ """ """
589
+ regions = []
590
+ for r in obj.get(RESULT_KEY):
591
+ if r.get('type') != "relation":
592
+ if not self.validate_region(r):
593
+ return False
594
+
595
+ regions.append(r)
596
+
597
+ for r in obj.get(RESULT_KEY):
598
+ if r.get('type') == "relation" and \
599
+ not self.validate_relation(r, regions):
600
+ return False
601
+
602
+ return True
603
+
571
604
  def validate_annotation(self, annotation):
572
605
  """Validates the given annotation against the current configuration.
573
606
 
@@ -586,11 +619,11 @@ class LabelInterface:
586
619
  validation, False otherwise.
587
620
 
588
621
  """
589
- return all(self.validate_region(r) for r in annotation.get(RESULT_KEY))
622
+ return self._validate_object(annotation)
590
623
 
591
624
  def validate_prediction(self, prediction):
592
625
  """Same as validate_annotation right now"""
593
- return all(self.validate_region(r) for r in prediction.get(RESULT_KEY))
626
+ return self._validate_object(prediction)
594
627
 
595
628
  def validate_region(self, region) -> bool:
596
629
  """Validates a region from the annotation against the current
@@ -620,22 +653,35 @@ class LabelInterface:
620
653
  if not control or not obj:
621
654
  return False
622
655
 
623
- # type of the region should match the tag name
656
+ # type of the region should match the tag name
624
657
  if control.tag.lower() != region["type"]:
625
658
  return False
626
-
659
+
627
660
  # make sure that in config it connects to the same tag as
628
661
  # immplied by the region data
629
662
  if region["to_name"] not in control.to_name:
630
663
  return False
631
-
664
+
632
665
  # validate the actual value, for example that <Labels /> tag
633
666
  # is producing start, end, and labels
634
667
  if not control.validate_value(region["value"]):
635
668
  return False
636
-
669
+
637
670
  return True
638
671
 
672
+ def validate_relation(self, relation, regions, _mapping=None) -> bool:
673
+ """Validates that the relation is correct and all the associated objects are provided"""
674
+ if _mapping is None:
675
+ _mapping = { r['id']: r for r in regions }
676
+
677
+ if relation.get("type") != "relation" or \
678
+ relation.get("direction") not in ("left", "right", "bi") or \
679
+ relation.get("from_id") not in _mapping or \
680
+ relation.get("to_id") not in _mapping:
681
+ return False
682
+
683
+ return True
684
+
639
685
  ### Generation
640
686
 
641
687
  def _sample_task(self, secure_mode=False):
@@ -8,7 +8,7 @@ import xml.etree.ElementTree
8
8
  from urllib.parse import urlencode
9
9
  from typing import Optional
10
10
 
11
- from .base import LabelStudioTag
11
+ from .base import LabelStudioTag, get_tag_class
12
12
 
13
13
  _TAG_TO_CLASS = {
14
14
  "audio": "AudioTag",
@@ -77,12 +77,6 @@ def data_examples(
77
77
  return _DATA_EXAMPLES[mode]
78
78
 
79
79
 
80
- def get_tag_class(name):
81
- """ """
82
- class_name = _TAG_TO_CLASS.get(name.lower())
83
- return globals().get(class_name, None)
84
-
85
-
86
80
  class ObjectTag(LabelStudioTag):
87
81
  """
88
82
  Class that represents a ObjectTag in Label Studio
@@ -103,7 +97,7 @@ class ObjectTag(LabelStudioTag):
103
97
  # self._value_type = value_type
104
98
 
105
99
  @classmethod
106
- def parse_node(cls, tag: xml.etree.ElementTree.Element) -> "ObjectTag":
100
+ def parse_node(cls, tag: xml.etree.ElementTree.Element, tags_mapping=None) -> "ObjectTag":
107
101
  """
108
102
  This class method parses a node and returns a ObjectTag object if the node has a name and a value.
109
103
 
@@ -117,8 +111,10 @@ class ObjectTag(LabelStudioTag):
117
111
  ObjectTag
118
112
  A new ObjectTag object with the tag name, attributes, name, and value.
119
113
  """
120
- tag_class = get_tag_class(tag.tag) or cls
121
-
114
+ tag_class = get_tag_class(tag.tag, _TAG_TO_CLASS, re_mapping=tags_mapping) or cls
115
+ if isinstance(tag_class, str):
116
+ tag_class = globals().get(tag_class, None)
117
+
122
118
  return tag_class(
123
119
  tag=tag.tag,
124
120
  attr=dict(tag.attrib),
@@ -146,10 +142,9 @@ class ObjectTag(LabelStudioTag):
146
142
  @property
147
143
  def value_is_variable(self) -> bool:
148
144
  """Check if value has variable"""
149
- pattern = re.compile(r"^\$[A-Za-z_]+$")
145
+ pattern = re.compile(r"^\$[^, ]+$")
150
146
  return bool(pattern.fullmatch(self.value))
151
-
152
- # TODO this should not be here as soon as we cover all the tags
147
+
153
148
 
154
149
  # and have generate_example in each
155
150
  def generate_example_value(self, mode="upload", secure_mode=False):
@@ -0,0 +1,60 @@
1
+ from typing import Type, Dict, Optional, List, Tuple, Any, Union
2
+ from pydantic import BaseModel, Field, confloat, field_serializer
3
+
4
+ from .region import Region
5
+
6
+
7
+ def serialize_regions(result):
8
+ res = []
9
+ relations = []
10
+ for r in result:
11
+ if isinstance(r, Region):
12
+ res.append(r._dict())
13
+ if r.has_relations:
14
+ relations.append(r._dict_relations())
15
+ else:
16
+ res.append(r)
17
+
18
+ return res + relations
19
+
20
+
21
+ class PredictionValue(BaseModel):
22
+ """ """
23
+
24
+ model_version: Optional[Any] = None
25
+ score: Optional[float] = 0.00
26
+ result: Optional[List[Union[Dict[str, Any], Region]]]
27
+
28
+ class Config:
29
+ allow_population_by_field_name = True
30
+
31
+ @field_serializer('result')
32
+ def serialize_result(self, result):
33
+ return serialize_regions(result)
34
+
35
+
36
+ class AnnotationValue(BaseModel):
37
+ """ """
38
+
39
+ was_cancelled: Optional[bool] = False
40
+ ground_truth: Optional[bool] = False
41
+ lead_time: Optional[float] = 0.0
42
+ result_count: Optional[int] = 0
43
+ completed_by: int
44
+
45
+ result: Optional[List[Union[Dict[str, Any], Region]]]
46
+
47
+ class Config:
48
+ allow_population_by_field_name = True
49
+
50
+ @field_serializer('result')
51
+ def serialize_result(self, result):
52
+ return serialize_regions(result)
53
+
54
+
55
+ class TaskValue(BaseModel):
56
+ """ """
57
+
58
+ data: Optional[dict]
59
+ annotations: Optional[List[AnnotationValue]]
60
+ predictions: Optional[List[PredictionValue]]
@@ -0,0 +1,75 @@
1
+ """
2
+ """
3
+
4
+ import json
5
+ from uuid import uuid4
6
+
7
+ from typing import Any, List, Dict, Optional
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class Region(BaseModel):
12
+ """
13
+ Class for Region Tag
14
+
15
+ Attributes:
16
+ -----------
17
+ id: str
18
+ The unique identifier of the region
19
+ x: int
20
+ The x coordinate of the region
21
+ y: int
22
+
23
+ """
24
+
25
+ id: str = Field(default_factory=lambda: str(uuid4()))
26
+ from_tag: Any
27
+ to_tag: Any
28
+ value: Any
29
+ relations: Optional[List[Dict]] = []
30
+
31
+ def _dict(self):
32
+ """ """
33
+ return {
34
+ "id": self.id,
35
+ "from_name": self.from_tag.name,
36
+ "to_name": self.to_tag.name,
37
+ "type": self.from_tag.tag.lower(),
38
+ # TODO This needs to be improved
39
+ "value": self.value.dict(),
40
+ }
41
+
42
+ def _dict_relations(self):
43
+ """ """
44
+ # this code does not include "labels" key if no labels were passed
45
+ return [
46
+ {**{
47
+ "from_id": self.id,
48
+ "to_id": rel.get("region", {}).id,
49
+ "type": "relation",
50
+ "direction": rel.get("direction", "right")},
51
+ **({"labels": rel["labels"]} if rel.get("labels") else {})
52
+ }
53
+ for rel in self.relations
54
+ ]
55
+
56
+ def to_json(self):
57
+ """ """
58
+ return json.dumps(self._dict())
59
+
60
+ def to_json_relations(self):
61
+ """ """
62
+ return json.dumps(self._dict_relations())
63
+
64
+ @property
65
+ def has_relations(self):
66
+ return len(self.relations) > 0
67
+
68
+ def add_relation(self, region=None, direction="right", label=None):
69
+ """ """
70
+ self.relations.append({ "region": region, "direction": direction, "labels": label })
71
+
72
+ def set_relations(self, rels):
73
+ """ """
74
+ self.relations = rels
75
+
@@ -82,6 +82,7 @@ class ProjectsClient:
82
82
  )
83
83
  client.projects.list()
84
84
  """
85
+ page = page or 1
85
86
  _response = self._client_wrapper.httpx_client.request(
86
87
  "api/projects/",
87
88
  method="GET",
@@ -95,7 +96,7 @@ class ProjectsClient:
95
96
  ordering=ordering,
96
97
  ids=ids,
97
98
  title=title,
98
- page=page + 1 if page is not None else 1,
99
+ page=page + 1,
99
100
  page_size=page_size,
100
101
  request_options=request_options,
101
102
  )
@@ -475,7 +476,7 @@ class ProjectsClient:
475
476
  if 200 <= _response.status_code < 300:
476
477
  return pydantic_v1.parse_obj_as(ProjectsImportTasksResponse, _response.json()) # type: ignore
477
478
  if _response.status_code == 400:
478
- raise BadRequestError(pydantic_v1.parse_obj_as(str, _response.json())) # type: ignore
479
+ raise BadRequestError(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore
479
480
  try:
480
481
  _response_json = _response.json()
481
482
  except JSONDecodeError:
@@ -597,6 +598,7 @@ class AsyncProjectsClient:
597
598
  )
598
599
  await client.projects.list()
599
600
  """
601
+ page = page or 1
600
602
  _response = await self._client_wrapper.httpx_client.request(
601
603
  "api/projects/",
602
604
  method="GET",
@@ -610,7 +612,7 @@ class AsyncProjectsClient:
610
612
  ordering=ordering,
611
613
  ids=ids,
612
614
  title=title,
613
- page=page + 1 if page is not None else 1,
615
+ page=page + 1,
614
616
  page_size=page_size,
615
617
  request_options=request_options,
616
618
  )
@@ -992,7 +994,7 @@ class AsyncProjectsClient:
992
994
  if 200 <= _response.status_code < 300:
993
995
  return pydantic_v1.parse_obj_as(ProjectsImportTasksResponse, _response.json()) # type: ignore
994
996
  if _response.status_code == 400:
995
- raise BadRequestError(pydantic_v1.parse_obj_as(str, _response.json())) # type: ignore
997
+ raise BadRequestError(pydantic_v1.parse_obj_as(typing.Any, _response.json())) # type: ignore
996
998
  try:
997
999
  _response_json = _response.json()
998
1000
  except JSONDecodeError:
@@ -0,0 +1,19 @@
1
+ from .client import ProjectsClient, AsyncProjectsClient
2
+
3
+ from label_studio_sdk._extensions.pager_ext import SyncPagerExt, AsyncPagerExt, T
4
+
5
+
6
+ class ProjectsClientExt(ProjectsClient):
7
+
8
+ def list(self, **kwargs) -> SyncPagerExt[T]:
9
+ return SyncPagerExt.from_sync_pager(super().list(**kwargs))
10
+
11
+ list.__doc__ = ProjectsClient.list.__doc__
12
+
13
+
14
+ class AsyncProjectsClientExt(AsyncProjectsClient):
15
+
16
+ async def list(self, **kwargs):
17
+ return await AsyncPagerExt.from_async_pager(await super().list(**kwargs))
18
+
19
+ list.__doc__ = AsyncProjectsClient.list.__doc__
@@ -10,6 +10,7 @@ from ..core.pagination import AsyncPager, SyncPager
10
10
  from ..core.pydantic_utilities import pydantic_v1
11
11
  from ..core.request_options import RequestOptions
12
12
  from ..types.base_task import BaseTask
13
+ from ..types.data_manager_task_serializer import DataManagerTaskSerializer
13
14
  from ..types.project_import import ProjectImport
14
15
  from ..types.task import Task
15
16
  from .types.tasks_list_request_fields import TasksListRequestFields
@@ -126,6 +127,7 @@ class TasksClient:
126
127
  fields: typing.Optional[TasksListRequestFields] = None,
127
128
  review: typing.Optional[bool] = None,
128
129
  include: typing.Optional[str] = None,
130
+ query: typing.Optional[str] = None,
129
131
  request_options: typing.Optional[RequestOptions] = None,
130
132
  ) -> SyncPager[Task]:
131
133
  """
@@ -161,6 +163,14 @@ class TasksClient:
161
163
  include : typing.Optional[str]
162
164
  Specify which fields to include in the response
163
165
 
166
+ query : typing.Optional[str]
167
+ Additional query to filter tasks. It must be JSON encoded string of dict containing one of the following parameters: `{"filters": ..., "selectedItems": ..., "ordering": ...}`. Check [Data Manager > Create View > see `data` field](#tag/Data-Manager/operation/api_dm_views_create) for more details about filters, selectedItems and ordering.
168
+
169
+ - **filters**: dict with `"conjunction"` string (`"or"` or `"and"`) and list of filters in `"items"` array. Each filter is a dictionary with keys: `"filter"`, `"operator"`, `"type"`, `"value"`. [Read more about available filters](https://labelstud.io/sdk/data_manager.html)<br/> Example: `{"conjunction": "or", "items": [{"filter": "filter:tasks:completed_at", "operator": "greater", "type": "Datetime", "value": "2021-01-01T00:00:00.000Z"}]}`
170
+ - **selectedItems**: dictionary with keys: `"all"`, `"included"`, `"excluded"`. If "all" is `false`, `"included"` must be used. If "all" is `true`, `"excluded"` must be used.<br/> Examples: `{"all": false, "included": [1, 2, 3]}` or `{"all": true, "excluded": [4, 5]}`
171
+ - **ordering**: list of fields to order by. Currently, ordering is supported by only one parameter. <br/>
172
+ Example: `["completed_at"]`
173
+
164
174
  request_options : typing.Optional[RequestOptions]
165
175
  Request-specific configuration.
166
176
 
@@ -178,6 +188,7 @@ class TasksClient:
178
188
  )
179
189
  client.tasks.list()
180
190
  """
191
+ page = page or 1
181
192
  _response = self._client_wrapper.httpx_client.request(
182
193
  "api/tasks/",
183
194
  method="GET",
@@ -190,6 +201,7 @@ class TasksClient:
190
201
  "fields": fields,
191
202
  "review": review,
192
203
  "include": include,
204
+ "query": query,
193
205
  },
194
206
  request_options=request_options,
195
207
  )
@@ -197,7 +209,7 @@ class TasksClient:
197
209
  _parsed_response = pydantic_v1.parse_obj_as(TasksListResponse, _response.json()) # type: ignore
198
210
  _has_next = True
199
211
  _get_next = lambda: self.list(
200
- page=page + 1 if page is not None else 1,
212
+ page=page + 1,
201
213
  page_size=page_size,
202
214
  view=view,
203
215
  project=project,
@@ -205,6 +217,7 @@ class TasksClient:
205
217
  fields=fields,
206
218
  review=review,
207
219
  include=include,
220
+ query=query,
208
221
  request_options=request_options,
209
222
  )
210
223
  _items = _parsed_response.tasks
@@ -272,7 +285,7 @@ class TasksClient:
272
285
  raise ApiError(status_code=_response.status_code, body=_response.text)
273
286
  raise ApiError(status_code=_response.status_code, body=_response_json)
274
287
 
275
- def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> BaseTask:
288
+ def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> DataManagerTaskSerializer:
276
289
  """
277
290
  Get task data, metadata, annotations and other attributes for a specific labeling task by task ID.
278
291
  The task ID is available from the Label Studio URL when viewing the task, or you can retrieve it programmatically with [Get task list](list).
@@ -287,7 +300,7 @@ class TasksClient:
287
300
 
288
301
  Returns
289
302
  -------
290
- BaseTask
303
+ DataManagerTaskSerializer
291
304
  Task
292
305
 
293
306
  Examples
@@ -305,7 +318,7 @@ class TasksClient:
305
318
  f"api/tasks/{jsonable_encoder(id)}/", method="GET", request_options=request_options
306
319
  )
307
320
  if 200 <= _response.status_code < 300:
308
- return pydantic_v1.parse_obj_as(BaseTask, _response.json()) # type: ignore
321
+ return pydantic_v1.parse_obj_as(DataManagerTaskSerializer, _response.json()) # type: ignore
309
322
  try:
310
323
  _response_json = _response.json()
311
324
  except JSONDecodeError:
@@ -522,6 +535,7 @@ class AsyncTasksClient:
522
535
  fields: typing.Optional[TasksListRequestFields] = None,
523
536
  review: typing.Optional[bool] = None,
524
537
  include: typing.Optional[str] = None,
538
+ query: typing.Optional[str] = None,
525
539
  request_options: typing.Optional[RequestOptions] = None,
526
540
  ) -> AsyncPager[Task]:
527
541
  """
@@ -557,6 +571,14 @@ class AsyncTasksClient:
557
571
  include : typing.Optional[str]
558
572
  Specify which fields to include in the response
559
573
 
574
+ query : typing.Optional[str]
575
+ Additional query to filter tasks. It must be JSON encoded string of dict containing one of the following parameters: `{"filters": ..., "selectedItems": ..., "ordering": ...}`. Check [Data Manager > Create View > see `data` field](#tag/Data-Manager/operation/api_dm_views_create) for more details about filters, selectedItems and ordering.
576
+
577
+ - **filters**: dict with `"conjunction"` string (`"or"` or `"and"`) and list of filters in `"items"` array. Each filter is a dictionary with keys: `"filter"`, `"operator"`, `"type"`, `"value"`. [Read more about available filters](https://labelstud.io/sdk/data_manager.html)<br/> Example: `{"conjunction": "or", "items": [{"filter": "filter:tasks:completed_at", "operator": "greater", "type": "Datetime", "value": "2021-01-01T00:00:00.000Z"}]}`
578
+ - **selectedItems**: dictionary with keys: `"all"`, `"included"`, `"excluded"`. If "all" is `false`, `"included"` must be used. If "all" is `true`, `"excluded"` must be used.<br/> Examples: `{"all": false, "included": [1, 2, 3]}` or `{"all": true, "excluded": [4, 5]}`
579
+ - **ordering**: list of fields to order by. Currently, ordering is supported by only one parameter. <br/>
580
+ Example: `["completed_at"]`
581
+
560
582
  request_options : typing.Optional[RequestOptions]
561
583
  Request-specific configuration.
562
584
 
@@ -574,6 +596,7 @@ class AsyncTasksClient:
574
596
  )
575
597
  await client.tasks.list()
576
598
  """
599
+ page = page or 1
577
600
  _response = await self._client_wrapper.httpx_client.request(
578
601
  "api/tasks/",
579
602
  method="GET",
@@ -586,6 +609,7 @@ class AsyncTasksClient:
586
609
  "fields": fields,
587
610
  "review": review,
588
611
  "include": include,
612
+ "query": query,
589
613
  },
590
614
  request_options=request_options,
591
615
  )
@@ -593,7 +617,7 @@ class AsyncTasksClient:
593
617
  _parsed_response = pydantic_v1.parse_obj_as(TasksListResponse, _response.json()) # type: ignore
594
618
  _has_next = True
595
619
  _get_next = lambda: self.list(
596
- page=page + 1 if page is not None else 1,
620
+ page=page + 1,
597
621
  page_size=page_size,
598
622
  view=view,
599
623
  project=project,
@@ -601,6 +625,7 @@ class AsyncTasksClient:
601
625
  fields=fields,
602
626
  review=review,
603
627
  include=include,
628
+ query=query,
604
629
  request_options=request_options,
605
630
  )
606
631
  _items = _parsed_response.tasks
@@ -668,7 +693,9 @@ class AsyncTasksClient:
668
693
  raise ApiError(status_code=_response.status_code, body=_response.text)
669
694
  raise ApiError(status_code=_response.status_code, body=_response_json)
670
695
 
671
- async def get(self, id: str, *, request_options: typing.Optional[RequestOptions] = None) -> BaseTask:
696
+ async def get(
697
+ self, id: str, *, request_options: typing.Optional[RequestOptions] = None
698
+ ) -> DataManagerTaskSerializer:
672
699
  """
673
700
  Get task data, metadata, annotations and other attributes for a specific labeling task by task ID.
674
701
  The task ID is available from the Label Studio URL when viewing the task, or you can retrieve it programmatically with [Get task list](list).
@@ -683,7 +710,7 @@ class AsyncTasksClient:
683
710
 
684
711
  Returns
685
712
  -------
686
- BaseTask
713
+ DataManagerTaskSerializer
687
714
  Task
688
715
 
689
716
  Examples
@@ -701,7 +728,7 @@ class AsyncTasksClient:
701
728
  f"api/tasks/{jsonable_encoder(id)}/", method="GET", request_options=request_options
702
729
  )
703
730
  if 200 <= _response.status_code < 300:
704
- return pydantic_v1.parse_obj_as(BaseTask, _response.json()) # type: ignore
731
+ return pydantic_v1.parse_obj_as(DataManagerTaskSerializer, _response.json()) # type: ignore
705
732
  try:
706
733
  _response_json = _response.json()
707
734
  except JSONDecodeError: