label-studio-sdk 0.0.30__py3-none-any.whl → 0.0.34__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.

Files changed (38) hide show
  1. label_studio_sdk/__init__.py +4 -1
  2. label_studio_sdk/client.py +104 -85
  3. label_studio_sdk/data_manager.py +32 -23
  4. label_studio_sdk/exceptions.py +10 -0
  5. label_studio_sdk/label_interface/__init__.py +1 -0
  6. label_studio_sdk/label_interface/base.py +77 -0
  7. label_studio_sdk/label_interface/control_tags.py +756 -0
  8. label_studio_sdk/label_interface/interface.py +922 -0
  9. label_studio_sdk/label_interface/label_tags.py +72 -0
  10. label_studio_sdk/label_interface/object_tags.py +292 -0
  11. label_studio_sdk/label_interface/region.py +43 -0
  12. label_studio_sdk/objects.py +35 -0
  13. label_studio_sdk/project.py +725 -262
  14. label_studio_sdk/schema/label_config_schema.json +226 -0
  15. label_studio_sdk/users.py +15 -13
  16. label_studio_sdk/utils.py +31 -30
  17. label_studio_sdk/workspaces.py +13 -11
  18. {label_studio_sdk-0.0.30.dist-info → label_studio_sdk-0.0.34.dist-info}/METADATA +7 -5
  19. label_studio_sdk-0.0.34.dist-info/RECORD +37 -0
  20. {label_studio_sdk-0.0.30.dist-info → label_studio_sdk-0.0.34.dist-info}/WHEEL +1 -1
  21. {label_studio_sdk-0.0.30.dist-info → label_studio_sdk-0.0.34.dist-info}/top_level.txt +0 -1
  22. tests/test_client.py +21 -10
  23. tests/test_export.py +105 -0
  24. tests/test_interface/__init__.py +1 -0
  25. tests/test_interface/configs.py +137 -0
  26. tests/test_interface/mockups.py +22 -0
  27. tests/test_interface/test_compat.py +64 -0
  28. tests/test_interface/test_control_tags.py +55 -0
  29. tests/test_interface/test_data_generation.py +45 -0
  30. tests/test_interface/test_lpi.py +15 -0
  31. tests/test_interface/test_main.py +196 -0
  32. tests/test_interface/test_object_tags.py +36 -0
  33. tests/test_interface/test_region.py +36 -0
  34. tests/test_interface/test_validate_summary.py +35 -0
  35. tests/test_interface/test_validation.py +59 -0
  36. docs/__init__.py +0 -3
  37. label_studio_sdk-0.0.30.dist-info/RECORD +0 -15
  38. {label_studio_sdk-0.0.30.dist-info → label_studio_sdk-0.0.34.dist-info}/LICENSE +0 -0
@@ -0,0 +1,756 @@
1
+ """
2
+ """
3
+
4
+ import xml.etree.ElementTree
5
+ from typing import Type, Dict, Optional, List, Tuple, Any, Union
6
+ from pydantic import BaseModel, confloat
7
+
8
+ from .base import LabelStudioTag
9
+ from .region import Region
10
+ from .object_tags import ObjectTag
11
+
12
+
13
+ _NOT_CONTROL_TAGS = {
14
+ "Filter",
15
+ }
16
+
17
+ _TAG_TO_CLASS = {
18
+ "choices": "ChoicesTag",
19
+ "labels": "LabelsTag",
20
+ "brush": "BrushTag",
21
+ "brushlabels": "BrushLabelsTag",
22
+ "ellipse": "EllipseTag",
23
+ "ellipselabels": "EllipseLabelsTag",
24
+ "keypoint": "KeyPointTag",
25
+ "keypointlabels": "KeyPointLabelsTag",
26
+ "polygon": "PolygonTag",
27
+ "polygonlabels": "PolygonLabelsTag",
28
+ "rectangle": "RectangleTag",
29
+ "rectanglelabels": "RectangleLabelsTag",
30
+ "videorectangle": "VideoRectangleTag",
31
+ "number": "NumberTag",
32
+ "datetime": "DateTimeTag",
33
+ "hypertext": "HyperTextLabelsTag",
34
+ "pairwise": "PairwiseTag",
35
+ "paragraphlabels": "ParagraphLabelsTag",
36
+ "ranker": "RankerTag",
37
+ "rating": "RatingTag",
38
+ "relations": "RelationsTag",
39
+ "taxonomy": "TaxonomyTag",
40
+ "textarea": "TextAreaTag",
41
+ "timeserieslabels": "TimeSeriesLabelsTag",
42
+ }
43
+
44
+
45
+ def get_tag_class(name):
46
+ """ """
47
+ class_name = _TAG_TO_CLASS.get(name.lower())
48
+ return globals().get(class_name, None)
49
+
50
+
51
+ class ControlTag(LabelStudioTag):
52
+ """
53
+ Class that represents a ControlTag in Label Studio
54
+
55
+ Attributes:
56
+ -----------
57
+ name : Optional[str]
58
+ The name of the tag
59
+ to_name : Optional[List[str]]
60
+ The name of the object the tag maps to
61
+ conditionals : Optional[Dict[str, Any]]
62
+ Conditional attributes
63
+ dynamic_value : Optional[bool]
64
+ A flag to indicate if the value is dynamic
65
+ objects : Optional[Any]
66
+ The object tag that the control tag maps to
67
+ labels : Optional[Any]
68
+ The labels for the control tag
69
+ labels_attrs : Optional[Any]
70
+ The labels attributes for the control tag
71
+ """
72
+
73
+ name: Optional[str] = None
74
+ to_name: Optional[List[str]] = None
75
+ conditionals: Optional[Dict[str, Any]] = None
76
+ dynamic_value: Optional[bool] = False
77
+ objects: Optional[Any] = None
78
+ labels: Optional[Any] = None
79
+ labels_attrs: Optional[Any] = None
80
+
81
+ @classmethod
82
+ def validate_node(cls, tag: xml.etree.ElementTree.Element) -> bool:
83
+ """
84
+ Naive check if tag is a control tag
85
+
86
+ Parameters:
87
+ -----------
88
+ tag : xml.etree.ElementTree.Element
89
+ The tag to validate
90
+
91
+ Returns:
92
+ --------
93
+ bool
94
+ True if tag is a control tag, False otherwise
95
+ """
96
+ return bool(
97
+ tag.attrib.get("name")
98
+ and tag.attrib.get("toName")
99
+ and tag.tag not in _NOT_CONTROL_TAGS
100
+ )
101
+
102
+ @classmethod
103
+ def parse_node(cls, tag: xml.etree.ElementTree.Element) -> "ControlTag":
104
+ """
105
+ Parse tag into a tag info
106
+
107
+ Parameters:
108
+ -----------
109
+ tag : xml.etree.ElementTree.Element
110
+ The tag to parse
111
+
112
+ Returns:
113
+ --------
114
+ ControlTag
115
+ The parsed tag
116
+ """
117
+ tag_class = get_tag_class(tag.tag) or cls
118
+
119
+ tag_info = {
120
+ "tag": tag.tag,
121
+ "name": tag.attrib["name"],
122
+ "to_name": tag.attrib["toName"].split(","),
123
+ "attr": dict(tag.attrib),
124
+ }
125
+
126
+ # Grab conditionals if any
127
+ conditionals = {}
128
+ if tag.attrib.get("perRegion") == "true":
129
+ if tag.attrib.get("whenTagName"):
130
+ conditionals = {"type": "tag", "name": tag.attrib["whenTagName"]}
131
+ elif tag.attrib.get("whenLabelValue"):
132
+ conditionals = {
133
+ "type": "label",
134
+ "name": tag.attrib["whenLabelValue"],
135
+ }
136
+ elif tag.attrib.get("whenChoiceValue"):
137
+ conditionals = {
138
+ "type": "choice",
139
+ "name": tag.attrib["whenChoiceValue"],
140
+ }
141
+
142
+ if conditionals:
143
+ tag_info["conditionals"] = conditionals
144
+
145
+ if tag.attrib.get("value", "empty")[0] == "$" or tag.attrib.get("apiUrl"):
146
+ tag_info["dynamic_value"] = True
147
+
148
+ return tag_class(**tag_info)
149
+
150
+ def get_object(self, name=None):
151
+ """
152
+ This method retrieves the object tag that the control tag maps to.
153
+
154
+ Parameters:
155
+ -----------
156
+ name : Optional[str]
157
+ The name of the object tag to retrieve. If not provided, the method will return the first object tag.
158
+
159
+ Returns:
160
+ --------
161
+ Any
162
+ The object tag that the control tag maps to.
163
+ """
164
+ return self.get_input(name=name)
165
+
166
+ def get_input(self, name=None):
167
+ """
168
+ This method retrieves the object tag that the control tag maps to based on the provided name.
169
+
170
+ Parameters:
171
+ -----------
172
+ name : Optional[str]
173
+ The name of the object tag to retrieve. If not provided, the method will return the first object tag if there is only one.
174
+ If there are multiple object tags and no name is provided, an exception will be raised.
175
+
176
+ Returns:
177
+ --------
178
+ Any
179
+ The object tag that the control tag maps to.
180
+
181
+ Raises:
182
+ -------
183
+ Exception
184
+ If the provided name is not found in the object tags or if there are multiple object tags and no name is provided.
185
+ """
186
+ if name is not None:
187
+ if name not in self.to_name:
188
+ raise Exception(f"Object name {name} is not found")
189
+
190
+ for tag in self.objects:
191
+ if tag.name == name:
192
+ return tag
193
+
194
+ if len(self.objects) > 1:
195
+ raise Exception("Multiple object tags connected, you should specify name")
196
+
197
+ return self.objects[0]
198
+
199
+ def set_labels_attrs(self, labels_attrs):
200
+ """
201
+ This method sets the labels attributes for the ControlTag.
202
+
203
+ Parameters:
204
+ -----------
205
+ labels_attrs : Any
206
+ The labels attributes to set for the ControlTag.
207
+ """
208
+ self.labels_attrs = labels_attrs
209
+
210
+ def set_object(self, tag: ObjectTag):
211
+ """
212
+ This method sets the object tag that the control tag maps to.
213
+
214
+ Parameters:
215
+ -----------
216
+ tag : ObjectTag
217
+ The object tag to set for the control tag.
218
+ """
219
+ self.set_objects([tag])
220
+
221
+ def set_objects(self, objects: List[ObjectTag]):
222
+ """
223
+ This method sets the object tags that the control tag maps to.
224
+
225
+ Parameters:
226
+ -----------
227
+ objects : List[ObjectTag]
228
+ The object tags to set for the control tag.
229
+ """
230
+ self.objects = objects
231
+
232
+ def set_labels(self, labels: List[str]):
233
+ """
234
+ Set labels for the ControlTag
235
+ """
236
+ self.labels = labels
237
+
238
+ def find_object_by_name(self, name: str) -> Optional[ObjectTag]:
239
+ """
240
+ This method finds and returns an object tag with the specified name from the list of object tags.
241
+
242
+ Parameters:
243
+ -----------
244
+ name : str
245
+ The name of the object tag to find.
246
+
247
+ Returns:
248
+ --------
249
+ Optional[ObjectTag]
250
+ The object tag with the specified name if found, None otherwise.
251
+ """
252
+ for obj in self.objects:
253
+ if obj.name == name:
254
+ return obj
255
+
256
+ def _validate_labels(self, labels):
257
+ """Check that labels is a subset of self.labels, used for
258
+ example when you're validate the annotaion or prediction to
259
+ make sure there no undefined labels used.
260
+
261
+ """
262
+ if not self.labels:
263
+ return True
264
+
265
+ return set(labels).issubset(set(self.labels))
266
+
267
+ def _validate_value_labels(self, value):
268
+ """ """
269
+ if self._label_attr_name not in value:
270
+ return False
271
+
272
+ return self._validate_labels(value.get(self._label_attr_name))
273
+
274
+ def validate_value(self, value: dict) -> bool:
275
+ """
276
+ Given "value" from [annotation result format](https://labelstud.io/guide/task_format),
277
+ validate if it's a valid value for this control tag.
278
+
279
+ Parameters:
280
+ -----------
281
+ value : dict
282
+ The value to validate
283
+ Example:
284
+ ```python
285
+ RectangleTag(name="rect", to_name=["img"], tag="rectangle", attr={}).validate_value({"x": 10, "y": 10, "width": 10, "height": 10, "rotation": 10})
286
+ ```
287
+
288
+ Returns:
289
+ --------
290
+ bool
291
+ True if the value is valid, False otherwise
292
+ """
293
+ if hasattr(self, "_label_attr_name"):
294
+ if not self._validate_value_labels(value):
295
+ return False
296
+
297
+ try:
298
+ inst = self._value_class(**value)
299
+ return True
300
+ except Exception as e:
301
+ return False
302
+
303
+ def _resolve_to_name(self, to_name: Optional[str] = None) -> str:
304
+ """
305
+ This method resolves the name of the object tag that the control tag maps to.
306
+ If a name is provided and it is found in the control tag, it is returned.
307
+ If no name is provided and there is only one object tag, its name is returned.
308
+ If no name is provided and there are multiple object tags, an exception is raised.
309
+
310
+ Parameters:
311
+ -----------
312
+ to_name : Optional[str]
313
+ The name of the object tag to resolve. If not provided, the method will return the name of the first object tag if there is only one.
314
+
315
+ Returns:
316
+ --------
317
+ str
318
+ The name of the object tag that the control tag maps to.
319
+
320
+ Raises:
321
+ -------
322
+ Exception
323
+ If the provided name is not found in the object tags or if there are multiple object tags and no name is provided.
324
+ """
325
+ if to_name:
326
+ if to_name not in self.to_name:
327
+ raise Exception("To name is not found in control tag")
328
+
329
+ return to_name
330
+ else:
331
+ if len(self.to_name) > 1:
332
+ raise Exception(
333
+ "Multiple to_name in control tag, specify to_name in function"
334
+ )
335
+
336
+ return self.to_name[0]
337
+
338
+ def _label_simple(self, to_name: Optional[str] = None, *args, **kwargs) -> Region:
339
+ """
340
+ This method creates a new Region object with the specified label applied.
341
+ It first resolves the name of the object tag that the control tag maps to.
342
+ Then it finds the object tag with the resolved name.
343
+ It creates a new instance of the value class with the provided arguments and keyword arguments.
344
+ Finally, it returns a new Region object with the current control tag, the found object tag, and the created value.
345
+
346
+ Parameters:
347
+ -----------
348
+ to_name : Optional[str]
349
+ The name of the object tag to resolve. If not provided, the method will return the name of the first object tag if there is only one.
350
+ *args : tuple
351
+ Variable length argument list.
352
+ **kwargs : dict
353
+ Arbitrary keyword arguments.
354
+
355
+ Returns:
356
+ --------
357
+ Region
358
+ A new Region object with the specified label applied.
359
+ """
360
+ to_name = self._resolve_to_name(to_name)
361
+ obj = self.find_object_by_name(to_name)
362
+ cls = self._value_class
363
+ value = cls(**kwargs)
364
+
365
+ return Region(from_tag=self, to_tag=obj, value=value)
366
+
367
+ def _label_with_labels(
368
+ self,
369
+ label: Union[str, List[str]] = None,
370
+ to_name: Optional[str] = None,
371
+ *args,
372
+ **kwargs,
373
+ ) -> Region:
374
+ """
375
+ This method creates a new Region object with the specified label applied.
376
+ It first checks if the label is a string and if so, converts it to a list.
377
+ Then it validates the labels and raises an exception if they are not valid.
378
+ It adds the labels to the keyword arguments under the attribute name for labels.
379
+ Finally, it calls the _label_simple method to create and return a new Region object.
380
+
381
+ Parameters:
382
+ -----------
383
+ label : Union[str, List[str]], optional
384
+ The label to be applied. If a string is provided, it is converted to a list.
385
+ to_name : Optional[str], optional
386
+ The name of the object tag to resolve. If not provided, the method will return the name of the first object tag if there is only one.
387
+ *args : tuple
388
+ Variable length argument list.
389
+ **kwargs : dict
390
+ Arbitrary keyword arguments.
391
+
392
+ Returns:
393
+ --------
394
+ Region
395
+ A new Region object with the specified label applied.
396
+
397
+ Raises:
398
+ -------
399
+ Exception
400
+ If the labels are not valid.
401
+ """
402
+ if isinstance(label, str):
403
+ label = [label]
404
+
405
+ if not self._validate_labels(label):
406
+ raise ValueError(
407
+ f"Using labels not defined in labeling config, possible values: {set(self.labels)}"
408
+ )
409
+
410
+ kwargs[self._label_attr_name] = label
411
+
412
+ return self._label_simple(to_name=to_name, **kwargs)
413
+
414
+ def label(
415
+ self,
416
+ label: Optional[Union[str, List[str]]] = None,
417
+ to_name: Optional[str] = None,
418
+ *args,
419
+ **kwargs,
420
+ ) -> Region:
421
+ """
422
+ This method creates a new Region object with the specified label applied.
423
+ If not labels are provided, it creates a new instance of the value class with the provided arguments and keyword arguments.
424
+ If labels are provided, it creates a new instance of the value class with the provided arguments and keyword arguments and adds the labels to the Region object.
425
+
426
+ Parameters:
427
+ -----------
428
+ label : Optional[Union[str, List[str]]]
429
+ The label to be applied. If a string is provided, it is converted to a list.
430
+ to_name : Optional[str]
431
+ The name of the object tag to resolve. If not provided, the method will return the name of the first object tag if there is only one.
432
+ *args : tuple
433
+ Variable length argument list.
434
+ **kwargs : dict
435
+ Arbitrary keyword arguments.
436
+
437
+ Returns:
438
+ --------
439
+ Region
440
+ A new Region object with the specified label applied.
441
+ """
442
+ if hasattr(self, "_label_attr_name"):
443
+ return self._label_with_labels(
444
+ label=label, to_name=to_name, *args, **kwargs
445
+ )
446
+ else:
447
+ return self._label_simple(to_name=to_name, *args, **kwargs)
448
+
449
+ def as_tuple(self):
450
+ """ """
451
+ from_name = self.name
452
+ to_name = self.to_name
453
+ tag_type = self.tag
454
+
455
+ if isinstance(to_name, list):
456
+ to_name = ",".join(to_name)
457
+
458
+ return "|".join([from_name, to_name, type.lower()])
459
+
460
+
461
+ class SpanSelection(BaseModel):
462
+ start: str
463
+ end: str
464
+
465
+
466
+ class SpanSelectionOffsets(SpanSelection):
467
+ startOffset: int
468
+ endOffset: int
469
+
470
+
471
+ class ChoicesValue(BaseModel):
472
+ choices: List[str]
473
+
474
+
475
+ class ChoicesTag(ControlTag):
476
+ """ """
477
+
478
+ _label_attr_name: str = "choices"
479
+ _value_class: Type[ChoicesValue] = ChoicesValue
480
+
481
+
482
+ class LabelsValue(SpanSelection):
483
+ labels: List[str]
484
+
485
+
486
+ class LabelsTag(ControlTag):
487
+ """ """
488
+
489
+ _label_attr_name: str = "labels"
490
+ _value_class: Type[LabelsValue] = LabelsValue
491
+
492
+
493
+ ## Image tags
494
+
495
+
496
+ class BrushValue(BaseModel):
497
+ format: str
498
+ rle: List[int]
499
+
500
+
501
+ class BrushLabelsValue(BrushValue):
502
+ brushlabels: List[str]
503
+
504
+
505
+ class BrushTag(ControlTag):
506
+ """ """
507
+
508
+ _value_class: Type[BrushValue] = BrushValue
509
+
510
+
511
+ class BrushLabelsTag(ControlTag):
512
+ """ """
513
+
514
+ _label_attr_name: str = "brushlabels"
515
+ _value_class: Type[BrushLabelsValue] = BrushLabelsValue
516
+
517
+
518
+ class EllipseValue(BaseModel):
519
+ x: confloat(le=100)
520
+ y: confloat(le=100)
521
+ radiusX: confloat(le=50)
522
+ radiusY: confloat(le=50)
523
+ rotation: confloat(le=360) = 0
524
+
525
+
526
+ class EllipseLabelsValue(EllipseValue):
527
+ ellipselabels: List[str]
528
+
529
+
530
+ class EllipseTag(ControlTag):
531
+ """ """
532
+
533
+ _value_class: Type[EllipseValue] = EllipseValue
534
+
535
+
536
+ class EllipseLabelsTag(ControlTag):
537
+ """ """
538
+
539
+ _label_attr_name: str = "ellipselabels"
540
+ _value_class: Type[EllipseLabelsValue] = EllipseLabelsValue
541
+
542
+
543
+ class KeyPointValue(BaseModel):
544
+ x: confloat(le=100)
545
+ y: confloat(le=100)
546
+
547
+
548
+ class KeyPointLabelsValue(KeyPointValue):
549
+ keypointlabels: List[str]
550
+
551
+
552
+ class KeyPointTag(ControlTag):
553
+ """ """
554
+
555
+ _value_class: Type[KeyPointValue] = KeyPointValue
556
+
557
+
558
+ class KeyPointLabelsTag(ControlTag):
559
+ """ """
560
+
561
+ _label_attr_name: str = "keypointlabels"
562
+ _value_class: Type[KeyPointLabelsValue] = KeyPointLabelsValue
563
+
564
+
565
+ class PolygonValue(BaseModel):
566
+ points: Tuple[confloat(le=100), confloat(le=100)]
567
+
568
+
569
+ class PolygonLabelsValue(PolygonValue):
570
+ polygonlabels: List[str]
571
+
572
+
573
+ class PolygonTag(ControlTag):
574
+ """ """
575
+
576
+ _value_class: Type[PolygonValue] = PolygonValue
577
+
578
+ def label(self, *args, **kwargs):
579
+ """ """
580
+
581
+
582
+ class PolygonLabelsTag(ControlTag):
583
+ """ """
584
+
585
+ _label_attr_name: str = "polygonlabels"
586
+ _value_class: Type[PolygonLabelsValue] = PolygonLabelsValue
587
+
588
+
589
+ class RectangleValue(BaseModel):
590
+ x: confloat(le=100)
591
+ y: confloat(le=100)
592
+ width: confloat(le=100)
593
+ height: confloat(le=100)
594
+ rotation: confloat(le=360)
595
+
596
+
597
+ class RectangleLabelsValue(RectangleValue):
598
+ rectanglelabels: List[str]
599
+
600
+
601
+ class RectangleTag(ControlTag):
602
+ """ """
603
+
604
+ _value_class: Type[RectangleValue] = RectangleValue
605
+
606
+
607
+ class RectangleLabelsTag(ControlTag):
608
+ """ """
609
+
610
+ _label_attr_name: str = "rectanglelabels"
611
+ _value_class: Type[RectangleLabelsValue] = RectangleLabelsValue
612
+
613
+
614
+ class VideoRectangleValue(BaseModel):
615
+ x: float
616
+ y: float
617
+ width: float
618
+ height: float
619
+ rotation: float
620
+
621
+
622
+ class VideoRectangleTag(ControlTag):
623
+ """ """
624
+
625
+ _value_class: Type[VideoRectangleValue] = VideoRectangleValue
626
+
627
+
628
+ class NumberTag(ControlTag):
629
+ """ """
630
+
631
+ def validate_value(self, value) -> bool:
632
+ """ """
633
+ # TODO implement
634
+ return True
635
+
636
+ def label(self, *args, **kwargs):
637
+ """ """
638
+
639
+
640
+ class DateTimeTag(ControlTag):
641
+ """ """
642
+
643
+ def validate_value(self, value) -> bool:
644
+ """ """
645
+ # TODO implement
646
+ return True
647
+
648
+ def label(self, *args, **kwargs):
649
+ """ """
650
+
651
+
652
+ class HyperTextLabelsValue(SpanSelectionOffsets):
653
+ htmllabels: List[str]
654
+
655
+
656
+ class HyperTextLabelsTag(ControlTag):
657
+ """ """
658
+
659
+ _label_attr_name: str = "htmllabels"
660
+ _value_class: Type[HyperTextLabelsValue] = HyperTextLabelsValue
661
+
662
+
663
+ class PairwiseValue(BaseModel):
664
+ selected: str
665
+
666
+
667
+ class PairwiseTag(ControlTag):
668
+ """ """
669
+
670
+ _value_class: Type[PairwiseValue] = PairwiseValue
671
+
672
+ def label(self, *args, **kwargs):
673
+ """ """
674
+
675
+
676
+ class ParagraphLabelsValue(SpanSelectionOffsets):
677
+ paragraphlabels: List[str]
678
+
679
+
680
+ class ParagraphLabelsTag(ControlTag):
681
+ """ """
682
+
683
+ _label_attr_name: str = "paragraphlabels"
684
+ _value_class: Type[ParagraphLabelsValue] = ParagraphLabelsValue
685
+
686
+ def label(self, *args, **kwargs):
687
+ """ """
688
+
689
+
690
+ class RankerTag(ControlTag):
691
+ """ """
692
+
693
+ def validate_value(self, value) -> bool:
694
+ """ """
695
+ # TODO
696
+ return True
697
+
698
+ def label(self, *args, **kwargs):
699
+ """ """
700
+
701
+
702
+ class RatingValue(BaseModel):
703
+ rating: int
704
+
705
+
706
+ class RatingTag(ControlTag):
707
+ """ """
708
+
709
+ _value_class: Type[RatingValue] = RatingValue
710
+
711
+ def label(self, *args, **kwargs):
712
+ """ """
713
+
714
+
715
+ class RelationsTag(ControlTag):
716
+ """ """
717
+
718
+ def validate_value(self, value) -> bool:
719
+ """ """
720
+ # TODO
721
+ return True
722
+
723
+ def label(self, *args, **kwargs):
724
+ """ """
725
+
726
+
727
+ class TaxonomyValue(BaseModel):
728
+ taxonomy: List[List[str]]
729
+
730
+
731
+ class TaxonomyTag(ControlTag):
732
+ """ """
733
+
734
+ _value_class: Type[TaxonomyValue] = TaxonomyValue
735
+
736
+
737
+ class TextAreaValue(BaseModel):
738
+ text: List[str]
739
+
740
+
741
+ class TextAreaTag(ControlTag):
742
+ """ """
743
+
744
+ _value_class: Type[TextAreaValue] = TextAreaValue
745
+
746
+
747
+ class TimeSeriesValue(SpanSelection):
748
+ instant: bool
749
+ timeserieslabels: List[str]
750
+
751
+
752
+ class TimeSeriesLabelsTag(ControlTag):
753
+ """ """
754
+
755
+ _label_attr_name: str = "timeserieslabels"
756
+ _value_class: Type[TimeSeriesValue] = TimeSeriesValue