label-studio-sdk 1.0.0__py3-none-any.whl → 1.0.1__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.
Potentially problematic release.
This version of label-studio-sdk might be problematic. Click here for more details.
- label_studio_sdk/__init__.py +30 -4
- label_studio_sdk/_extensions/pager_ext.py +49 -0
- label_studio_sdk/_legacy/objects.py +46 -7
- label_studio_sdk/_legacy/schema/label_config_schema.json +14 -14
- label_studio_sdk/actions/__init__.py +25 -0
- label_studio_sdk/actions/client.py +79 -6
- label_studio_sdk/actions/types/__init__.py +25 -0
- label_studio_sdk/actions/types/actions_create_request_filters.py +43 -0
- label_studio_sdk/actions/types/actions_create_request_filters_conjunction.py +5 -0
- label_studio_sdk/actions/types/actions_create_request_filters_items_item.py +49 -0
- label_studio_sdk/actions/types/actions_create_request_filters_items_item_filter.py +31 -0
- label_studio_sdk/actions/types/actions_create_request_filters_items_item_operator.py +23 -0
- label_studio_sdk/actions/types/actions_create_request_id.py +19 -0
- label_studio_sdk/actions/types/actions_create_request_ordering_item.py +31 -0
- label_studio_sdk/actions/types/actions_create_request_selected_items.py +10 -0
- label_studio_sdk/actions/types/actions_create_request_selected_items_excluded.py +39 -0
- label_studio_sdk/actions/types/actions_create_request_selected_items_included.py +39 -0
- label_studio_sdk/base_client.py +183 -0
- label_studio_sdk/client.py +17 -175
- label_studio_sdk/core/client_wrapper.py +1 -1
- label_studio_sdk/core/http_client.py +5 -1
- label_studio_sdk/export_storage/azure/client.py +176 -10
- label_studio_sdk/export_storage/azure/types/azure_create_response.py +15 -0
- label_studio_sdk/export_storage/azure/types/azure_update_response.py +15 -0
- label_studio_sdk/export_storage/gcs/client.py +180 -14
- label_studio_sdk/export_storage/gcs/types/gcs_create_response.py +16 -1
- label_studio_sdk/export_storage/gcs/types/gcs_update_response.py +16 -1
- label_studio_sdk/export_storage/local/client.py +168 -22
- label_studio_sdk/export_storage/local/types/local_create_response.py +12 -2
- label_studio_sdk/export_storage/local/types/local_update_response.py +12 -2
- label_studio_sdk/export_storage/redis/client.py +234 -30
- label_studio_sdk/export_storage/redis/types/redis_create_response.py +20 -5
- label_studio_sdk/export_storage/redis/types/redis_update_response.py +20 -5
- label_studio_sdk/export_storage/s3/client.py +214 -26
- label_studio_sdk/export_storage/s3/types/s3create_response.py +15 -0
- label_studio_sdk/export_storage/s3/types/s3update_response.py +15 -0
- label_studio_sdk/import_storage/azure/client.py +266 -90
- label_studio_sdk/import_storage/azure/types/azure_create_response.py +28 -18
- label_studio_sdk/import_storage/azure/types/azure_update_response.py +28 -18
- label_studio_sdk/import_storage/gcs/client.py +270 -94
- label_studio_sdk/import_storage/gcs/types/gcs_create_response.py +28 -18
- label_studio_sdk/import_storage/gcs/types/gcs_update_response.py +28 -18
- label_studio_sdk/import_storage/local/client.py +168 -22
- label_studio_sdk/import_storage/local/types/local_create_response.py +12 -2
- label_studio_sdk/import_storage/local/types/local_update_response.py +12 -2
- label_studio_sdk/import_storage/redis/client.py +206 -50
- label_studio_sdk/import_storage/redis/types/redis_create_response.py +20 -10
- label_studio_sdk/import_storage/redis/types/redis_update_response.py +20 -10
- label_studio_sdk/import_storage/s3/client.py +336 -110
- label_studio_sdk/import_storage/s3/types/s3create_response.py +35 -25
- label_studio_sdk/import_storage/s3/types/s3update_response.py +35 -25
- label_studio_sdk/{_legacy/label_interface → label_interface}/base.py +10 -0
- label_studio_sdk/{_legacy/label_interface → label_interface}/control_tags.py +109 -71
- label_studio_sdk/{_legacy/label_interface → label_interface}/interface.py +96 -50
- label_studio_sdk/{_legacy/label_interface → label_interface}/object_tags.py +8 -13
- label_studio_sdk/label_interface/region.py +75 -0
- label_studio_sdk/projects/client.py +4 -2
- label_studio_sdk/projects/client_ext.py +19 -0
- label_studio_sdk/tasks/client.py +26 -2
- label_studio_sdk/tasks/client_ext.py +18 -0
- label_studio_sdk/types/annotation.py +5 -5
- label_studio_sdk/types/task.py +1 -1
- label_studio_sdk/views/__init__.py +8 -4
- label_studio_sdk/views/types/__init__.py +8 -4
- label_studio_sdk/views/types/views_create_request_data.py +2 -2
- label_studio_sdk/views/types/views_create_request_data_filters.py +5 -5
- label_studio_sdk/views/types/views_create_request_data_filters_conjunction.py +1 -1
- label_studio_sdk/views/types/views_create_request_data_filters_items_item.py +10 -8
- label_studio_sdk/views/types/views_create_request_data_filters_items_item_filter.py +31 -0
- label_studio_sdk/views/types/views_create_request_data_filters_items_item_operator.py +23 -0
- label_studio_sdk/views/types/views_create_request_data_ordering_item.py +27 -34
- label_studio_sdk/views/types/views_update_request_data.py +2 -2
- label_studio_sdk/views/types/views_update_request_data_filters.py +5 -5
- label_studio_sdk/views/types/views_update_request_data_filters_conjunction.py +1 -1
- label_studio_sdk/views/types/views_update_request_data_filters_items_item.py +10 -8
- label_studio_sdk/views/types/views_update_request_data_filters_items_item_filter.py +31 -0
- label_studio_sdk/views/types/views_update_request_data_filters_items_item_operator.py +23 -0
- label_studio_sdk/views/types/views_update_request_data_ordering_item.py +27 -34
- {label_studio_sdk-1.0.0.dist-info → label_studio_sdk-1.0.1.dist-info}/METADATA +17 -161
- {label_studio_sdk-1.0.0.dist-info → label_studio_sdk-1.0.1.dist-info}/RECORD +84 -67
- label_studio_sdk/_legacy/label_interface/region.py +0 -43
- label_studio_sdk/views/types/views_create_request_data_ordering_item_direction.py +0 -5
- label_studio_sdk/views/types/views_update_request_data_ordering_item_direction.py +0 -5
- /label_studio_sdk/{_legacy/label_interface → label_interface}/__init__.py +0 -0
- /label_studio_sdk/{_legacy/label_interface → label_interface}/data_examples.json +0 -0
- /label_studio_sdk/{_legacy/label_interface → label_interface}/label_tags.py +0 -0
- {label_studio_sdk-1.0.0.dist-info → label_studio_sdk-1.0.1.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
|
|
25
|
+
from .control_tags import (
|
|
26
26
|
ControlTag,
|
|
27
27
|
ChoicesTag,
|
|
28
28
|
LabelsTag,
|
|
29
29
|
)
|
|
30
|
-
from
|
|
31
|
-
from
|
|
30
|
+
from .object_tags import ObjectTag
|
|
31
|
+
from .label_tags import LabelTag
|
|
32
32
|
from label_studio_sdk._legacy.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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
204
|
+
<Choices name="sentiment" toName="txt">
|
|
200
205
|
<Choice value="Positive" />
|
|
201
206
|
<Choice value="Negative" />
|
|
202
207
|
<Choice value="Neutral" />
|
|
203
|
-
|
|
204
|
-
|
|
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
|
|
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
|
|
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"^\$[
|
|
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,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
|
|
99
|
+
page=page + 1,
|
|
99
100
|
page_size=page_size,
|
|
100
101
|
request_options=request_options,
|
|
101
102
|
)
|
|
@@ -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
|
|
615
|
+
page=page + 1,
|
|
614
616
|
page_size=page_size,
|
|
615
617
|
request_options=request_options,
|
|
616
618
|
)
|
|
@@ -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__
|
label_studio_sdk/tasks/client.py
CHANGED
|
@@ -126,6 +126,7 @@ class TasksClient:
|
|
|
126
126
|
fields: typing.Optional[TasksListRequestFields] = None,
|
|
127
127
|
review: typing.Optional[bool] = None,
|
|
128
128
|
include: typing.Optional[str] = None,
|
|
129
|
+
query: typing.Optional[str] = None,
|
|
129
130
|
request_options: typing.Optional[RequestOptions] = None,
|
|
130
131
|
) -> SyncPager[Task]:
|
|
131
132
|
"""
|
|
@@ -161,6 +162,14 @@ class TasksClient:
|
|
|
161
162
|
include : typing.Optional[str]
|
|
162
163
|
Specify which fields to include in the response
|
|
163
164
|
|
|
165
|
+
query : typing.Optional[str]
|
|
166
|
+
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 for more details about filters, selectedItems and ordering.
|
|
167
|
+
|
|
168
|
+
- **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"}]}`
|
|
169
|
+
- **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]}`
|
|
170
|
+
- **ordering**: list of fields to order by. Currently, ordering is supported by only one parameter. <br/>
|
|
171
|
+
Example: `["completed_at"]`
|
|
172
|
+
|
|
164
173
|
request_options : typing.Optional[RequestOptions]
|
|
165
174
|
Request-specific configuration.
|
|
166
175
|
|
|
@@ -178,6 +187,7 @@ class TasksClient:
|
|
|
178
187
|
)
|
|
179
188
|
client.tasks.list()
|
|
180
189
|
"""
|
|
190
|
+
page = page or 1
|
|
181
191
|
_response = self._client_wrapper.httpx_client.request(
|
|
182
192
|
"api/tasks/",
|
|
183
193
|
method="GET",
|
|
@@ -190,6 +200,7 @@ class TasksClient:
|
|
|
190
200
|
"fields": fields,
|
|
191
201
|
"review": review,
|
|
192
202
|
"include": include,
|
|
203
|
+
"query": query,
|
|
193
204
|
},
|
|
194
205
|
request_options=request_options,
|
|
195
206
|
)
|
|
@@ -197,7 +208,7 @@ class TasksClient:
|
|
|
197
208
|
_parsed_response = pydantic_v1.parse_obj_as(TasksListResponse, _response.json()) # type: ignore
|
|
198
209
|
_has_next = True
|
|
199
210
|
_get_next = lambda: self.list(
|
|
200
|
-
page=page + 1
|
|
211
|
+
page=page + 1,
|
|
201
212
|
page_size=page_size,
|
|
202
213
|
view=view,
|
|
203
214
|
project=project,
|
|
@@ -205,6 +216,7 @@ class TasksClient:
|
|
|
205
216
|
fields=fields,
|
|
206
217
|
review=review,
|
|
207
218
|
include=include,
|
|
219
|
+
query=query,
|
|
208
220
|
request_options=request_options,
|
|
209
221
|
)
|
|
210
222
|
_items = _parsed_response.tasks
|
|
@@ -522,6 +534,7 @@ class AsyncTasksClient:
|
|
|
522
534
|
fields: typing.Optional[TasksListRequestFields] = None,
|
|
523
535
|
review: typing.Optional[bool] = None,
|
|
524
536
|
include: typing.Optional[str] = None,
|
|
537
|
+
query: typing.Optional[str] = None,
|
|
525
538
|
request_options: typing.Optional[RequestOptions] = None,
|
|
526
539
|
) -> AsyncPager[Task]:
|
|
527
540
|
"""
|
|
@@ -557,6 +570,14 @@ class AsyncTasksClient:
|
|
|
557
570
|
include : typing.Optional[str]
|
|
558
571
|
Specify which fields to include in the response
|
|
559
572
|
|
|
573
|
+
query : typing.Optional[str]
|
|
574
|
+
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 for more details about filters, selectedItems and ordering.
|
|
575
|
+
|
|
576
|
+
- **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"}]}`
|
|
577
|
+
- **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]}`
|
|
578
|
+
- **ordering**: list of fields to order by. Currently, ordering is supported by only one parameter. <br/>
|
|
579
|
+
Example: `["completed_at"]`
|
|
580
|
+
|
|
560
581
|
request_options : typing.Optional[RequestOptions]
|
|
561
582
|
Request-specific configuration.
|
|
562
583
|
|
|
@@ -574,6 +595,7 @@ class AsyncTasksClient:
|
|
|
574
595
|
)
|
|
575
596
|
await client.tasks.list()
|
|
576
597
|
"""
|
|
598
|
+
page = page or 1
|
|
577
599
|
_response = await self._client_wrapper.httpx_client.request(
|
|
578
600
|
"api/tasks/",
|
|
579
601
|
method="GET",
|
|
@@ -586,6 +608,7 @@ class AsyncTasksClient:
|
|
|
586
608
|
"fields": fields,
|
|
587
609
|
"review": review,
|
|
588
610
|
"include": include,
|
|
611
|
+
"query": query,
|
|
589
612
|
},
|
|
590
613
|
request_options=request_options,
|
|
591
614
|
)
|
|
@@ -593,7 +616,7 @@ class AsyncTasksClient:
|
|
|
593
616
|
_parsed_response = pydantic_v1.parse_obj_as(TasksListResponse, _response.json()) # type: ignore
|
|
594
617
|
_has_next = True
|
|
595
618
|
_get_next = lambda: self.list(
|
|
596
|
-
page=page + 1
|
|
619
|
+
page=page + 1,
|
|
597
620
|
page_size=page_size,
|
|
598
621
|
view=view,
|
|
599
622
|
project=project,
|
|
@@ -601,6 +624,7 @@ class AsyncTasksClient:
|
|
|
601
624
|
fields=fields,
|
|
602
625
|
review=review,
|
|
603
626
|
include=include,
|
|
627
|
+
query=query,
|
|
604
628
|
request_options=request_options,
|
|
605
629
|
)
|
|
606
630
|
_items = _parsed_response.tasks
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .client import TasksClient, AsyncTasksClient
|
|
2
|
+
from label_studio_sdk._extensions.pager_ext import SyncPagerExt, AsyncPagerExt, T
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TasksClientExt(TasksClient):
|
|
6
|
+
|
|
7
|
+
def list(self, **kwargs) -> SyncPagerExt[T]:
|
|
8
|
+
return SyncPagerExt.from_sync_pager(super().list(**kwargs))
|
|
9
|
+
|
|
10
|
+
list.__doc__ = TasksClient.list.__doc__
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncTasksClientExt(AsyncTasksClient):
|
|
14
|
+
|
|
15
|
+
async def list(self, **kwargs):
|
|
16
|
+
return await AsyncPagerExt.from_async_pager(await super().list(**kwargs))
|
|
17
|
+
|
|
18
|
+
list.__doc__ = AsyncTasksClient.list.__doc__
|
|
@@ -10,6 +10,11 @@ from .annotation_last_action import AnnotationLastAction
|
|
|
10
10
|
|
|
11
11
|
class Annotation(pydantic_v1.BaseModel):
|
|
12
12
|
id: typing.Optional[int] = None
|
|
13
|
+
result: typing.Optional[typing.List[typing.Dict[str, typing.Any]]] = pydantic_v1.Field(default=None)
|
|
14
|
+
"""
|
|
15
|
+
List of annotation results for the task
|
|
16
|
+
"""
|
|
17
|
+
|
|
13
18
|
created_username: typing.Optional[str] = pydantic_v1.Field(default=None)
|
|
14
19
|
"""
|
|
15
20
|
Username string
|
|
@@ -22,11 +27,6 @@ class Annotation(pydantic_v1.BaseModel):
|
|
|
22
27
|
|
|
23
28
|
completed_by: typing.Optional[int] = None
|
|
24
29
|
unique_id: typing.Optional[str] = None
|
|
25
|
-
result: typing.Optional[typing.Dict[str, typing.Any]] = pydantic_v1.Field(default=None)
|
|
26
|
-
"""
|
|
27
|
-
The main value of annotator work - labeling result in JSON format
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
30
|
was_cancelled: typing.Optional[bool] = pydantic_v1.Field(default=None)
|
|
31
31
|
"""
|
|
32
32
|
User skipped the task
|
label_studio_sdk/types/task.py
CHANGED
|
@@ -58,7 +58,7 @@ class Task(pydantic_v1.BaseModel):
|
|
|
58
58
|
Completion time of the task
|
|
59
59
|
"""
|
|
60
60
|
|
|
61
|
-
file_upload: typing.Optional[
|
|
61
|
+
file_upload: typing.Optional[str] = pydantic_v1.Field(default=None)
|
|
62
62
|
"""
|
|
63
63
|
File upload ID for this task
|
|
64
64
|
"""
|
|
@@ -5,14 +5,16 @@ from .types import (
|
|
|
5
5
|
ViewsCreateRequestDataFilters,
|
|
6
6
|
ViewsCreateRequestDataFiltersConjunction,
|
|
7
7
|
ViewsCreateRequestDataFiltersItemsItem,
|
|
8
|
+
ViewsCreateRequestDataFiltersItemsItemFilter,
|
|
9
|
+
ViewsCreateRequestDataFiltersItemsItemOperator,
|
|
8
10
|
ViewsCreateRequestDataOrderingItem,
|
|
9
|
-
ViewsCreateRequestDataOrderingItemDirection,
|
|
10
11
|
ViewsUpdateRequestData,
|
|
11
12
|
ViewsUpdateRequestDataFilters,
|
|
12
13
|
ViewsUpdateRequestDataFiltersConjunction,
|
|
13
14
|
ViewsUpdateRequestDataFiltersItemsItem,
|
|
15
|
+
ViewsUpdateRequestDataFiltersItemsItemFilter,
|
|
16
|
+
ViewsUpdateRequestDataFiltersItemsItemOperator,
|
|
14
17
|
ViewsUpdateRequestDataOrderingItem,
|
|
15
|
-
ViewsUpdateRequestDataOrderingItemDirection,
|
|
16
18
|
)
|
|
17
19
|
|
|
18
20
|
__all__ = [
|
|
@@ -20,12 +22,14 @@ __all__ = [
|
|
|
20
22
|
"ViewsCreateRequestDataFilters",
|
|
21
23
|
"ViewsCreateRequestDataFiltersConjunction",
|
|
22
24
|
"ViewsCreateRequestDataFiltersItemsItem",
|
|
25
|
+
"ViewsCreateRequestDataFiltersItemsItemFilter",
|
|
26
|
+
"ViewsCreateRequestDataFiltersItemsItemOperator",
|
|
23
27
|
"ViewsCreateRequestDataOrderingItem",
|
|
24
|
-
"ViewsCreateRequestDataOrderingItemDirection",
|
|
25
28
|
"ViewsUpdateRequestData",
|
|
26
29
|
"ViewsUpdateRequestDataFilters",
|
|
27
30
|
"ViewsUpdateRequestDataFiltersConjunction",
|
|
28
31
|
"ViewsUpdateRequestDataFiltersItemsItem",
|
|
32
|
+
"ViewsUpdateRequestDataFiltersItemsItemFilter",
|
|
33
|
+
"ViewsUpdateRequestDataFiltersItemsItemOperator",
|
|
29
34
|
"ViewsUpdateRequestDataOrderingItem",
|
|
30
|
-
"ViewsUpdateRequestDataOrderingItemDirection",
|
|
31
35
|
]
|
|
@@ -4,26 +4,30 @@ from .views_create_request_data import ViewsCreateRequestData
|
|
|
4
4
|
from .views_create_request_data_filters import ViewsCreateRequestDataFilters
|
|
5
5
|
from .views_create_request_data_filters_conjunction import ViewsCreateRequestDataFiltersConjunction
|
|
6
6
|
from .views_create_request_data_filters_items_item import ViewsCreateRequestDataFiltersItemsItem
|
|
7
|
+
from .views_create_request_data_filters_items_item_filter import ViewsCreateRequestDataFiltersItemsItemFilter
|
|
8
|
+
from .views_create_request_data_filters_items_item_operator import ViewsCreateRequestDataFiltersItemsItemOperator
|
|
7
9
|
from .views_create_request_data_ordering_item import ViewsCreateRequestDataOrderingItem
|
|
8
|
-
from .views_create_request_data_ordering_item_direction import ViewsCreateRequestDataOrderingItemDirection
|
|
9
10
|
from .views_update_request_data import ViewsUpdateRequestData
|
|
10
11
|
from .views_update_request_data_filters import ViewsUpdateRequestDataFilters
|
|
11
12
|
from .views_update_request_data_filters_conjunction import ViewsUpdateRequestDataFiltersConjunction
|
|
12
13
|
from .views_update_request_data_filters_items_item import ViewsUpdateRequestDataFiltersItemsItem
|
|
14
|
+
from .views_update_request_data_filters_items_item_filter import ViewsUpdateRequestDataFiltersItemsItemFilter
|
|
15
|
+
from .views_update_request_data_filters_items_item_operator import ViewsUpdateRequestDataFiltersItemsItemOperator
|
|
13
16
|
from .views_update_request_data_ordering_item import ViewsUpdateRequestDataOrderingItem
|
|
14
|
-
from .views_update_request_data_ordering_item_direction import ViewsUpdateRequestDataOrderingItemDirection
|
|
15
17
|
|
|
16
18
|
__all__ = [
|
|
17
19
|
"ViewsCreateRequestData",
|
|
18
20
|
"ViewsCreateRequestDataFilters",
|
|
19
21
|
"ViewsCreateRequestDataFiltersConjunction",
|
|
20
22
|
"ViewsCreateRequestDataFiltersItemsItem",
|
|
23
|
+
"ViewsCreateRequestDataFiltersItemsItemFilter",
|
|
24
|
+
"ViewsCreateRequestDataFiltersItemsItemOperator",
|
|
21
25
|
"ViewsCreateRequestDataOrderingItem",
|
|
22
|
-
"ViewsCreateRequestDataOrderingItemDirection",
|
|
23
26
|
"ViewsUpdateRequestData",
|
|
24
27
|
"ViewsUpdateRequestDataFilters",
|
|
25
28
|
"ViewsUpdateRequestDataFiltersConjunction",
|
|
26
29
|
"ViewsUpdateRequestDataFiltersItemsItem",
|
|
30
|
+
"ViewsUpdateRequestDataFiltersItemsItemFilter",
|
|
31
|
+
"ViewsUpdateRequestDataFiltersItemsItemOperator",
|
|
27
32
|
"ViewsUpdateRequestDataOrderingItem",
|
|
28
|
-
"ViewsUpdateRequestDataOrderingItemDirection",
|
|
29
33
|
]
|