label-studio-sdk 1.0.5__py3-none-any.whl → 1.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.
Potentially problematic release.
This version of label-studio-sdk might be problematic. Click here for more details.
- label_studio_sdk/__init__.py +76 -0
- label_studio_sdk/_extensions/eval/categorical.py +83 -0
- label_studio_sdk/_extensions/label_studio_tools/core/label_config.py +13 -4
- label_studio_sdk/_extensions/label_studio_tools/core/utils/io.py +35 -17
- label_studio_sdk/_extensions/label_studio_tools/core/utils/json_schema.py +86 -0
- label_studio_sdk/_legacy/schema/label_config_schema.json +42 -11
- label_studio_sdk/annotations/__init__.py +3 -0
- label_studio_sdk/annotations/client.py +109 -0
- label_studio_sdk/annotations/types/__init__.py +5 -0
- label_studio_sdk/annotations/types/annotations_create_bulk_response_item.py +29 -0
- label_studio_sdk/base_client.py +9 -0
- label_studio_sdk/comments/__init__.py +2 -0
- label_studio_sdk/comments/client.py +512 -0
- label_studio_sdk/converter/converter.py +11 -4
- label_studio_sdk/converter/imports/coco.py +14 -13
- label_studio_sdk/converter/utils.py +72 -3
- label_studio_sdk/core/client_wrapper.py +1 -1
- label_studio_sdk/files/client.py +26 -16
- label_studio_sdk/label_interface/control_tags.py +205 -10
- label_studio_sdk/label_interface/interface.py +117 -10
- label_studio_sdk/label_interface/region.py +1 -10
- label_studio_sdk/model_providers/__init__.py +2 -0
- label_studio_sdk/model_providers/client.py +708 -0
- label_studio_sdk/projects/client.py +32 -16
- label_studio_sdk/projects/exports/client.py +133 -40
- label_studio_sdk/prompts/__init__.py +21 -0
- label_studio_sdk/prompts/client.py +862 -0
- label_studio_sdk/prompts/indicators/__init__.py +2 -0
- label_studio_sdk/prompts/indicators/client.py +194 -0
- label_studio_sdk/prompts/runs/__init__.py +5 -0
- label_studio_sdk/prompts/runs/client.py +354 -0
- label_studio_sdk/prompts/runs/types/__init__.py +5 -0
- label_studio_sdk/prompts/runs/types/runs_list_request_project_subset.py +5 -0
- label_studio_sdk/prompts/types/__init__.py +15 -0
- label_studio_sdk/prompts/types/prompts_batch_failed_predictions_request_failed_predictions_item.py +42 -0
- label_studio_sdk/prompts/types/prompts_batch_failed_predictions_response.py +29 -0
- label_studio_sdk/prompts/types/prompts_batch_predictions_request_results_item.py +62 -0
- label_studio_sdk/prompts/types/prompts_batch_predictions_response.py +29 -0
- label_studio_sdk/prompts/versions/__init__.py +2 -0
- label_studio_sdk/prompts/versions/client.py +1046 -0
- label_studio_sdk/types/__init__.py +58 -0
- label_studio_sdk/types/comment.py +39 -0
- label_studio_sdk/types/comment_created_by.py +5 -0
- label_studio_sdk/types/inference_run.py +43 -0
- label_studio_sdk/types/inference_run_cost_estimate.py +57 -0
- label_studio_sdk/types/inference_run_created_by.py +5 -0
- label_studio_sdk/types/inference_run_organization.py +5 -0
- label_studio_sdk/types/inference_run_project_subset.py +5 -0
- label_studio_sdk/types/inference_run_status.py +7 -0
- label_studio_sdk/types/key_indicator_value.py +30 -0
- label_studio_sdk/types/key_indicators.py +7 -0
- label_studio_sdk/types/key_indicators_item.py +51 -0
- label_studio_sdk/types/key_indicators_item_additional_kpis_item.py +37 -0
- label_studio_sdk/types/key_indicators_item_extra_kpis_item.py +37 -0
- label_studio_sdk/types/model_provider_connection.py +71 -0
- label_studio_sdk/types/model_provider_connection_budget_reset_period.py +5 -0
- label_studio_sdk/types/model_provider_connection_created_by.py +5 -0
- label_studio_sdk/types/model_provider_connection_organization.py +5 -0
- label_studio_sdk/types/model_provider_connection_provider.py +5 -0
- label_studio_sdk/types/model_provider_connection_scope.py +5 -0
- label_studio_sdk/types/prompt.py +79 -0
- label_studio_sdk/types/prompt_created_by.py +5 -0
- label_studio_sdk/types/prompt_organization.py +5 -0
- label_studio_sdk/types/prompt_version.py +41 -0
- label_studio_sdk/types/prompt_version_created_by.py +5 -0
- label_studio_sdk/types/prompt_version_organization.py +5 -0
- label_studio_sdk/types/prompt_version_provider.py +5 -0
- label_studio_sdk/types/refined_prompt_response.py +64 -0
- label_studio_sdk/types/refined_prompt_response_refinement_status.py +7 -0
- label_studio_sdk/types/task.py +3 -2
- label_studio_sdk/types/task_comment_authors_item.py +5 -0
- label_studio_sdk/webhooks/client.py +245 -36
- label_studio_sdk/workspaces/client.py +20 -20
- label_studio_sdk-1.0.8.dist-info/LICENSE +201 -0
- {label_studio_sdk-1.0.5.dist-info → label_studio_sdk-1.0.8.dist-info}/METADATA +19 -3
- {label_studio_sdk-1.0.5.dist-info → label_studio_sdk-1.0.8.dist-info}/RECORD +77 -24
- {label_studio_sdk-1.0.5.dist-info → label_studio_sdk-1.0.8.dist-info}/WHEEL +1 -1
label_studio_sdk/__init__.py
CHANGED
|
@@ -14,6 +14,8 @@ from .types import (
|
|
|
14
14
|
BaseTaskFileUpload,
|
|
15
15
|
BaseTaskUpdatedBy,
|
|
16
16
|
BaseUser,
|
|
17
|
+
Comment,
|
|
18
|
+
CommentCreatedBy,
|
|
17
19
|
ConvertedFormat,
|
|
18
20
|
ConvertedFormatStatus,
|
|
19
21
|
DataManagerTaskSerializer,
|
|
@@ -32,6 +34,17 @@ from .types import (
|
|
|
32
34
|
GcsExportStorageStatus,
|
|
33
35
|
GcsImportStorage,
|
|
34
36
|
GcsImportStorageStatus,
|
|
37
|
+
InferenceRun,
|
|
38
|
+
InferenceRunCostEstimate,
|
|
39
|
+
InferenceRunCreatedBy,
|
|
40
|
+
InferenceRunOrganization,
|
|
41
|
+
InferenceRunProjectSubset,
|
|
42
|
+
InferenceRunStatus,
|
|
43
|
+
KeyIndicatorValue,
|
|
44
|
+
KeyIndicators,
|
|
45
|
+
KeyIndicatorsItem,
|
|
46
|
+
KeyIndicatorsItemAdditionalKpisItem,
|
|
47
|
+
KeyIndicatorsItemExtraKpisItem,
|
|
35
48
|
LocalFilesExportStorage,
|
|
36
49
|
LocalFilesExportStorageStatus,
|
|
37
50
|
LocalFilesImportStorage,
|
|
@@ -39,6 +52,12 @@ from .types import (
|
|
|
39
52
|
MlBackend,
|
|
40
53
|
MlBackendAuthMethod,
|
|
41
54
|
MlBackendState,
|
|
55
|
+
ModelProviderConnection,
|
|
56
|
+
ModelProviderConnectionBudgetResetPeriod,
|
|
57
|
+
ModelProviderConnectionCreatedBy,
|
|
58
|
+
ModelProviderConnectionOrganization,
|
|
59
|
+
ModelProviderConnectionProvider,
|
|
60
|
+
ModelProviderConnectionScope,
|
|
42
61
|
Prediction,
|
|
43
62
|
Project,
|
|
44
63
|
ProjectImport,
|
|
@@ -46,10 +65,19 @@ from .types import (
|
|
|
46
65
|
ProjectLabelConfig,
|
|
47
66
|
ProjectSampling,
|
|
48
67
|
ProjectSkipQueue,
|
|
68
|
+
Prompt,
|
|
69
|
+
PromptCreatedBy,
|
|
70
|
+
PromptOrganization,
|
|
71
|
+
PromptVersion,
|
|
72
|
+
PromptVersionCreatedBy,
|
|
73
|
+
PromptVersionOrganization,
|
|
74
|
+
PromptVersionProvider,
|
|
49
75
|
RedisExportStorage,
|
|
50
76
|
RedisExportStorageStatus,
|
|
51
77
|
RedisImportStorage,
|
|
52
78
|
RedisImportStorageStatus,
|
|
79
|
+
RefinedPromptResponse,
|
|
80
|
+
RefinedPromptResponseRefinementStatus,
|
|
53
81
|
S3ExportStorage,
|
|
54
82
|
S3ExportStorageStatus,
|
|
55
83
|
S3ImportStorage,
|
|
@@ -61,6 +89,7 @@ from .types import (
|
|
|
61
89
|
SerializationOptions,
|
|
62
90
|
Task,
|
|
63
91
|
TaskAnnotatorsItem,
|
|
92
|
+
TaskCommentAuthorsItem,
|
|
64
93
|
TaskFilterOptions,
|
|
65
94
|
UserSimple,
|
|
66
95
|
View,
|
|
@@ -74,12 +103,15 @@ from .errors import BadRequestError, InternalServerError
|
|
|
74
103
|
from . import (
|
|
75
104
|
actions,
|
|
76
105
|
annotations,
|
|
106
|
+
comments,
|
|
77
107
|
export_storage,
|
|
78
108
|
files,
|
|
79
109
|
import_storage,
|
|
80
110
|
ml,
|
|
111
|
+
model_providers,
|
|
81
112
|
predictions,
|
|
82
113
|
projects,
|
|
114
|
+
prompts,
|
|
83
115
|
tasks,
|
|
84
116
|
users,
|
|
85
117
|
views,
|
|
@@ -100,6 +132,7 @@ from .actions import (
|
|
|
100
132
|
ActionsCreateRequestSelectedItemsExcluded,
|
|
101
133
|
ActionsCreateRequestSelectedItemsIncluded,
|
|
102
134
|
)
|
|
135
|
+
from .annotations import AnnotationsCreateBulkResponseItem
|
|
103
136
|
from .environment import LabelStudioEnvironment
|
|
104
137
|
from .export_storage import ExportStorageListTypesResponseItem
|
|
105
138
|
from .import_storage import ImportStorageListTypesResponseItem
|
|
@@ -112,6 +145,12 @@ from .ml import (
|
|
|
112
145
|
MlUpdateResponseAuthMethod,
|
|
113
146
|
)
|
|
114
147
|
from .projects import ProjectsCreateResponse, ProjectsImportTasksResponse, ProjectsListResponse, ProjectsUpdateResponse
|
|
148
|
+
from .prompts import (
|
|
149
|
+
PromptsBatchFailedPredictionsRequestFailedPredictionsItem,
|
|
150
|
+
PromptsBatchFailedPredictionsResponse,
|
|
151
|
+
PromptsBatchPredictionsRequestResultsItem,
|
|
152
|
+
PromptsBatchPredictionsResponse,
|
|
153
|
+
)
|
|
115
154
|
from .tasks import TasksListRequestFields, TasksListResponse
|
|
116
155
|
from .users import UsersGetTokenResponse, UsersResetTokenResponse
|
|
117
156
|
from .version import __version__
|
|
@@ -150,6 +189,7 @@ __all__ = [
|
|
|
150
189
|
"Annotation",
|
|
151
190
|
"AnnotationFilterOptions",
|
|
152
191
|
"AnnotationLastAction",
|
|
192
|
+
"AnnotationsCreateBulkResponseItem",
|
|
153
193
|
"AnnotationsDmField",
|
|
154
194
|
"AnnotationsDmFieldLastAction",
|
|
155
195
|
"AzureBlobExportStorage",
|
|
@@ -162,6 +202,8 @@ __all__ = [
|
|
|
162
202
|
"BaseTaskUpdatedBy",
|
|
163
203
|
"BaseUser",
|
|
164
204
|
"Client",
|
|
205
|
+
"Comment",
|
|
206
|
+
"CommentCreatedBy",
|
|
165
207
|
"ConvertedFormat",
|
|
166
208
|
"ConvertedFormatStatus",
|
|
167
209
|
"DataManagerTaskSerializer",
|
|
@@ -182,7 +224,18 @@ __all__ = [
|
|
|
182
224
|
"GcsImportStorage",
|
|
183
225
|
"GcsImportStorageStatus",
|
|
184
226
|
"ImportStorageListTypesResponseItem",
|
|
227
|
+
"InferenceRun",
|
|
228
|
+
"InferenceRunCostEstimate",
|
|
229
|
+
"InferenceRunCreatedBy",
|
|
230
|
+
"InferenceRunOrganization",
|
|
231
|
+
"InferenceRunProjectSubset",
|
|
232
|
+
"InferenceRunStatus",
|
|
185
233
|
"InternalServerError",
|
|
234
|
+
"KeyIndicatorValue",
|
|
235
|
+
"KeyIndicators",
|
|
236
|
+
"KeyIndicatorsItem",
|
|
237
|
+
"KeyIndicatorsItemAdditionalKpisItem",
|
|
238
|
+
"KeyIndicatorsItemExtraKpisItem",
|
|
186
239
|
"LabelStudioEnvironment",
|
|
187
240
|
"LocalFilesExportStorage",
|
|
188
241
|
"LocalFilesExportStorageStatus",
|
|
@@ -197,6 +250,12 @@ __all__ = [
|
|
|
197
250
|
"MlUpdateRequestAuthMethod",
|
|
198
251
|
"MlUpdateResponse",
|
|
199
252
|
"MlUpdateResponseAuthMethod",
|
|
253
|
+
"ModelProviderConnection",
|
|
254
|
+
"ModelProviderConnectionBudgetResetPeriod",
|
|
255
|
+
"ModelProviderConnectionCreatedBy",
|
|
256
|
+
"ModelProviderConnectionOrganization",
|
|
257
|
+
"ModelProviderConnectionProvider",
|
|
258
|
+
"ModelProviderConnectionScope",
|
|
200
259
|
"Prediction",
|
|
201
260
|
"Project",
|
|
202
261
|
"ProjectImport",
|
|
@@ -208,10 +267,23 @@ __all__ = [
|
|
|
208
267
|
"ProjectsImportTasksResponse",
|
|
209
268
|
"ProjectsListResponse",
|
|
210
269
|
"ProjectsUpdateResponse",
|
|
270
|
+
"Prompt",
|
|
271
|
+
"PromptCreatedBy",
|
|
272
|
+
"PromptOrganization",
|
|
273
|
+
"PromptVersion",
|
|
274
|
+
"PromptVersionCreatedBy",
|
|
275
|
+
"PromptVersionOrganization",
|
|
276
|
+
"PromptVersionProvider",
|
|
277
|
+
"PromptsBatchFailedPredictionsRequestFailedPredictionsItem",
|
|
278
|
+
"PromptsBatchFailedPredictionsResponse",
|
|
279
|
+
"PromptsBatchPredictionsRequestResultsItem",
|
|
280
|
+
"PromptsBatchPredictionsResponse",
|
|
211
281
|
"RedisExportStorage",
|
|
212
282
|
"RedisExportStorageStatus",
|
|
213
283
|
"RedisImportStorage",
|
|
214
284
|
"RedisImportStorageStatus",
|
|
285
|
+
"RefinedPromptResponse",
|
|
286
|
+
"RefinedPromptResponseRefinementStatus",
|
|
215
287
|
"S3ExportStorage",
|
|
216
288
|
"S3ExportStorageStatus",
|
|
217
289
|
"S3ImportStorage",
|
|
@@ -223,6 +295,7 @@ __all__ = [
|
|
|
223
295
|
"SerializationOptions",
|
|
224
296
|
"Task",
|
|
225
297
|
"TaskAnnotatorsItem",
|
|
298
|
+
"TaskCommentAuthorsItem",
|
|
226
299
|
"TaskFilterOptions",
|
|
227
300
|
"TasksListRequestFields",
|
|
228
301
|
"TasksListResponse",
|
|
@@ -255,12 +328,15 @@ __all__ = [
|
|
|
255
328
|
"__version__",
|
|
256
329
|
"actions",
|
|
257
330
|
"annotations",
|
|
331
|
+
"comments",
|
|
258
332
|
"export_storage",
|
|
259
333
|
"files",
|
|
260
334
|
"import_storage",
|
|
261
335
|
"ml",
|
|
336
|
+
"model_providers",
|
|
262
337
|
"predictions",
|
|
263
338
|
"projects",
|
|
339
|
+
"prompts",
|
|
264
340
|
"tasks",
|
|
265
341
|
"users",
|
|
266
342
|
"views",
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import List, Dict, Iterable
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
# TODO: after python 3.8 support is dropped (Oct'24), remove try-except block
|
|
8
|
+
from sklearn.metrics import precision_recall_fscore_support
|
|
9
|
+
except ImportError:
|
|
10
|
+
logger.warning('scikit-learn is not installed. Please install scikit-learn to use this module.')
|
|
11
|
+
precision_recall_fscore_support = None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_single_choice(annotation: Dict):
|
|
15
|
+
"""
|
|
16
|
+
Get the single choice from the annotation.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
annotation: Annotation dict
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Single choice
|
|
23
|
+
"""
|
|
24
|
+
maybe_choice = next((r['value']['choices'][0] for r in annotation['result'] if r['type'] == 'choices'), None)
|
|
25
|
+
if maybe_choice:
|
|
26
|
+
return maybe_choice
|
|
27
|
+
raise NotImplementedError('Only single choice is supported')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_precision_recall_f1_per_choice(annotations: Iterable[Dict], predictions: Iterable[Dict]) -> Dict:
|
|
31
|
+
"""
|
|
32
|
+
Given the iterator over annotations and predictions, calculate precision, recall, and F1 per choice.
|
|
33
|
+
Each annotation and prediction follows the format of the Label Studio output.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
annotations: Iterable of annotation dicts
|
|
37
|
+
predictions: Iterable of prediction dicts
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict of the following format:
|
|
41
|
+
{
|
|
42
|
+
'precision': {
|
|
43
|
+
'choice1': 0.5,
|
|
44
|
+
'choice2': 0.7,
|
|
45
|
+
...
|
|
46
|
+
},
|
|
47
|
+
'recall': {
|
|
48
|
+
'choice1': 0.5,
|
|
49
|
+
'choice2': 0.7,
|
|
50
|
+
...
|
|
51
|
+
},
|
|
52
|
+
'f1': {
|
|
53
|
+
'choice1': 0.5,
|
|
54
|
+
'choice2': 0.7,
|
|
55
|
+
...
|
|
56
|
+
},
|
|
57
|
+
'support': {
|
|
58
|
+
'choice1': 10,
|
|
59
|
+
'choice2': 20,
|
|
60
|
+
...
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
if precision_recall_fscore_support is None:
|
|
66
|
+
raise ImportError('scikit-learn is not installed. Please install scikit-learn to use this module.')
|
|
67
|
+
|
|
68
|
+
annotation_choices = [_get_single_choice(annotation) for annotation in annotations]
|
|
69
|
+
prediction_choices = [_get_single_choice(prediction) for prediction in predictions]
|
|
70
|
+
# get unique choices names
|
|
71
|
+
unique_choices = sorted(set(annotation_choices + prediction_choices))
|
|
72
|
+
|
|
73
|
+
metrics_per_choice = precision_recall_fscore_support(
|
|
74
|
+
annotation_choices, prediction_choices,
|
|
75
|
+
average=None, labels=unique_choices)
|
|
76
|
+
|
|
77
|
+
results = {}
|
|
78
|
+
for metric_name, metric_per_choice in zip(['precision', 'recall', 'f1', 'support'], metrics_per_choice):
|
|
79
|
+
results[metric_name] = {}
|
|
80
|
+
for choice, metric in zip(unique_choices, metric_per_choice):
|
|
81
|
+
results[metric_name][choice] = metric
|
|
82
|
+
|
|
83
|
+
return results
|
|
@@ -82,17 +82,26 @@ def parse_config(config_string):
|
|
|
82
82
|
elif _is_input_tag(tag):
|
|
83
83
|
inputs[tag.attrib["name"]] = {
|
|
84
84
|
"type": tag.tag,
|
|
85
|
-
"value": tag.attrib["value"].lstrip("$"),
|
|
86
85
|
"valueType": tag.attrib.get("valueType"),
|
|
87
86
|
}
|
|
87
|
+
if 'value' in tag.attrib:
|
|
88
|
+
inputs[tag.attrib["name"]]["value"] = tag.attrib["value"].lstrip("$")
|
|
89
|
+
elif 'valueList' in tag.attrib:
|
|
90
|
+
inputs[tag.attrib["name"]]["valueList"] = tag.attrib["valueList"].lstrip("$")
|
|
91
|
+
else:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
'Inspecting tag {tag_name}... found no "value" or "valueList" attributes.'.format(
|
|
94
|
+
tag_name=etree.tostring(tag, encoding="unicode").strip()[:50]
|
|
95
|
+
)
|
|
96
|
+
)
|
|
88
97
|
if tag.tag not in _LABEL_TAGS:
|
|
89
98
|
continue
|
|
90
99
|
parent_name = _get_parent_output_tag_name(tag, outputs)
|
|
91
100
|
if parent_name is not None:
|
|
92
|
-
actual_value = tag.attrib.get("alias") or tag.attrib.get("value")
|
|
101
|
+
actual_value = tag.attrib.get("alias") or tag.attrib.get("value") or tag.attrib.get("valueList")
|
|
93
102
|
if not actual_value:
|
|
94
103
|
logger.debug(
|
|
95
|
-
'Inspecting tag {tag_name}... found no "value" or "alias" attributes.'.format(
|
|
104
|
+
'Inspecting tag {tag_name}... found no "value", "valueList", or "alias" attributes.'.format(
|
|
96
105
|
tag_name=etree.tostring(tag, encoding="unicode").strip()[:50]
|
|
97
106
|
)
|
|
98
107
|
)
|
|
@@ -137,7 +146,7 @@ def _is_input_tag(tag):
|
|
|
137
146
|
"""
|
|
138
147
|
Check if tag is input
|
|
139
148
|
"""
|
|
140
|
-
return tag.attrib.get("name") and tag.attrib.get("value")
|
|
149
|
+
return tag.attrib.get("name") and (tag.attrib.get("value") or tag.attrib.get("valueList"))
|
|
141
150
|
|
|
142
151
|
|
|
143
152
|
def _is_output_tag(tag):
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import io
|
|
3
|
-
import shutil
|
|
4
|
-
import urllib
|
|
5
1
|
import hashlib
|
|
6
|
-
import
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
7
4
|
import os
|
|
8
|
-
|
|
9
|
-
from appdirs import user_cache_dir, user_data_dir
|
|
10
|
-
from urllib.parse import urlparse, urljoin
|
|
5
|
+
import shutil
|
|
11
6
|
from contextlib import contextmanager
|
|
12
7
|
from tempfile import mkdtemp
|
|
8
|
+
from urllib.parse import urlparse
|
|
9
|
+
|
|
10
|
+
import requests
|
|
11
|
+
from appdirs import user_cache_dir, user_data_dir
|
|
13
12
|
|
|
14
13
|
from label_studio_sdk._extensions.label_studio_tools.core.utils.params import get_env
|
|
15
14
|
|
|
@@ -17,6 +16,7 @@ _DIR_APP_NAME = "label-studio"
|
|
|
17
16
|
LOCAL_FILES_DOCUMENT_ROOT = get_env(
|
|
18
17
|
"LOCAL_FILES_DOCUMENT_ROOT", default=os.path.abspath(os.sep)
|
|
19
18
|
)
|
|
19
|
+
VERIFY_SSL = get_env("VERIFY_SSL", default=True, is_bool=True)
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
@@ -80,6 +80,13 @@ def get_local_path(
|
|
|
80
80
|
f"`localhost` is not accessible inside of docker containers. "
|
|
81
81
|
f"You can check your IP with utilities like `ifconfig` and set it as LABEL_STUDIO_URL."
|
|
82
82
|
)
|
|
83
|
+
if hostname and not (
|
|
84
|
+
hostname.startswith("http://") or hostname.startswith("https://")
|
|
85
|
+
):
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"Invalid hostname in LABEL_STUDIO_URL: {hostname}. "
|
|
88
|
+
"Please provide full URL starting with protocol (http:// or https://)."
|
|
89
|
+
)
|
|
83
90
|
|
|
84
91
|
# fix file upload url
|
|
85
92
|
if url.startswith("upload") or url.startswith("/upload"):
|
|
@@ -180,13 +187,17 @@ def download_and_cache(
|
|
|
180
187
|
# File specified by remote URL - download and cache it
|
|
181
188
|
cache_dir = cache_dir or get_cache_dir()
|
|
182
189
|
parsed_url = urlparse(url)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
|
|
191
|
+
# local storage: /data/local-files?d=dir/1.jpg => 1.jpg
|
|
192
|
+
if is_local_storage_file:
|
|
193
|
+
url_filename = os.path.basename(url.split('?d=')[1])
|
|
194
|
+
# cloud storage: s3://bucket/1.jpg => 1.jpg
|
|
195
|
+
elif is_cloud_storage_file:
|
|
196
|
+
url_filename = os.path.basename(url)
|
|
197
|
+
# all others: /some/url/1.jpg?expire=xxx => 1.jpg
|
|
198
|
+
else:
|
|
199
|
+
url_filename = os.path.basename(parsed_url.path)
|
|
200
|
+
|
|
190
201
|
url_hash = hashlib.md5(url.encode()).hexdigest()[:8]
|
|
191
202
|
filepath = os.path.join(cache_dir, url_hash + "__" + url_filename)
|
|
192
203
|
|
|
@@ -205,8 +216,15 @@ def download_and_cache(
|
|
|
205
216
|
):
|
|
206
217
|
headers["Authorization"] = "Token " + access_token
|
|
207
218
|
logger.debug("Authorization token is used for download_and_cache")
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
try:
|
|
220
|
+
r = requests.get(url, stream=True, headers=headers, verify=VERIFY_SSL)
|
|
221
|
+
r.raise_for_status()
|
|
222
|
+
except requests.exceptions.SSLError as e:
|
|
223
|
+
logger.error(
|
|
224
|
+
f"SSL error during requests.get('{url}'): {e}\n"
|
|
225
|
+
f"Try to set VERIFY_SSL=False in environment variables to bypass SSL verification."
|
|
226
|
+
)
|
|
227
|
+
raise e
|
|
210
228
|
with io.open(filepath, mode="wb") as fout:
|
|
211
229
|
fout.write(r.content)
|
|
212
230
|
return filepath
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import types
|
|
3
|
+
import sys
|
|
4
|
+
import functools
|
|
5
|
+
from typing import Type, Dict, Any, Tuple, Generator
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from tempfile import TemporaryDirectory
|
|
8
|
+
from datamodel_code_generator import DataModelType, PythonVersion, LiteralType
|
|
9
|
+
from datamodel_code_generator.model import get_data_model_types
|
|
10
|
+
from datamodel_code_generator.parser.jsonschema import JsonSchemaParser
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@functools.lru_cache(maxsize=128)
|
|
16
|
+
def _generate_model_code(json_schema_str: str, class_name: str = 'MyModel') -> str:
|
|
17
|
+
|
|
18
|
+
data_model_types = get_data_model_types(
|
|
19
|
+
DataModelType.PydanticV2BaseModel,
|
|
20
|
+
target_python_version=PythonVersion.PY_311
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
parser = JsonSchemaParser(
|
|
24
|
+
json_schema_str,
|
|
25
|
+
data_model_type=data_model_types.data_model,
|
|
26
|
+
data_model_root_type=data_model_types.root_model,
|
|
27
|
+
data_model_field_type=data_model_types.field_model,
|
|
28
|
+
data_type_manager_type=data_model_types.data_type_manager,
|
|
29
|
+
dump_resolve_reference_action=data_model_types.dump_resolve_reference_action,
|
|
30
|
+
enum_field_as_literal=LiteralType.All,
|
|
31
|
+
class_name=class_name
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
model_code = parser.parse()
|
|
35
|
+
return model_code
|
|
36
|
+
|
|
37
|
+
@contextmanager
|
|
38
|
+
def json_schema_to_pydantic(json_schema: dict, class_name: str = 'MyModel') -> Generator[Type[BaseModel], None, None]:
|
|
39
|
+
"""
|
|
40
|
+
Convert a JSON schema to a Pydantic model and provide it as a context manager.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
json_schema (dict): The JSON schema to convert.
|
|
44
|
+
class_name (str, optional): The name of the generated Pydantic class. Defaults to 'MyModel'.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
```python
|
|
48
|
+
example_schema = {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"sentiment": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Sentiment of the text",
|
|
54
|
+
"enum": ["Positive", "Negative", "Neutral"],
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"required": ["sentiment"]
|
|
58
|
+
}
|
|
59
|
+
with json_schema_to_pydantic(example_schema) as ResponseModel:
|
|
60
|
+
instance = ResponseModel(sentiment='Positive')
|
|
61
|
+
print(instance.model_dump())
|
|
62
|
+
```
|
|
63
|
+
"""
|
|
64
|
+
# Convert the JSON schema dictionary to a JSON string
|
|
65
|
+
json_schema_str = json.dumps(json_schema)
|
|
66
|
+
|
|
67
|
+
# Generate Pydantic model code from the JSON schema string
|
|
68
|
+
model_code: str = _generate_model_code(json_schema_str, class_name)
|
|
69
|
+
|
|
70
|
+
# Create a unique module name using the id of the JSON schema string
|
|
71
|
+
module_name = f'dynamic_module_{id(json_schema_str)}'
|
|
72
|
+
|
|
73
|
+
# Create a new module object with the unique name and execute the generated model code in the context of the new module
|
|
74
|
+
mod = types.ModuleType(module_name)
|
|
75
|
+
exec(model_code, mod.__dict__)
|
|
76
|
+
model_class = getattr(mod, class_name)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
# Add the new module to sys.modules to make it importable
|
|
80
|
+
# This is necessary to avoid Pydantic errors related to undefined models
|
|
81
|
+
sys.modules[module_name] = mod
|
|
82
|
+
yield model_class
|
|
83
|
+
finally:
|
|
84
|
+
if module_name in sys.modules:
|
|
85
|
+
del sys.modules[module_name]
|
|
86
|
+
|
|
@@ -35,22 +35,44 @@
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
-
"
|
|
38
|
+
"tag_with_name_and_toname": {
|
|
39
39
|
"type": "object",
|
|
40
|
-
"
|
|
41
|
-
{
|
|
42
|
-
"required": [
|
|
43
|
-
"@name",
|
|
44
|
-
"@valueList"
|
|
45
|
-
]
|
|
46
|
-
},
|
|
40
|
+
"oneOf": [
|
|
47
41
|
{
|
|
48
42
|
"required": [
|
|
49
43
|
"@name",
|
|
50
|
-
"@
|
|
44
|
+
"@toName"
|
|
51
45
|
]
|
|
52
46
|
}
|
|
53
47
|
],
|
|
48
|
+
"properties": {
|
|
49
|
+
"@name": {
|
|
50
|
+
"$ref": "#/definitions/@name"
|
|
51
|
+
},
|
|
52
|
+
"@toName": {
|
|
53
|
+
"$ref": "#/definitions/@toName"
|
|
54
|
+
},
|
|
55
|
+
"$": {
|
|
56
|
+
"$ref": "#/definitions/$"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"tag_with_value_required_name": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"oneOf": [
|
|
63
|
+
{
|
|
64
|
+
"required": [
|
|
65
|
+
"@name",
|
|
66
|
+
"@value"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"required": [
|
|
71
|
+
"@name",
|
|
72
|
+
"@valueList"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
],
|
|
54
76
|
"properties": {
|
|
55
77
|
"@value": {
|
|
56
78
|
"$ref": "#/definitions/@value"
|
|
@@ -80,6 +102,13 @@
|
|
|
80
102
|
"items": {"$ref": "#/definitions/tag_with_value_required_name"}
|
|
81
103
|
}, {"$ref": "#/definitions/tag_with_value_required_name"}]
|
|
82
104
|
},
|
|
105
|
+
"tags_with_name_and_toname": {
|
|
106
|
+
"anyOf": [{"$ref": "#/definitions/tag_with_name_and_toname"},
|
|
107
|
+
{
|
|
108
|
+
"type": "array",
|
|
109
|
+
"items": {"$ref": "#/definitions/tag_with_name_and_toname"}
|
|
110
|
+
}]
|
|
111
|
+
},
|
|
83
112
|
"View": {
|
|
84
113
|
"type": "object",
|
|
85
114
|
"additionalProperties": true,
|
|
@@ -92,7 +121,8 @@
|
|
|
92
121
|
"Text": {"$ref": "#/definitions/tags_with_value_required_name"},
|
|
93
122
|
"HyperText": {"$ref": "#/definitions/tags_with_value_required_name"},
|
|
94
123
|
"View": {"$ref": "#/definitions/MaybeMultipleView"},
|
|
95
|
-
"TextArea": {"$ref": "#/definitions/MaybeMultipleTextAreas"}
|
|
124
|
+
"TextArea": {"$ref": "#/definitions/MaybeMultipleTextAreas"},
|
|
125
|
+
"Number": {"$ref": "#/definitions/tags_with_name_and_toname"}
|
|
96
126
|
}
|
|
97
127
|
},
|
|
98
128
|
"MaybeMultipleView": {
|
|
@@ -219,7 +249,8 @@
|
|
|
219
249
|
"Image": {"$ref": "#/definitions/tags_with_value_required_name"},
|
|
220
250
|
"Text": {"$ref": "#/definitions/tags_with_value_required_name"},
|
|
221
251
|
"HyperText": {"$ref": "#/definitions/tags_with_value_required_name"},
|
|
222
|
-
"TextArea": {"$ref": "#/definitions/MaybeMultipleTextAreas"}
|
|
252
|
+
"TextArea": {"$ref": "#/definitions/MaybeMultipleTextAreas"},
|
|
253
|
+
"Number": {"$ref": "#/definitions/tags_with_name_and_toname"}
|
|
223
254
|
}
|
|
224
255
|
}
|
|
225
256
|
}
|