label-studio-sdk 2.0.6__py3-none-any.whl → 2.0.8__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 (67) hide show
  1. label_studio_sdk/__init__.py +32 -2
  2. label_studio_sdk/base_client.py +4 -0
  3. label_studio_sdk/converter/exports/yolo.py +89 -74
  4. label_studio_sdk/core/client_wrapper.py +1 -1
  5. label_studio_sdk/import_storage/azure_spi/client.py +30 -0
  6. label_studio_sdk/label_interface/control_tags.py +38 -0
  7. label_studio_sdk/label_interface/data_examples.json +10 -0
  8. label_studio_sdk/label_interface/interface.py +13 -0
  9. label_studio_sdk/label_interface/object_tags.py +9 -0
  10. label_studio_sdk/ml/client.py +124 -0
  11. label_studio_sdk/organizations/__init__.py +3 -2
  12. label_studio_sdk/organizations/client.py +540 -1
  13. label_studio_sdk/organizations/invites/__init__.py +2 -0
  14. label_studio_sdk/organizations/invites/client.py +368 -0
  15. label_studio_sdk/organizations/permissions/__init__.py +2 -0
  16. label_studio_sdk/organizations/permissions/client.py +1129 -0
  17. label_studio_sdk/organizations/types/__init__.py +5 -0
  18. label_studio_sdk/organizations/types/patched_default_role_request_custom_scripts_editable_by.py +7 -0
  19. label_studio_sdk/project_templates/__init__.py +2 -0
  20. label_studio_sdk/project_templates/client.py +909 -0
  21. label_studio_sdk/projects/__init__.py +30 -0
  22. label_studio_sdk/projects/client.py +355 -0
  23. label_studio_sdk/projects/stats/__init__.py +28 -0
  24. label_studio_sdk/projects/stats/client.py +1002 -43
  25. label_studio_sdk/projects/stats/types/__init__.py +30 -0
  26. label_studio_sdk/projects/stats/types/stats_agreement_annotator_response.py +26 -0
  27. label_studio_sdk/projects/stats/types/stats_data_filters_response.py +23 -0
  28. label_studio_sdk/projects/stats/types/stats_data_filters_response_user_filters.py +34 -0
  29. label_studio_sdk/projects/stats/types/stats_data_filters_response_user_filters_stats_item.py +22 -0
  30. label_studio_sdk/projects/stats/types/stats_finished_tasks_response.py +32 -0
  31. label_studio_sdk/projects/stats/types/stats_lead_time_response.py +23 -0
  32. label_studio_sdk/projects/stats/types/stats_lead_time_response_lead_time_stats_item.py +37 -0
  33. label_studio_sdk/projects/stats/types/stats_user_ground_truth_agreement_response.py +20 -0
  34. label_studio_sdk/projects/stats/types/stats_user_ground_truth_agreement_response_agreement.py +5 -0
  35. label_studio_sdk/projects/stats/types/stats_user_prediction_agreement_response.py +24 -0
  36. label_studio_sdk/projects/stats/types/stats_user_prediction_agreement_response_average_prediction_agreement_per_user.py +5 -0
  37. label_studio_sdk/projects/stats/types/stats_user_review_score_response.py +22 -0
  38. label_studio_sdk/projects/stats/types/stats_user_review_score_response_performance_score.py +5 -0
  39. label_studio_sdk/projects/stats/types/stats_user_review_score_response_review_score.py +5 -0
  40. label_studio_sdk/projects/types/__init__.py +2 -0
  41. label_studio_sdk/projects/types/projects_import_predictions_response.py +26 -0
  42. label_studio_sdk/prompts/versions/client.py +4 -16
  43. label_studio_sdk/types/__init__.py +26 -2
  44. label_studio_sdk/types/azure_service_principal_import_storage.py +5 -0
  45. label_studio_sdk/types/azure_service_principal_import_storage_request.py +5 -0
  46. label_studio_sdk/types/configurable_permission_option.py +25 -0
  47. label_studio_sdk/types/configurable_permission_option_default.py +7 -0
  48. label_studio_sdk/types/default_role.py +75 -0
  49. label_studio_sdk/types/default_role_custom_scripts_editable_by.py +7 -0
  50. label_studio_sdk/types/lse_organization.py +2 -2
  51. label_studio_sdk/types/lse_project.py +223 -0
  52. label_studio_sdk/types/lse_project_counts.py +46 -0
  53. label_studio_sdk/types/lse_project_sampling.py +7 -0
  54. label_studio_sdk/types/lse_project_skip_queue.py +7 -0
  55. label_studio_sdk/types/lse_task.py +1 -1
  56. label_studio_sdk/types/lse_task_serializer_for_reviewers.py +1 -1
  57. label_studio_sdk/types/organization_permission.py +31 -0
  58. label_studio_sdk/types/organization_permission_request.py +24 -0
  59. label_studio_sdk/types/paginated_lse_project_counts_list.py +23 -0
  60. label_studio_sdk/types/project_template.py +41 -0
  61. label_studio_sdk/types/project_template_request.py +38 -0
  62. label_studio_sdk/types/who_am_i_user.py +1 -0
  63. {label_studio_sdk-2.0.6.dist-info → label_studio_sdk-2.0.8.dist-info}/METADATA +1 -1
  64. {label_studio_sdk-2.0.6.dist-info → label_studio_sdk-2.0.8.dist-info}/RECORD +66 -31
  65. label_studio_sdk/types/default_role_enum.py +0 -5
  66. {label_studio_sdk-2.0.6.dist-info → label_studio_sdk-2.0.8.dist-info}/LICENSE +0 -0
  67. {label_studio_sdk-2.0.6.dist-info → label_studio_sdk-2.0.8.dist-info}/WHEEL +0 -0
@@ -42,11 +42,14 @@ from .types import (
42
42
  Comment,
43
43
  CommentRequest,
44
44
  CommentSerializerWithExpandedUser,
45
+ ConfigurablePermissionOption,
46
+ ConfigurablePermissionOptionDefault,
45
47
  ConvertedFormat,
46
48
  ConvertedFormatRequest,
47
49
  CountLimit,
48
50
  CustomScriptsEditableByEnum,
49
- DefaultRoleEnum,
51
+ DefaultRole,
52
+ DefaultRoleCustomScriptsEditableBy,
50
53
  EditionEnum,
51
54
  Export,
52
55
  FileUpload,
@@ -78,9 +81,13 @@ from .types import (
78
81
  LseOrganization,
79
82
  LseOrganizationCustomScriptsEditableBy,
80
83
  LseOrganizationMemberList,
84
+ LseProject,
85
+ LseProjectCounts,
81
86
  LseProjectCreate,
82
87
  LseProjectCreateSampling,
83
88
  LseProjectCreateSkipQueue,
89
+ LseProjectSampling,
90
+ LseProjectSkipQueue,
84
91
  LseProjectUpdate,
85
92
  LseProjectUpdateSampling,
86
93
  LseProjectUpdateSkipQueue,
@@ -132,9 +139,12 @@ from .types import (
132
139
  OrganizationInvite,
133
140
  OrganizationMember,
134
141
  OrganizationMembership,
142
+ OrganizationPermission,
143
+ OrganizationPermissionRequest,
135
144
  PaginatedAllRolesProjectListList,
136
145
  PaginatedAnnotationHistoryList,
137
146
  PaginatedLseOrganizationMemberListList,
147
+ PaginatedLseProjectCountsList,
138
148
  PaginatedLseUserList,
139
149
  PaginatedPaginatedProjectMemberList,
140
150
  PaginatedProjectMember,
@@ -154,6 +164,8 @@ from .types import (
154
164
  ProjectSampling,
155
165
  ProjectSkipQueue,
156
166
  ProjectSubsetEnum,
167
+ ProjectTemplate,
168
+ ProjectTemplateRequest,
157
169
  PromptsStatusEnum,
158
170
  ProviderEnum,
159
171
  ReasonEnum,
@@ -238,6 +250,7 @@ from . import (
238
250
  model_providers,
239
251
  organizations,
240
252
  predictions,
253
+ project_templates,
241
254
  projects,
242
255
  prompts,
243
256
  session_policy,
@@ -275,12 +288,14 @@ from .export_storage import ExportStorageListTypesResponseItem
275
288
  from .import_storage import ImportStorageListTypesResponseItem
276
289
  from .ml import MlCreateRequestAuthMethod, MlListModelVersionsResponse, MlUpdateRequestAuthMethod
277
290
  from .model_providers import ModelProvidersListModelProviderChoicesResponse
291
+ from .organizations import PatchedDefaultRoleRequestCustomScriptsEditableBy
278
292
  from .projects import (
279
293
  LseProjectCreateRequestSampling,
280
294
  LseProjectCreateRequestSkipQueue,
281
295
  PatchedLseProjectUpdateRequestSampling,
282
296
  PatchedLseProjectUpdateRequestSkipQueue,
283
297
  ProjectsDuplicateResponse,
298
+ ProjectsImportPredictionsResponse,
284
299
  ProjectsImportTasksResponse,
285
300
  ProjectsListRequestFilter,
286
301
  )
@@ -387,11 +402,14 @@ __all__ = [
387
402
  "Comment",
388
403
  "CommentRequest",
389
404
  "CommentSerializerWithExpandedUser",
405
+ "ConfigurablePermissionOption",
406
+ "ConfigurablePermissionOptionDefault",
390
407
  "ConvertedFormat",
391
408
  "ConvertedFormatRequest",
392
409
  "CountLimit",
393
410
  "CustomScriptsEditableByEnum",
394
- "DefaultRoleEnum",
411
+ "DefaultRole",
412
+ "DefaultRoleCustomScriptsEditableBy",
395
413
  "EditionEnum",
396
414
  "Export",
397
415
  "ExportStorageListTypesResponseItem",
@@ -429,11 +447,15 @@ __all__ = [
429
447
  "LseOrganization",
430
448
  "LseOrganizationCustomScriptsEditableBy",
431
449
  "LseOrganizationMemberList",
450
+ "LseProject",
451
+ "LseProjectCounts",
432
452
  "LseProjectCreate",
433
453
  "LseProjectCreateRequestSampling",
434
454
  "LseProjectCreateRequestSkipQueue",
435
455
  "LseProjectCreateSampling",
436
456
  "LseProjectCreateSkipQueue",
457
+ "LseProjectSampling",
458
+ "LseProjectSkipQueue",
437
459
  "LseProjectUpdate",
438
460
  "LseProjectUpdateSampling",
439
461
  "LseProjectUpdateSkipQueue",
@@ -491,13 +513,17 @@ __all__ = [
491
513
  "OrganizationInvite",
492
514
  "OrganizationMember",
493
515
  "OrganizationMembership",
516
+ "OrganizationPermission",
517
+ "OrganizationPermissionRequest",
494
518
  "PaginatedAllRolesProjectListList",
495
519
  "PaginatedAnnotationHistoryList",
496
520
  "PaginatedLseOrganizationMemberListList",
521
+ "PaginatedLseProjectCountsList",
497
522
  "PaginatedLseUserList",
498
523
  "PaginatedPaginatedProjectMemberList",
499
524
  "PaginatedProjectMember",
500
525
  "PaginatedRoleBasedTaskList",
526
+ "PatchedDefaultRoleRequestCustomScriptsEditableBy",
501
527
  "PatchedLseProjectUpdateRequestSampling",
502
528
  "PatchedLseProjectUpdateRequestSkipQueue",
503
529
  "Pause",
@@ -515,7 +541,10 @@ __all__ = [
515
541
  "ProjectSampling",
516
542
  "ProjectSkipQueue",
517
543
  "ProjectSubsetEnum",
544
+ "ProjectTemplate",
545
+ "ProjectTemplateRequest",
518
546
  "ProjectsDuplicateResponse",
547
+ "ProjectsImportPredictionsResponse",
519
548
  "ProjectsImportTasksResponse",
520
549
  "ProjectsListRequestFilter",
521
550
  "PromptsCompatibleProjectsRequestProjectType",
@@ -630,6 +659,7 @@ __all__ = [
630
659
  "model_providers",
631
660
  "organizations",
632
661
  "predictions",
662
+ "project_templates",
633
663
  "projects",
634
664
  "prompts",
635
665
  "session_policy",
@@ -23,6 +23,7 @@ from .ml.client import MlClient
23
23
  from .model_providers.client import ModelProvidersClient
24
24
  from .prompts.client import PromptsClient
25
25
  from .predictions.client import PredictionsClient
26
+ from .project_templates.client import ProjectTemplatesClient
26
27
  from .projects.client import ProjectsClient
27
28
  from .tasks.client import TasksClient
28
29
  from .session_policy.client import SessionPolicyClient
@@ -51,6 +52,7 @@ from .ml.client import AsyncMlClient
51
52
  from .model_providers.client import AsyncModelProvidersClient
52
53
  from .prompts.client import AsyncPromptsClient
53
54
  from .predictions.client import AsyncPredictionsClient
55
+ from .project_templates.client import AsyncProjectTemplatesClient
54
56
  from .projects.client import AsyncProjectsClient
55
57
  from .tasks.client import AsyncTasksClient
56
58
  from .session_policy.client import AsyncSessionPolicyClient
@@ -142,6 +144,7 @@ class LabelStudioBase:
142
144
  self.model_providers = ModelProvidersClient(client_wrapper=self._client_wrapper)
143
145
  self.prompts = PromptsClient(client_wrapper=self._client_wrapper)
144
146
  self.predictions = PredictionsClient(client_wrapper=self._client_wrapper)
147
+ self.project_templates = ProjectTemplatesClient(client_wrapper=self._client_wrapper)
145
148
  self.projects = ProjectsClient(client_wrapper=self._client_wrapper)
146
149
  self.tasks = TasksClient(client_wrapper=self._client_wrapper)
147
150
  self.session_policy = SessionPolicyClient(client_wrapper=self._client_wrapper)
@@ -233,6 +236,7 @@ class AsyncLabelStudioBase:
233
236
  self.model_providers = AsyncModelProvidersClient(client_wrapper=self._client_wrapper)
234
237
  self.prompts = AsyncPromptsClient(client_wrapper=self._client_wrapper)
235
238
  self.predictions = AsyncPredictionsClient(client_wrapper=self._client_wrapper)
239
+ self.project_templates = AsyncProjectTemplatesClient(client_wrapper=self._client_wrapper)
236
240
  self.projects = AsyncProjectsClient(client_wrapper=self._client_wrapper)
237
241
  self.tasks = AsyncTasksClient(client_wrapper=self._client_wrapper)
238
242
  self.session_policy = AsyncSessionPolicyClient(client_wrapper=self._client_wrapper)
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  from label_studio_sdk.converter.utils import convert_annotation_to_yolo, convert_annotation_to_yolo_obb
3
4
  from label_studio_sdk.converter.keypoints import build_kp_order
4
5
 
@@ -69,81 +70,95 @@ def process_and_save_yolo_annotations(labels, label_path, category_name_to_id, c
69
70
  process_keypoints_for_yolo(labels, label_path, category_name_to_id, categories, is_obb, kp_order)
70
71
  return categories, category_name_to_id
71
72
 
72
- annotations = []
73
- for label in labels:
74
- category_name = None
75
- category_names = [] # considering multi-label
76
- for key in ["rectanglelabels", "polygonlabels", "labels"]:
77
- if key in label and len(label[key]) > 0:
78
- # change to save multi-label
79
- for category_name in label[key]:
80
- category_names.append(category_name)
81
-
82
- if len(category_names) == 0:
83
- logger.debug(
84
- "Unknown label type or labels are empty: " + str(label)
85
- )
86
- continue
87
-
88
- for category_name in category_names:
89
- if category_name not in category_name_to_id:
90
- category_id = len(categories)
91
- category_name_to_id[category_name] = category_id
92
- categories.append({"id": category_id, "name": category_name})
93
- category_id = category_name_to_id[category_name]
94
-
95
- if (
96
- "rectanglelabels" in label
97
- or "rectangle" in label
98
- or "labels" in label
99
- ):
100
- # yolo obb
101
- if is_obb:
102
- obb_annotation = convert_annotation_to_yolo_obb(label)
103
- if obb_annotation is None:
104
- continue
105
-
106
- top_left, top_right, bottom_right, bottom_left = (
107
- obb_annotation
73
+ # Stream annotations directly to a temporary file to avoid
74
+ # accumulating them in memory and to preserve atomic writes.
75
+ tmp_path = f"{label_path}.tmp"
76
+
77
+ try:
78
+ with open(tmp_path, "w") as f:
79
+ for label in labels:
80
+ category_name = None
81
+ category_names = [] # considering multi-label
82
+ for key in ["rectanglelabels", "polygonlabels", "labels"]:
83
+ if key in label and len(label[key]) > 0:
84
+ # change to save multi-label
85
+ for category_name in label[key]:
86
+ category_names.append(category_name)
87
+
88
+ if len(category_names) == 0:
89
+ logger.debug(
90
+ "Unknown label type or labels are empty: " + str(label)
108
91
  )
109
- x1, y1 = top_left
110
- x2, y2 = top_right
111
- x3, y3 = bottom_right
112
- x4, y4 = bottom_left
113
- annotations.append(
114
- [category_id, x1, y1, x2, y2, x3, y3, x4, y4]
115
- )
116
-
117
- # simple yolo
118
- else:
119
- annotation = convert_annotation_to_yolo(label)
120
- if annotation is None:
121
- continue
122
-
123
- (
124
- x,
125
- y,
126
- w,
127
- h,
128
- ) = annotation
129
- annotations.append([category_id, x, y, w, h])
130
-
131
- elif "polygonlabels" in label or "polygon" in label:
132
- if not ('points' in label):
133
92
  continue
134
- points_abs = [(x / 100, y / 100) for x, y in label["points"]]
135
- annotations.append(
136
- [category_id]
137
- + [coord for point in points_abs for coord in point]
138
- )
139
- else:
140
- raise ValueError(f"Unknown label type {label}")
141
- with open(label_path, "w") as f:
142
- for annotation in annotations:
143
- for idx, l in enumerate(annotation):
144
- if idx == len(annotation) - 1:
145
- f.write(f"{l}\n")
146
- else:
147
- f.write(f"{l} ")
93
+
94
+ for category_name in category_names:
95
+ if category_name not in category_name_to_id:
96
+ category_id = len(categories)
97
+ category_name_to_id[category_name] = category_id
98
+ categories.append({"id": category_id, "name": category_name})
99
+ category_id = category_name_to_id[category_name]
100
+
101
+ if (
102
+ "rectanglelabels" in label
103
+ or "rectangle" in label
104
+ or "labels" in label
105
+ ):
106
+ # yolo obb
107
+ if is_obb:
108
+ obb_annotation = convert_annotation_to_yolo_obb(label)
109
+ if obb_annotation is None:
110
+ continue
111
+
112
+ top_left, top_right, bottom_right, bottom_left = (
113
+ obb_annotation
114
+ )
115
+ x1, y1 = top_left
116
+ x2, y2 = top_right
117
+ x3, y3 = bottom_right
118
+ x4, y4 = bottom_left
119
+ annotation_values = [category_id, x1, y1, x2, y2, x3, y3, x4, y4]
120
+
121
+ # simple yolo
122
+ else:
123
+ annotation = convert_annotation_to_yolo(label)
124
+ if annotation is None:
125
+ continue
126
+
127
+ (
128
+ x,
129
+ y,
130
+ w,
131
+ h,
132
+ ) = annotation
133
+ annotation_values = [category_id, x, y, w, h]
134
+
135
+ elif "polygonlabels" in label or "polygon" in label:
136
+ if not ('points' in label):
137
+ continue
138
+ points_abs = [(x / 100, y / 100) for x, y in label["points"]]
139
+ annotation_values = (
140
+ [category_id]
141
+ + [coord for point in points_abs for coord in point]
142
+ )
143
+ else:
144
+ raise ValueError(f"Unknown label type {label}")
145
+
146
+ # Write the annotation line immediately
147
+ for idx, val in enumerate(annotation_values):
148
+ if idx == len(annotation_values) - 1:
149
+ f.write(f"{val}\n")
150
+ else:
151
+ f.write(f"{val} ")
152
+
153
+ # Replace the target file atomically after successful write
154
+ os.replace(tmp_path, label_path)
155
+
156
+ finally:
157
+ # Clean up the temp file in case of exceptions before replace
158
+ try:
159
+ if os.path.exists(tmp_path):
160
+ os.remove(tmp_path)
161
+ except Exception:
162
+ pass
148
163
 
149
164
  return categories, category_name_to_id
@@ -4,7 +4,7 @@ import httpx
4
4
 
5
5
  from .http_client import AsyncHttpClient, HttpClient
6
6
 
7
- VERSION = "2.0.6"
7
+ VERSION = "2.0.8"
8
8
 
9
9
  class BaseClientWrapper:
10
10
  def __init__(
@@ -94,6 +94,7 @@ class AzureSpiClient:
94
94
  prefix: typing.Optional[str] = OMIT,
95
95
  presign: typing.Optional[bool] = OMIT,
96
96
  presign_ttl: typing.Optional[int] = OMIT,
97
+ recursive_scan: typing.Optional[bool] = OMIT,
97
98
  regex_filter: typing.Optional[str] = OMIT,
98
99
  status: typing.Optional[StatusC5AEnum] = OMIT,
99
100
  synchronizable: typing.Optional[bool] = OMIT,
@@ -146,6 +147,9 @@ class AzureSpiClient:
146
147
  presign_ttl : typing.Optional[int]
147
148
  Presigned URLs TTL (in minutes)
148
149
 
150
+ recursive_scan : typing.Optional[bool]
151
+ Perform recursive scan
152
+
149
153
  regex_filter : typing.Optional[str]
150
154
  Cloud storage regex for filtering objects
151
155
 
@@ -204,6 +208,7 @@ class AzureSpiClient:
204
208
  "presign": presign,
205
209
  "presign_ttl": presign_ttl,
206
210
  "project": project,
211
+ "recursive_scan": recursive_scan,
207
212
  "regex_filter": regex_filter,
208
213
  "status": status,
209
214
  "synchronizable": synchronizable,
@@ -246,6 +251,7 @@ class AzureSpiClient:
246
251
  prefix: typing.Optional[str] = OMIT,
247
252
  presign: typing.Optional[bool] = OMIT,
248
253
  presign_ttl: typing.Optional[int] = OMIT,
254
+ recursive_scan: typing.Optional[bool] = OMIT,
249
255
  regex_filter: typing.Optional[str] = OMIT,
250
256
  status: typing.Optional[StatusC5AEnum] = OMIT,
251
257
  synchronizable: typing.Optional[bool] = OMIT,
@@ -298,6 +304,9 @@ class AzureSpiClient:
298
304
  presign_ttl : typing.Optional[int]
299
305
  Presigned URLs TTL (in minutes)
300
306
 
307
+ recursive_scan : typing.Optional[bool]
308
+ Perform recursive scan
309
+
301
310
  regex_filter : typing.Optional[str]
302
311
  Cloud storage regex for filtering objects
303
312
 
@@ -355,6 +364,7 @@ class AzureSpiClient:
355
364
  "presign": presign,
356
365
  "presign_ttl": presign_ttl,
357
366
  "project": project,
367
+ "recursive_scan": recursive_scan,
358
368
  "regex_filter": regex_filter,
359
369
  "status": status,
360
370
  "synchronizable": synchronizable,
@@ -479,6 +489,7 @@ class AzureSpiClient:
479
489
  presign: typing.Optional[bool] = OMIT,
480
490
  presign_ttl: typing.Optional[int] = OMIT,
481
491
  project: typing.Optional[int] = OMIT,
492
+ recursive_scan: typing.Optional[bool] = OMIT,
482
493
  regex_filter: typing.Optional[str] = OMIT,
483
494
  status: typing.Optional[StatusC5AEnum] = OMIT,
484
495
  synchronizable: typing.Optional[bool] = OMIT,
@@ -533,6 +544,9 @@ class AzureSpiClient:
533
544
  project : typing.Optional[int]
534
545
  A unique integer value identifying this project.
535
546
 
547
+ recursive_scan : typing.Optional[bool]
548
+ Perform recursive scan
549
+
536
550
  regex_filter : typing.Optional[str]
537
551
  Cloud storage regex for filtering objects
538
552
 
@@ -591,6 +605,7 @@ class AzureSpiClient:
591
605
  "presign": presign,
592
606
  "presign_ttl": presign_ttl,
593
607
  "project": project,
608
+ "recursive_scan": recursive_scan,
594
609
  "regex_filter": regex_filter,
595
610
  "status": status,
596
611
  "synchronizable": synchronizable,
@@ -755,6 +770,7 @@ class AsyncAzureSpiClient:
755
770
  prefix: typing.Optional[str] = OMIT,
756
771
  presign: typing.Optional[bool] = OMIT,
757
772
  presign_ttl: typing.Optional[int] = OMIT,
773
+ recursive_scan: typing.Optional[bool] = OMIT,
758
774
  regex_filter: typing.Optional[str] = OMIT,
759
775
  status: typing.Optional[StatusC5AEnum] = OMIT,
760
776
  synchronizable: typing.Optional[bool] = OMIT,
@@ -807,6 +823,9 @@ class AsyncAzureSpiClient:
807
823
  presign_ttl : typing.Optional[int]
808
824
  Presigned URLs TTL (in minutes)
809
825
 
826
+ recursive_scan : typing.Optional[bool]
827
+ Perform recursive scan
828
+
810
829
  regex_filter : typing.Optional[str]
811
830
  Cloud storage regex for filtering objects
812
831
 
@@ -873,6 +892,7 @@ class AsyncAzureSpiClient:
873
892
  "presign": presign,
874
893
  "presign_ttl": presign_ttl,
875
894
  "project": project,
895
+ "recursive_scan": recursive_scan,
876
896
  "regex_filter": regex_filter,
877
897
  "status": status,
878
898
  "synchronizable": synchronizable,
@@ -915,6 +935,7 @@ class AsyncAzureSpiClient:
915
935
  prefix: typing.Optional[str] = OMIT,
916
936
  presign: typing.Optional[bool] = OMIT,
917
937
  presign_ttl: typing.Optional[int] = OMIT,
938
+ recursive_scan: typing.Optional[bool] = OMIT,
918
939
  regex_filter: typing.Optional[str] = OMIT,
919
940
  status: typing.Optional[StatusC5AEnum] = OMIT,
920
941
  synchronizable: typing.Optional[bool] = OMIT,
@@ -967,6 +988,9 @@ class AsyncAzureSpiClient:
967
988
  presign_ttl : typing.Optional[int]
968
989
  Presigned URLs TTL (in minutes)
969
990
 
991
+ recursive_scan : typing.Optional[bool]
992
+ Perform recursive scan
993
+
970
994
  regex_filter : typing.Optional[str]
971
995
  Cloud storage regex for filtering objects
972
996
 
@@ -1032,6 +1056,7 @@ class AsyncAzureSpiClient:
1032
1056
  "presign": presign,
1033
1057
  "presign_ttl": presign_ttl,
1034
1058
  "project": project,
1059
+ "recursive_scan": recursive_scan,
1035
1060
  "regex_filter": regex_filter,
1036
1061
  "status": status,
1037
1062
  "synchronizable": synchronizable,
@@ -1172,6 +1197,7 @@ class AsyncAzureSpiClient:
1172
1197
  presign: typing.Optional[bool] = OMIT,
1173
1198
  presign_ttl: typing.Optional[int] = OMIT,
1174
1199
  project: typing.Optional[int] = OMIT,
1200
+ recursive_scan: typing.Optional[bool] = OMIT,
1175
1201
  regex_filter: typing.Optional[str] = OMIT,
1176
1202
  status: typing.Optional[StatusC5AEnum] = OMIT,
1177
1203
  synchronizable: typing.Optional[bool] = OMIT,
@@ -1226,6 +1252,9 @@ class AsyncAzureSpiClient:
1226
1252
  project : typing.Optional[int]
1227
1253
  A unique integer value identifying this project.
1228
1254
 
1255
+ recursive_scan : typing.Optional[bool]
1256
+ Perform recursive scan
1257
+
1229
1258
  regex_filter : typing.Optional[str]
1230
1259
  Cloud storage regex for filtering objects
1231
1260
 
@@ -1292,6 +1321,7 @@ class AsyncAzureSpiClient:
1292
1321
  "presign": presign,
1293
1322
  "presign_ttl": presign_ttl,
1294
1323
  "project": project,
1324
+ "recursive_scan": recursive_scan,
1295
1325
  "regex_filter": regex_filter,
1296
1326
  "status": status,
1297
1327
  "synchronizable": synchronizable,
@@ -39,6 +39,7 @@ _TAG_TO_CLASS = {
39
39
  "taxonomy": "TaxonomyTag",
40
40
  "textarea": "TextAreaTag",
41
41
  "timeserieslabels": "TimeSeriesLabelsTag",
42
+ "chatmessage": "ChatMessageTag",
42
43
  }
43
44
 
44
45
 
@@ -950,6 +951,43 @@ class RatingTag(ControlTag):
950
951
  }
951
952
 
952
953
 
954
+ class ChatMessageContent(BaseModel):
955
+ role: str
956
+ content: str
957
+ createdAt: Optional[int] = None
958
+
959
+
960
+ class ChatMessageValue(BaseModel):
961
+ chatmessage: ChatMessageContent
962
+
963
+
964
+ class ChatMessageTag(ControlTag):
965
+ """Control tag for chat messages targeting a `<Chat>` object.
966
+
967
+ This tag is a hybrid where `from_name == to_name` and `type == 'chatmessage'`.
968
+ """
969
+ tag: str = "ChatMessage"
970
+ _value_class: Type[ChatMessageValue] = ChatMessageValue
971
+
972
+ def to_json_schema(self):
973
+ return {
974
+ "type": "object",
975
+ "required": ["chatmessage"],
976
+ "properties": {
977
+ "chatmessage": {
978
+ "type": "object",
979
+ "required": ["role", "content"],
980
+ "properties": {
981
+ "role": {"type": "string"},
982
+ "content": {"type": "string"},
983
+ "createdAt": {"type": "number"}
984
+ }
985
+ }
986
+ },
987
+ "description": f"Chat message for {self.to_name[0]}"
988
+ }
989
+
990
+
953
991
  class RelationsTag(ControlTag):
954
992
  """ """
955
993
  tag: str = "Relations"
@@ -10,6 +10,12 @@
10
10
  "Header": "Task header",
11
11
  "Paragraphs": [{"author": "Alice", "text": "Hi, Bob."}, {"author": "Bob", "text": "Hello, Alice!"}, {"author": "Alice", "text": "What's up?"}, {"author": "Bob", "text": "Good. Ciao!"}, {"author": "Alice", "text": "Bye, Bob."}],
12
12
  "ParagraphsUrl": "<HOSTNAME>/samples/paragraphs.json?",
13
+ "Chat": [
14
+ {"role": "user", "content": "Hi there!", "createdAt": 1710000000000},
15
+ {"role": "assistant", "content": "Hello! How can I help you today?", "createdAt": 1710000005000},
16
+ {"role": "user", "content": "Can you summarize our onboarding checklist?", "createdAt": 1710000010000},
17
+ {"role": "assistant", "content": "Sure. It includes account setup, roles, labeling instructions, GT tasks, overlap and review.", "createdAt": 1710000015000}
18
+ ],
13
19
  "Table": {"Card number": 18799210, "First name": "Max", "Last name": "Nobel"},
14
20
  "$videoHack": "<video src='<HOSTNAME>/static/samples/opossum_snow.mp4' width=100% controls>",
15
21
  "Video": "<HOSTNAME>/static/samples/opossum_snow.mp4",
@@ -85,6 +91,10 @@
85
91
  "AudioPlus": "<HOSTNAME>/static/samples/game.wav",
86
92
  "Header": "Task header",
87
93
  "Paragraphs": [{"author": "Alice", "text": "Hi, Bob."}, {"author": "Bob", "text": "Hello, Alice!"}, {"author": "Alice", "text": "What's up?"}, {"author": "Bob", "text": "Good. Ciao!"}, {"author": "Alice", "text": "Bye, Bob."}],
94
+ "Chat": [
95
+ {"role": "user", "content": "Hello!", "createdAt": 1710000000000},
96
+ {"role": "assistant", "content": "Hi! What can I do for you?", "createdAt": 1710000004000}
97
+ ],
88
98
  "Table": {"Card number": 18799210, "First name": "Max", "Last name": "Nobel"},
89
99
  "$videoHack": "<video src='static/samples/opossum_snow.mp4' width=100% controls>",
90
100
  "Video": "<HOSTNAME>/static/samples/opossum_snow.mp4",
@@ -27,6 +27,7 @@ from label_studio_sdk._legacy.exceptions import (
27
27
 
28
28
  from .base import LabelStudioTag
29
29
  from .control_tags import (
30
+ ChatMessageTag,
30
31
  ControlTag,
31
32
  ChoicesTag,
32
33
  LabelsTag,
@@ -623,6 +624,18 @@ class LabelInterface:
623
624
  if lb:
624
625
  labels[lb.parent_name][lb.value] = lb
625
626
 
627
+ # Special handling: auto-create ChatMessage control for each Chat object
628
+ chat_object_names = [name for name, obj in objects.items() if getattr(obj, 'tag', '').lower() == 'chat']
629
+ for name in chat_object_names:
630
+ if name not in controls:
631
+ controls[name] = ChatMessageTag(
632
+ tag='ChatMessage',
633
+ name=name,
634
+ to_name=[name],
635
+ attr={"name": name, "toName": name}
636
+ )
637
+
638
+
626
639
  return controls, objects, labels, xml_tree
627
640
 
628
641
  @classmethod
@@ -20,6 +20,7 @@ _TAG_TO_CLASS = {
20
20
  "list": "ListTag",
21
21
  "paragraphs": "ParagraphsTag",
22
22
  "timeseries": "TimeSeriesTag",
23
+ "chat": "ChatTag",
23
24
  }
24
25
 
25
26
  _DATA_EXAMPLES = None
@@ -306,3 +307,11 @@ class TimeSeriesTag(ObjectTag):
306
307
  else:
307
308
  # data is JSON
308
309
  return generate_time_series_json(time_column, value_columns, time_format)
310
+
311
+ class ChatTag(ObjectTag):
312
+ """ """
313
+ tag: str = "Chat"
314
+
315
+ def _generate_example(self, examples, only_urls=False):
316
+ """ """
317
+ return examples.get("Chat")