label-studio-sdk 1.0.10__py3-none-any.whl → 1.0.11__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 +17 -1
- label_studio_sdk/_extensions/label_studio_tools/core/utils/json_schema.py +5 -0
- label_studio_sdk/base_client.py +8 -0
- label_studio_sdk/core/client_wrapper.py +34 -15
- label_studio_sdk/errors/__init__.py +3 -1
- label_studio_sdk/errors/not_found_error.py +9 -0
- label_studio_sdk/errors/unauthorized_error.py +9 -0
- label_studio_sdk/jwt_settings/__init__.py +2 -0
- label_studio_sdk/jwt_settings/client.py +259 -0
- label_studio_sdk/label_interface/control_tags.py +15 -2
- label_studio_sdk/label_interface/interface.py +80 -1
- label_studio_sdk/label_interface/object_tags.py +2 -2
- label_studio_sdk/projects/__init__.py +2 -1
- label_studio_sdk/projects/client.py +4 -0
- label_studio_sdk/projects/exports/client_ext.py +106 -40
- label_studio_sdk/projects/pauses/__init__.py +2 -0
- label_studio_sdk/projects/pauses/client.py +704 -0
- label_studio_sdk/projects/types/projects_update_response.py +10 -0
- label_studio_sdk/tokens/__init__.py +2 -0
- label_studio_sdk/tokens/client.py +470 -0
- label_studio_sdk/tokens/client_ext.py +94 -0
- label_studio_sdk/types/__init__.py +10 -0
- label_studio_sdk/types/access_token_response.py +22 -0
- label_studio_sdk/types/api_token_response.py +32 -0
- label_studio_sdk/types/jwt_settings_response.py +32 -0
- label_studio_sdk/types/model_provider_connection_provider.py +1 -1
- label_studio_sdk/types/pause.py +34 -0
- label_studio_sdk/types/pause_paused_by.py +5 -0
- label_studio_sdk/types/project.py +10 -0
- label_studio_sdk/types/prompt_version_provider.py +1 -1
- {label_studio_sdk-1.0.10.dist-info → label_studio_sdk-1.0.11.dist-info}/METADATA +2 -1
- {label_studio_sdk-1.0.10.dist-info → label_studio_sdk-1.0.11.dist-info}/RECORD +34 -20
- {label_studio_sdk-1.0.10.dist-info → label_studio_sdk-1.0.11.dist-info}/WHEEL +1 -1
- {label_studio_sdk-1.0.10.dist-info → label_studio_sdk-1.0.11.dist-info}/LICENSE +0 -0
label_studio_sdk/__init__.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# This file was auto-generated by Fern from our API Definition.
|
|
2
2
|
|
|
3
3
|
from .types import (
|
|
4
|
+
AccessTokenResponse,
|
|
4
5
|
Annotation,
|
|
5
6
|
AnnotationFilterOptions,
|
|
6
7
|
AnnotationLastAction,
|
|
7
8
|
AnnotationsDmField,
|
|
8
9
|
AnnotationsDmFieldLastAction,
|
|
10
|
+
ApiTokenResponse,
|
|
9
11
|
AzureBlobExportStorage,
|
|
10
12
|
AzureBlobExportStorageStatus,
|
|
11
13
|
AzureBlobImportStorage,
|
|
@@ -40,6 +42,7 @@ from .types import (
|
|
|
40
42
|
InferenceRunOrganization,
|
|
41
43
|
InferenceRunProjectSubset,
|
|
42
44
|
InferenceRunStatus,
|
|
45
|
+
JwtSettingsResponse,
|
|
43
46
|
KeyIndicatorValue,
|
|
44
47
|
KeyIndicators,
|
|
45
48
|
KeyIndicatorsItem,
|
|
@@ -58,6 +61,8 @@ from .types import (
|
|
|
58
61
|
ModelProviderConnectionOrganization,
|
|
59
62
|
ModelProviderConnectionProvider,
|
|
60
63
|
ModelProviderConnectionScope,
|
|
64
|
+
Pause,
|
|
65
|
+
PausePausedBy,
|
|
61
66
|
Prediction,
|
|
62
67
|
Project,
|
|
63
68
|
ProjectImport,
|
|
@@ -101,7 +106,7 @@ from .types import (
|
|
|
101
106
|
WebhookSerializerForUpdateActionsItem,
|
|
102
107
|
Workspace,
|
|
103
108
|
)
|
|
104
|
-
from .errors import BadRequestError, InternalServerError
|
|
109
|
+
from .errors import BadRequestError, InternalServerError, NotFoundError, UnauthorizedError
|
|
105
110
|
from . import (
|
|
106
111
|
actions,
|
|
107
112
|
annotations,
|
|
@@ -109,12 +114,14 @@ from . import (
|
|
|
109
114
|
export_storage,
|
|
110
115
|
files,
|
|
111
116
|
import_storage,
|
|
117
|
+
jwt_settings,
|
|
112
118
|
ml,
|
|
113
119
|
model_providers,
|
|
114
120
|
predictions,
|
|
115
121
|
projects,
|
|
116
122
|
prompts,
|
|
117
123
|
tasks,
|
|
124
|
+
tokens,
|
|
118
125
|
users,
|
|
119
126
|
versions,
|
|
120
127
|
views,
|
|
@@ -180,6 +187,7 @@ from .views import (
|
|
|
180
187
|
from .webhooks import WebhooksUpdateRequestActionsItem
|
|
181
188
|
|
|
182
189
|
__all__ = [
|
|
190
|
+
"AccessTokenResponse",
|
|
183
191
|
"ActionsCreateRequestFilters",
|
|
184
192
|
"ActionsCreateRequestFiltersConjunction",
|
|
185
193
|
"ActionsCreateRequestFiltersItemsItem",
|
|
@@ -198,6 +206,7 @@ __all__ = [
|
|
|
198
206
|
"AnnotationsCreateBulkResponseItem",
|
|
199
207
|
"AnnotationsDmField",
|
|
200
208
|
"AnnotationsDmFieldLastAction",
|
|
209
|
+
"ApiTokenResponse",
|
|
201
210
|
"AsyncLabelStudio",
|
|
202
211
|
"AzureBlobExportStorage",
|
|
203
212
|
"AzureBlobExportStorageStatus",
|
|
@@ -238,6 +247,7 @@ __all__ = [
|
|
|
238
247
|
"InferenceRunProjectSubset",
|
|
239
248
|
"InferenceRunStatus",
|
|
240
249
|
"InternalServerError",
|
|
250
|
+
"JwtSettingsResponse",
|
|
241
251
|
"KeyIndicatorValue",
|
|
242
252
|
"KeyIndicators",
|
|
243
253
|
"KeyIndicatorsItem",
|
|
@@ -264,6 +274,9 @@ __all__ = [
|
|
|
264
274
|
"ModelProviderConnectionOrganization",
|
|
265
275
|
"ModelProviderConnectionProvider",
|
|
266
276
|
"ModelProviderConnectionScope",
|
|
277
|
+
"NotFoundError",
|
|
278
|
+
"Pause",
|
|
279
|
+
"PausePausedBy",
|
|
267
280
|
"Prediction",
|
|
268
281
|
"Project",
|
|
269
282
|
"ProjectImport",
|
|
@@ -309,6 +322,7 @@ __all__ = [
|
|
|
309
322
|
"TaskFilterOptions",
|
|
310
323
|
"TasksListRequestFields",
|
|
311
324
|
"TasksListResponse",
|
|
325
|
+
"UnauthorizedError",
|
|
312
326
|
"UserSimple",
|
|
313
327
|
"UsersGetTokenResponse",
|
|
314
328
|
"UsersResetTokenResponse",
|
|
@@ -344,12 +358,14 @@ __all__ = [
|
|
|
344
358
|
"export_storage",
|
|
345
359
|
"files",
|
|
346
360
|
"import_storage",
|
|
361
|
+
"jwt_settings",
|
|
347
362
|
"ml",
|
|
348
363
|
"model_providers",
|
|
349
364
|
"predictions",
|
|
350
365
|
"projects",
|
|
351
366
|
"prompts",
|
|
352
367
|
"tasks",
|
|
368
|
+
"tokens",
|
|
353
369
|
"users",
|
|
354
370
|
"versions",
|
|
355
371
|
"views",
|
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
import types
|
|
3
3
|
import sys
|
|
4
4
|
import functools
|
|
5
|
+
import logging
|
|
5
6
|
from typing import Type, Dict, Any, Tuple, Generator
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from tempfile import TemporaryDirectory
|
|
@@ -11,6 +12,8 @@ from datamodel_code_generator.parser.jsonschema import JsonSchemaParser
|
|
|
11
12
|
from pydantic import BaseModel
|
|
12
13
|
from contextlib import contextmanager
|
|
13
14
|
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
@functools.lru_cache(maxsize=128)
|
|
16
19
|
def _generate_model_code(json_schema_str: str, class_name: str = 'MyModel') -> str:
|
|
@@ -65,6 +68,7 @@ def json_schema_to_pydantic(json_schema: dict, class_name: str = 'MyModel') -> G
|
|
|
65
68
|
json_schema_str = json.dumps(json_schema)
|
|
66
69
|
|
|
67
70
|
# Generate Pydantic model code from the JSON schema string
|
|
71
|
+
logger.debug(f"Generating Pydantic model code from json schema: {json_schema_str}")
|
|
68
72
|
model_code: str = _generate_model_code(json_schema_str, class_name)
|
|
69
73
|
|
|
70
74
|
# Create a unique module name using the id of the JSON schema string
|
|
@@ -79,6 +83,7 @@ def json_schema_to_pydantic(json_schema: dict, class_name: str = 'MyModel') -> G
|
|
|
79
83
|
# Add the new module to sys.modules to make it importable
|
|
80
84
|
# This is necessary to avoid Pydantic errors related to undefined models
|
|
81
85
|
sys.modules[module_name] = mod
|
|
86
|
+
logger.debug(f"Generated Pydantic model: {model_class}")
|
|
82
87
|
yield model_class
|
|
83
88
|
finally:
|
|
84
89
|
if module_name in sys.modules:
|
label_studio_sdk/base_client.py
CHANGED
|
@@ -23,6 +23,8 @@ from .prompts.client import PromptsClient
|
|
|
23
23
|
from .model_providers.client import ModelProvidersClient
|
|
24
24
|
from .comments.client import CommentsClient
|
|
25
25
|
from .workspaces.client import WorkspacesClient
|
|
26
|
+
from .tokens.client import TokensClient
|
|
27
|
+
from .jwt_settings.client import JwtSettingsClient
|
|
26
28
|
from .core.client_wrapper import AsyncClientWrapper
|
|
27
29
|
from .annotations.client import AsyncAnnotationsClient
|
|
28
30
|
from .users.client import AsyncUsersClient
|
|
@@ -41,6 +43,8 @@ from .prompts.client import AsyncPromptsClient
|
|
|
41
43
|
from .model_providers.client import AsyncModelProvidersClient
|
|
42
44
|
from .comments.client import AsyncCommentsClient
|
|
43
45
|
from .workspaces.client import AsyncWorkspacesClient
|
|
46
|
+
from .tokens.client import AsyncTokensClient
|
|
47
|
+
from .jwt_settings.client import AsyncJwtSettingsClient
|
|
44
48
|
|
|
45
49
|
|
|
46
50
|
class LabelStudioBase:
|
|
@@ -122,6 +126,8 @@ class LabelStudioBase:
|
|
|
122
126
|
self.model_providers = ModelProvidersClient(client_wrapper=self._client_wrapper)
|
|
123
127
|
self.comments = CommentsClient(client_wrapper=self._client_wrapper)
|
|
124
128
|
self.workspaces = WorkspacesClient(client_wrapper=self._client_wrapper)
|
|
129
|
+
self.tokens = TokensClient(client_wrapper=self._client_wrapper)
|
|
130
|
+
self.jwt_settings = JwtSettingsClient(client_wrapper=self._client_wrapper)
|
|
125
131
|
|
|
126
132
|
|
|
127
133
|
class AsyncLabelStudioBase:
|
|
@@ -203,6 +209,8 @@ class AsyncLabelStudioBase:
|
|
|
203
209
|
self.model_providers = AsyncModelProvidersClient(client_wrapper=self._client_wrapper)
|
|
204
210
|
self.comments = AsyncCommentsClient(client_wrapper=self._client_wrapper)
|
|
205
211
|
self.workspaces = AsyncWorkspacesClient(client_wrapper=self._client_wrapper)
|
|
212
|
+
self.tokens = AsyncTokensClient(client_wrapper=self._client_wrapper)
|
|
213
|
+
self.jwt_settings = AsyncJwtSettingsClient(client_wrapper=self._client_wrapper)
|
|
206
214
|
|
|
207
215
|
|
|
208
216
|
def _get_base_url(*, base_url: typing.Optional[str] = None, environment: LabelStudioEnvironment) -> str:
|
|
@@ -1,36 +1,55 @@
|
|
|
1
|
-
# This file was auto-generated by Fern from our API Definition.
|
|
2
|
-
|
|
3
1
|
import typing
|
|
2
|
+
|
|
4
3
|
import httpx
|
|
5
|
-
|
|
6
|
-
from .http_client import AsyncHttpClient
|
|
4
|
+
|
|
5
|
+
from .http_client import AsyncHttpClient, HttpClient
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class BaseClientWrapper:
|
|
10
|
-
def __init__(
|
|
11
|
-
self
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
*,
|
|
12
|
+
api_key: str,
|
|
13
|
+
base_url: str,
|
|
14
|
+
timeout: typing.Optional[float] = None,
|
|
15
|
+
):
|
|
12
16
|
self._base_url = base_url
|
|
13
17
|
self._timeout = timeout
|
|
14
18
|
|
|
19
|
+
# even in the async case, refreshing access token (when the existing one is expired) should be sync
|
|
20
|
+
from ..tokens.client_ext import TokensClientExt
|
|
21
|
+
self._tokens_client = TokensClientExt(base_url=base_url, api_key=api_key)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_timeout(self) -> typing.Optional[float]:
|
|
25
|
+
return self._timeout
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_base_url(self) -> str:
|
|
29
|
+
return self._base_url
|
|
30
|
+
|
|
31
|
+
|
|
15
32
|
def get_headers(self) -> typing.Dict[str, str]:
|
|
16
33
|
headers: typing.Dict[str, str] = {
|
|
17
34
|
"X-Fern-Language": "Python",
|
|
18
35
|
"X-Fern-SDK-Name": "label-studio-sdk",
|
|
19
|
-
"X-Fern-SDK-Version": "1.0.
|
|
36
|
+
"X-Fern-SDK-Version": "1.0.11",
|
|
20
37
|
}
|
|
21
|
-
|
|
38
|
+
if self._tokens_client._use_legacy_token:
|
|
39
|
+
headers["Authorization"] = f"Token {self._tokens_client.api_key}"
|
|
40
|
+
else:
|
|
41
|
+
headers["Authorization"] = f"Bearer {self._tokens_client.api_key}"
|
|
22
42
|
return headers
|
|
23
43
|
|
|
24
|
-
def get_base_url(self) -> str:
|
|
25
|
-
return self._base_url
|
|
26
|
-
|
|
27
|
-
def get_timeout(self) -> typing.Optional[float]:
|
|
28
|
-
return self._timeout
|
|
29
|
-
|
|
30
44
|
|
|
31
45
|
class SyncClientWrapper(BaseClientWrapper):
|
|
32
46
|
def __init__(
|
|
33
|
-
self,
|
|
47
|
+
self,
|
|
48
|
+
*,
|
|
49
|
+
api_key: str,
|
|
50
|
+
base_url: str,
|
|
51
|
+
timeout: typing.Optional[float] = None,
|
|
52
|
+
httpx_client: httpx.Client,
|
|
34
53
|
):
|
|
35
54
|
super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
|
|
36
55
|
self.httpx_client = HttpClient(
|
|
@@ -2,5 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from .bad_request_error import BadRequestError
|
|
4
4
|
from .internal_server_error import InternalServerError
|
|
5
|
+
from .not_found_error import NotFoundError
|
|
6
|
+
from .unauthorized_error import UnauthorizedError
|
|
5
7
|
|
|
6
|
-
__all__ = ["BadRequestError", "InternalServerError"]
|
|
8
|
+
__all__ = ["BadRequestError", "InternalServerError", "NotFoundError", "UnauthorizedError"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
|
2
|
+
|
|
3
|
+
from ..core.api_error import ApiError
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UnauthorizedError(ApiError):
|
|
8
|
+
def __init__(self, body: typing.Optional[typing.Any]):
|
|
9
|
+
super().__init__(status_code=401, body=body)
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
from ..core.client_wrapper import SyncClientWrapper
|
|
5
|
+
from ..core.request_options import RequestOptions
|
|
6
|
+
from ..types.jwt_settings_response import JwtSettingsResponse
|
|
7
|
+
from ..core.pydantic_utilities import parse_obj_as
|
|
8
|
+
from json.decoder import JSONDecodeError
|
|
9
|
+
from ..core.api_error import ApiError
|
|
10
|
+
from ..core.client_wrapper import AsyncClientWrapper
|
|
11
|
+
|
|
12
|
+
# this is used as the default value for optional parameters
|
|
13
|
+
OMIT = typing.cast(typing.Any, ...)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class JwtSettingsClient:
|
|
17
|
+
def __init__(self, *, client_wrapper: SyncClientWrapper):
|
|
18
|
+
self._client_wrapper = client_wrapper
|
|
19
|
+
|
|
20
|
+
def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> JwtSettingsResponse:
|
|
21
|
+
"""
|
|
22
|
+
Retrieve JWT settings for the currently active organization.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
request_options : typing.Optional[RequestOptions]
|
|
27
|
+
Request-specific configuration.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
JwtSettingsResponse
|
|
32
|
+
JWT settings retrieved successfully
|
|
33
|
+
|
|
34
|
+
Examples
|
|
35
|
+
--------
|
|
36
|
+
from label_studio_sdk import LabelStudio
|
|
37
|
+
|
|
38
|
+
client = LabelStudio(
|
|
39
|
+
api_key="YOUR_API_KEY",
|
|
40
|
+
)
|
|
41
|
+
client.jwt_settings.get()
|
|
42
|
+
"""
|
|
43
|
+
_response = self._client_wrapper.httpx_client.request(
|
|
44
|
+
"api/jwt/settings",
|
|
45
|
+
method="GET",
|
|
46
|
+
request_options=request_options,
|
|
47
|
+
)
|
|
48
|
+
try:
|
|
49
|
+
if 200 <= _response.status_code < 300:
|
|
50
|
+
return typing.cast(
|
|
51
|
+
JwtSettingsResponse,
|
|
52
|
+
parse_obj_as(
|
|
53
|
+
type_=JwtSettingsResponse, # type: ignore
|
|
54
|
+
object_=_response.json(),
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
_response_json = _response.json()
|
|
58
|
+
except JSONDecodeError:
|
|
59
|
+
raise ApiError(status_code=_response.status_code, body=_response.text)
|
|
60
|
+
raise ApiError(status_code=_response.status_code, body=_response_json)
|
|
61
|
+
|
|
62
|
+
def create(
|
|
63
|
+
self,
|
|
64
|
+
*,
|
|
65
|
+
api_tokens_enabled: bool,
|
|
66
|
+
legacy_api_tokens_enabled: bool,
|
|
67
|
+
api_token_ttl_days: int,
|
|
68
|
+
request_options: typing.Optional[RequestOptions] = None,
|
|
69
|
+
) -> JwtSettingsResponse:
|
|
70
|
+
"""
|
|
71
|
+
Update JWT settings for the currently active organization.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
api_tokens_enabled : bool
|
|
76
|
+
Whether JWT API tokens are enabled
|
|
77
|
+
|
|
78
|
+
legacy_api_tokens_enabled : bool
|
|
79
|
+
Whether legacy API tokens are enabled
|
|
80
|
+
|
|
81
|
+
api_token_ttl_days : int
|
|
82
|
+
Number of days before API tokens expire
|
|
83
|
+
|
|
84
|
+
request_options : typing.Optional[RequestOptions]
|
|
85
|
+
Request-specific configuration.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
JwtSettingsResponse
|
|
90
|
+
JWT settings updated successfully
|
|
91
|
+
|
|
92
|
+
Examples
|
|
93
|
+
--------
|
|
94
|
+
from label_studio_sdk import LabelStudio
|
|
95
|
+
|
|
96
|
+
client = LabelStudio(
|
|
97
|
+
api_key="YOUR_API_KEY",
|
|
98
|
+
)
|
|
99
|
+
client.jwt_settings.create(
|
|
100
|
+
api_tokens_enabled=True,
|
|
101
|
+
legacy_api_tokens_enabled=True,
|
|
102
|
+
api_token_ttl_days=1,
|
|
103
|
+
)
|
|
104
|
+
"""
|
|
105
|
+
_response = self._client_wrapper.httpx_client.request(
|
|
106
|
+
"api/jwt/settings",
|
|
107
|
+
method="POST",
|
|
108
|
+
json={
|
|
109
|
+
"api_tokens_enabled": api_tokens_enabled,
|
|
110
|
+
"legacy_api_tokens_enabled": legacy_api_tokens_enabled,
|
|
111
|
+
"api_token_ttl_days": api_token_ttl_days,
|
|
112
|
+
},
|
|
113
|
+
request_options=request_options,
|
|
114
|
+
omit=OMIT,
|
|
115
|
+
)
|
|
116
|
+
try:
|
|
117
|
+
if 200 <= _response.status_code < 300:
|
|
118
|
+
return typing.cast(
|
|
119
|
+
JwtSettingsResponse,
|
|
120
|
+
parse_obj_as(
|
|
121
|
+
type_=JwtSettingsResponse, # type: ignore
|
|
122
|
+
object_=_response.json(),
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
_response_json = _response.json()
|
|
126
|
+
except JSONDecodeError:
|
|
127
|
+
raise ApiError(status_code=_response.status_code, body=_response.text)
|
|
128
|
+
raise ApiError(status_code=_response.status_code, body=_response_json)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class AsyncJwtSettingsClient:
|
|
132
|
+
def __init__(self, *, client_wrapper: AsyncClientWrapper):
|
|
133
|
+
self._client_wrapper = client_wrapper
|
|
134
|
+
|
|
135
|
+
async def get(self, *, request_options: typing.Optional[RequestOptions] = None) -> JwtSettingsResponse:
|
|
136
|
+
"""
|
|
137
|
+
Retrieve JWT settings for the currently active organization.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
request_options : typing.Optional[RequestOptions]
|
|
142
|
+
Request-specific configuration.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
JwtSettingsResponse
|
|
147
|
+
JWT settings retrieved successfully
|
|
148
|
+
|
|
149
|
+
Examples
|
|
150
|
+
--------
|
|
151
|
+
import asyncio
|
|
152
|
+
|
|
153
|
+
from label_studio_sdk import AsyncLabelStudio
|
|
154
|
+
|
|
155
|
+
client = AsyncLabelStudio(
|
|
156
|
+
api_key="YOUR_API_KEY",
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def main() -> None:
|
|
161
|
+
await client.jwt_settings.get()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
asyncio.run(main())
|
|
165
|
+
"""
|
|
166
|
+
_response = await self._client_wrapper.httpx_client.request(
|
|
167
|
+
"api/jwt/settings",
|
|
168
|
+
method="GET",
|
|
169
|
+
request_options=request_options,
|
|
170
|
+
)
|
|
171
|
+
try:
|
|
172
|
+
if 200 <= _response.status_code < 300:
|
|
173
|
+
return typing.cast(
|
|
174
|
+
JwtSettingsResponse,
|
|
175
|
+
parse_obj_as(
|
|
176
|
+
type_=JwtSettingsResponse, # type: ignore
|
|
177
|
+
object_=_response.json(),
|
|
178
|
+
),
|
|
179
|
+
)
|
|
180
|
+
_response_json = _response.json()
|
|
181
|
+
except JSONDecodeError:
|
|
182
|
+
raise ApiError(status_code=_response.status_code, body=_response.text)
|
|
183
|
+
raise ApiError(status_code=_response.status_code, body=_response_json)
|
|
184
|
+
|
|
185
|
+
async def create(
|
|
186
|
+
self,
|
|
187
|
+
*,
|
|
188
|
+
api_tokens_enabled: bool,
|
|
189
|
+
legacy_api_tokens_enabled: bool,
|
|
190
|
+
api_token_ttl_days: int,
|
|
191
|
+
request_options: typing.Optional[RequestOptions] = None,
|
|
192
|
+
) -> JwtSettingsResponse:
|
|
193
|
+
"""
|
|
194
|
+
Update JWT settings for the currently active organization.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
api_tokens_enabled : bool
|
|
199
|
+
Whether JWT API tokens are enabled
|
|
200
|
+
|
|
201
|
+
legacy_api_tokens_enabled : bool
|
|
202
|
+
Whether legacy API tokens are enabled
|
|
203
|
+
|
|
204
|
+
api_token_ttl_days : int
|
|
205
|
+
Number of days before API tokens expire
|
|
206
|
+
|
|
207
|
+
request_options : typing.Optional[RequestOptions]
|
|
208
|
+
Request-specific configuration.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
JwtSettingsResponse
|
|
213
|
+
JWT settings updated successfully
|
|
214
|
+
|
|
215
|
+
Examples
|
|
216
|
+
--------
|
|
217
|
+
import asyncio
|
|
218
|
+
|
|
219
|
+
from label_studio_sdk import AsyncLabelStudio
|
|
220
|
+
|
|
221
|
+
client = AsyncLabelStudio(
|
|
222
|
+
api_key="YOUR_API_KEY",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
async def main() -> None:
|
|
227
|
+
await client.jwt_settings.create(
|
|
228
|
+
api_tokens_enabled=True,
|
|
229
|
+
legacy_api_tokens_enabled=True,
|
|
230
|
+
api_token_ttl_days=1,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
asyncio.run(main())
|
|
235
|
+
"""
|
|
236
|
+
_response = await self._client_wrapper.httpx_client.request(
|
|
237
|
+
"api/jwt/settings",
|
|
238
|
+
method="POST",
|
|
239
|
+
json={
|
|
240
|
+
"api_tokens_enabled": api_tokens_enabled,
|
|
241
|
+
"legacy_api_tokens_enabled": legacy_api_tokens_enabled,
|
|
242
|
+
"api_token_ttl_days": api_token_ttl_days,
|
|
243
|
+
},
|
|
244
|
+
request_options=request_options,
|
|
245
|
+
omit=OMIT,
|
|
246
|
+
)
|
|
247
|
+
try:
|
|
248
|
+
if 200 <= _response.status_code < 300:
|
|
249
|
+
return typing.cast(
|
|
250
|
+
JwtSettingsResponse,
|
|
251
|
+
parse_obj_as(
|
|
252
|
+
type_=JwtSettingsResponse, # type: ignore
|
|
253
|
+
object_=_response.json(),
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
_response_json = _response.json()
|
|
257
|
+
except JSONDecodeError:
|
|
258
|
+
raise ApiError(status_code=_response.status_code, body=_response.text)
|
|
259
|
+
raise ApiError(status_code=_response.status_code, body=_response_json)
|
|
@@ -92,6 +92,12 @@ class ControlTag(LabelStudioTag):
|
|
|
92
92
|
and tag.attrib.get("toName")
|
|
93
93
|
and tag.tag not in _NOT_CONTROL_TAGS
|
|
94
94
|
)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def is_output_required(self):
|
|
98
|
+
# TextArea can be blank unless user specifies "required"="true" in the attribute
|
|
99
|
+
required_in_attr = str(self.attr.get("required", "false")).lower() == "true"
|
|
100
|
+
return required_in_attr or False
|
|
95
101
|
|
|
96
102
|
def to_json_schema(self):
|
|
97
103
|
"""
|
|
@@ -566,6 +572,7 @@ class LabelsTag(ControlTag):
|
|
|
566
572
|
"properties": {
|
|
567
573
|
"start": {
|
|
568
574
|
"type": "integer",
|
|
575
|
+
# TODO: this is incompatible with the OpenAI API using PredictedOutputs
|
|
569
576
|
"minimum": 0
|
|
570
577
|
},
|
|
571
578
|
"end": {
|
|
@@ -985,8 +992,14 @@ class TextAreaTag(ControlTag):
|
|
|
985
992
|
dict: A dictionary representing the JSON Schema compatible with OpenAPI 3.
|
|
986
993
|
"""
|
|
987
994
|
return {
|
|
988
|
-
"
|
|
989
|
-
|
|
995
|
+
"oneOf": [
|
|
996
|
+
{"type": "string"},
|
|
997
|
+
{
|
|
998
|
+
"type": "array",
|
|
999
|
+
"items": {"type": "string"}
|
|
1000
|
+
}
|
|
1001
|
+
],
|
|
1002
|
+
"description": f"Text or list of texts for {self.to_name[0]}"
|
|
990
1003
|
}
|
|
991
1004
|
|
|
992
1005
|
|
|
@@ -312,6 +312,15 @@ class LabelInterface:
|
|
|
312
312
|
"""
|
|
313
313
|
regions = []
|
|
314
314
|
for control_tag_name, payload in data.items():
|
|
315
|
+
|
|
316
|
+
if payload is None:
|
|
317
|
+
logger.warning(
|
|
318
|
+
f"Payload for control tag '{control_tag_name}' is None: "
|
|
319
|
+
"it can be ok if this payload is autogenerated by LLMs - "
|
|
320
|
+
"try to adjust the prompt to generate valid payloads. "
|
|
321
|
+
"Otherwise, it can signify an error")
|
|
322
|
+
continue
|
|
323
|
+
|
|
315
324
|
if control_tag_name not in self._controls:
|
|
316
325
|
logger.info(f"Control tag '{control_tag_name}' not found in the config")
|
|
317
326
|
continue
|
|
@@ -545,12 +554,16 @@ class LabelInterface:
|
|
|
545
554
|
Returns:
|
|
546
555
|
dict: A dictionary representing the JSON Schema.
|
|
547
556
|
"""
|
|
557
|
+
required_outputs = [
|
|
558
|
+
name for name, control in self._controls.items()
|
|
559
|
+
if control.is_output_required
|
|
560
|
+
]
|
|
548
561
|
return {
|
|
549
562
|
"type": "object",
|
|
550
563
|
"properties": {
|
|
551
564
|
name: control.to_json_schema() for name, control in self._controls.items()
|
|
552
565
|
},
|
|
553
|
-
"required":
|
|
566
|
+
"required": required_outputs
|
|
554
567
|
}
|
|
555
568
|
|
|
556
569
|
def parse(self, config_string: str) -> Tuple[Dict, Dict, Dict, etree._Element]:
|
|
@@ -902,6 +915,72 @@ class LabelInterface:
|
|
|
902
915
|
logger.debug(f'Sample annotation {annotation_dct} failed validation for label config {self.config}')
|
|
903
916
|
return None
|
|
904
917
|
|
|
918
|
+
def generate_complete_sample_task(self, raise_on_failure: bool = False) -> Optional[dict]:
|
|
919
|
+
"""Generate a complete sample task with annotations and predictions.
|
|
920
|
+
|
|
921
|
+
This method combines the generation of a sample task, sample prediction, and sample annotation
|
|
922
|
+
into a single method call, returning a complete task structure.
|
|
923
|
+
|
|
924
|
+
Args:
|
|
925
|
+
raise_on_failure: If True, will raise ValueError when any step
|
|
926
|
+
(including annotation/prediction generation) fails
|
|
927
|
+
|
|
928
|
+
Raises:
|
|
929
|
+
ValueError: If raise_on_failure is True and any generation step fails.
|
|
930
|
+
|
|
931
|
+
Example:
|
|
932
|
+
{
|
|
933
|
+
'data': {'text': 'Sample text for labeling'},
|
|
934
|
+
'annotations': [
|
|
935
|
+
{'was_cancelled': False,
|
|
936
|
+
'ground_truth': False,
|
|
937
|
+
'completed_by': -1,
|
|
938
|
+
'result': [
|
|
939
|
+
{'id': 'b05da11d-3ffc-4657-8b8d-f5bc37cd59ac',
|
|
940
|
+
'from_name': 'sentiment',
|
|
941
|
+
'to_name': 'text',
|
|
942
|
+
'type': 'choices',
|
|
943
|
+
'value': {'choices': ['Negative']}}
|
|
944
|
+
]
|
|
945
|
+
}
|
|
946
|
+
],
|
|
947
|
+
'predictions': [
|
|
948
|
+
{'model_version': 'sample model version',
|
|
949
|
+
'score': 0.95,
|
|
950
|
+
'result': [
|
|
951
|
+
{'id': 'e7bd76e6-4e88-4eb3-b433-55e03661bf5d',
|
|
952
|
+
'from_name': 'sentiment',
|
|
953
|
+
'to_name': 'text',
|
|
954
|
+
'type': 'choices',
|
|
955
|
+
'value': {'choices': ['Neutral']}}
|
|
956
|
+
]
|
|
957
|
+
}
|
|
958
|
+
]
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
NOTE: `id` field in result is not required when importing predictions; it will be generated automatically.
|
|
962
|
+
NOTE: for each control tag, depends on tag.to_json_schema() being implemented correctly
|
|
963
|
+
"""
|
|
964
|
+
sample_task = self.generate_sample_task()
|
|
965
|
+
if not sample_task:
|
|
966
|
+
if raise_on_failure:
|
|
967
|
+
raise ValueError("LabelInterface.generate_sample_task failed to generate a valid sample task")
|
|
968
|
+
return None
|
|
969
|
+
|
|
970
|
+
sample_prediction = self.generate_sample_prediction()
|
|
971
|
+
if not sample_prediction and raise_on_failure:
|
|
972
|
+
raise ValueError("LabelInterface.generate_sample_prediction failed to generate a valid prediction")
|
|
973
|
+
|
|
974
|
+
sample_annotation = self.generate_sample_annotation()
|
|
975
|
+
if not sample_annotation and raise_on_failure:
|
|
976
|
+
raise ValueError("LabelInterface.generate_sample_annotation failed to generate a valid annotation")
|
|
977
|
+
|
|
978
|
+
return {
|
|
979
|
+
"data": sample_task,
|
|
980
|
+
"annotations": [sample_annotation] if sample_annotation else [],
|
|
981
|
+
"predictions": [sample_prediction] if sample_prediction else []
|
|
982
|
+
}
|
|
983
|
+
|
|
905
984
|
#####
|
|
906
985
|
##### COMPATIBILITY LAYER
|
|
907
986
|
#####
|