label-studio-sdk 0.0.27__tar.gz → 0.0.29__tar.gz
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-0.0.27/label_studio_sdk.egg-info → label-studio-sdk-0.0.29}/PKG-INFO +1 -1
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/README.md +5 -1
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk/__init__.py +1 -1
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk/client.py +38 -13
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk/project.py +33 -2
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29/label_studio_sdk.egg-info}/PKG-INFO +1 -1
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk.egg-info/SOURCES.txt +1 -0
- label-studio-sdk-0.0.29/tests/import_test.py +35 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/LICENSE +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/MANIFEST.in +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/docs/__init__.py +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk/data_manager.py +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk/users.py +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk/utils.py +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk/workspaces.py +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk.egg-info/dependency_links.txt +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk.egg-info/requires.txt +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk.egg-info/top_level.txt +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/requirements.txt +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/setup.cfg +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/setup.py +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/tests/__init__.py +0 -0
- {label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/tests/test_client.py +0 -0
|
@@ -17,7 +17,7 @@ If you want to take action not supported natively by the SDK, you can [call the
|
|
|
17
17
|
This is the first release of the Label Studio SDK. It supports Label Studio Enterprise, Label Studio Teams, and Label Studio Community.
|
|
18
18
|
|
|
19
19
|
- **Find a bug?** [Create a GitHub issue](https://github.com/heartexlabs/label-studio-sdk/issues)!
|
|
20
|
-
- **Have a question?** [Join the Slack Community](https://slack.
|
|
20
|
+
- **Have a question?** [Join the Slack Community](https://slack.labelstud.io/?source=github-sdk)!
|
|
21
21
|
- **Want to contribute?** [See the contributing guide](https://github.com/heartexlabs/label-studio-sdk/CONTRIBUTING.md)
|
|
22
22
|
|
|
23
23
|
## Quickstart
|
|
@@ -43,6 +43,10 @@ The Label Studio SDK includes the following:
|
|
|
43
43
|
|
|
44
44
|
For all the details, see the [reference documentation](https://labelstud.io/sdk) or [review the code directly](https://github.com/heartexlabs/label-studio-sdk/tree/master/label_studio_sdk).
|
|
45
45
|
|
|
46
|
+
## Error logging
|
|
47
|
+
|
|
48
|
+
To see error logs, you can use `stderr` (we use `logger.error()` for error output). If you use console to run SDK commands, you will see all errors there.
|
|
49
|
+
|
|
46
50
|
## Contribute to the SDK
|
|
47
51
|
|
|
48
52
|
If you want to extend the SDK:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
""" .. include::../docs/client.md
|
|
2
2
|
"""
|
|
3
|
+
import json
|
|
3
4
|
import warnings
|
|
4
5
|
import logging
|
|
5
6
|
import requests
|
|
@@ -7,11 +8,12 @@ import requests
|
|
|
7
8
|
from typing import Optional
|
|
8
9
|
from pydantic import BaseModel, constr, root_validator
|
|
9
10
|
from requests.adapters import HTTPAdapter
|
|
11
|
+
from types import SimpleNamespace
|
|
10
12
|
|
|
11
13
|
logger = logging.getLogger(__name__)
|
|
12
14
|
|
|
13
15
|
MAX_RETRIES = 3
|
|
14
|
-
TIMEOUT = (
|
|
16
|
+
TIMEOUT = (10.0, 180.0)
|
|
15
17
|
HEADERS = {}
|
|
16
18
|
|
|
17
19
|
|
|
@@ -42,7 +44,7 @@ class Client(object):
|
|
|
42
44
|
cookies: dict = None,
|
|
43
45
|
oidc_token=None,
|
|
44
46
|
versions=None,
|
|
45
|
-
make_request_raise=True
|
|
47
|
+
make_request_raise=True,
|
|
46
48
|
):
|
|
47
49
|
"""Initialize the client. Do this before using other Label Studio SDK classes and methods in your script.
|
|
48
50
|
|
|
@@ -95,7 +97,6 @@ class Client(object):
|
|
|
95
97
|
self.versions = versions if versions else self.get_versions()
|
|
96
98
|
self.is_enterprise = 'label-studio-enterprise-backend' in self.versions
|
|
97
99
|
|
|
98
|
-
|
|
99
100
|
def get_versions(self):
|
|
100
101
|
"""Call /version api and get all Label Studio component versions
|
|
101
102
|
|
|
@@ -259,7 +260,7 @@ class Client(object):
|
|
|
259
260
|
users.append(User(**user_data))
|
|
260
261
|
return users
|
|
261
262
|
|
|
262
|
-
def create_user(self, user):
|
|
263
|
+
def create_user(self, user, exist_ok=True):
|
|
263
264
|
"""Create a new user
|
|
264
265
|
|
|
265
266
|
Parameters
|
|
@@ -267,6 +268,8 @@ class Client(object):
|
|
|
267
268
|
user: User or dict
|
|
268
269
|
User instance, you can initialize it this way:
|
|
269
270
|
User(username='x', email='x@x.xx', first_name='X', last_name='Z')
|
|
271
|
+
exist_ok: bool
|
|
272
|
+
True by default, it won't print error if user exists and exist_ok=True
|
|
270
273
|
|
|
271
274
|
Returns
|
|
272
275
|
-------
|
|
@@ -278,7 +281,7 @@ class Client(object):
|
|
|
278
281
|
|
|
279
282
|
payload = (
|
|
280
283
|
{
|
|
281
|
-
'username': user.username,
|
|
284
|
+
'username': user.username if user.username else user.email,
|
|
282
285
|
'email': user.email,
|
|
283
286
|
'first_name': user.first_name,
|
|
284
287
|
'last_name': user.last_name,
|
|
@@ -288,13 +291,16 @@ class Client(object):
|
|
|
288
291
|
else user
|
|
289
292
|
)
|
|
290
293
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
294
|
+
response = self.make_request('POST', '/api/users', json=payload, raise_exceptions=False)
|
|
295
|
+
user_data = response.json()
|
|
296
|
+
user_data['client'] = self
|
|
297
|
+
|
|
298
|
+
if response.status_code < 400:
|
|
295
299
|
return User(**user_data)
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
else:
|
|
301
|
+
if 'already exists' in response.text and exist_ok is True:
|
|
302
|
+
return None
|
|
303
|
+
logger.error('Create user error: ' + str(response.json()))
|
|
298
304
|
return None
|
|
299
305
|
|
|
300
306
|
def get_workspaces(self):
|
|
@@ -367,6 +373,11 @@ class Client(object):
|
|
|
367
373
|
"""
|
|
368
374
|
if 'timeout' not in kwargs:
|
|
369
375
|
kwargs['timeout'] = TIMEOUT
|
|
376
|
+
|
|
377
|
+
raise_exceptions = self.make_request_raise
|
|
378
|
+
if 'raise_exceptions' in kwargs: # kwargs have higher priority
|
|
379
|
+
raise_exceptions = kwargs.pop('raise_exceptions')
|
|
380
|
+
|
|
370
381
|
logger.debug(f'{method}: {url} with args={args}, kwargs={kwargs}')
|
|
371
382
|
response = self.session.request(
|
|
372
383
|
method,
|
|
@@ -376,8 +387,22 @@ class Client(object):
|
|
|
376
387
|
*args,
|
|
377
388
|
**kwargs,
|
|
378
389
|
)
|
|
379
|
-
|
|
380
|
-
|
|
390
|
+
|
|
391
|
+
if raise_exceptions:
|
|
392
|
+
if response.status_code >= 400:
|
|
393
|
+
try:
|
|
394
|
+
content = json.dumps(json.loads(response.content), indent=2)
|
|
395
|
+
except:
|
|
396
|
+
content = response.text
|
|
397
|
+
|
|
398
|
+
logger.error(
|
|
399
|
+
f'\n--------------------------------------------\n'
|
|
400
|
+
f'Request URL: {response.url}\n'
|
|
401
|
+
f'Response status code: {response.status_code}\n'
|
|
402
|
+
f'Response content:\n{content}\n\n'
|
|
403
|
+
f'SDK error traceback:')
|
|
404
|
+
response.raise_for_status()
|
|
405
|
+
|
|
381
406
|
return response
|
|
382
407
|
|
|
383
408
|
def sync_storage(self, storage_type, storage_id):
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
6
|
import pathlib
|
|
7
|
+
import time
|
|
7
8
|
|
|
8
9
|
from enum import Enum, auto
|
|
9
10
|
from random import sample, shuffle
|
|
@@ -447,7 +448,7 @@ class Project(Client):
|
|
|
447
448
|
session=client.session,
|
|
448
449
|
extra_headers=client.headers,
|
|
449
450
|
versions=client.versions,
|
|
450
|
-
make_request_raise=client.make_request_raise
|
|
451
|
+
make_request_raise=client.make_request_raise,
|
|
451
452
|
)
|
|
452
453
|
if params and isinstance(params, dict):
|
|
453
454
|
# TODO: validate project parameters
|
|
@@ -518,7 +519,37 @@ class Project(Client):
|
|
|
518
519
|
raise TypeError(
|
|
519
520
|
f'Not supported type provided as "tasks" argument: {type(tasks)}'
|
|
520
521
|
)
|
|
521
|
-
|
|
522
|
+
response = response.json()
|
|
523
|
+
|
|
524
|
+
if 'import' in response:
|
|
525
|
+
# check import status
|
|
526
|
+
timeout = 300
|
|
527
|
+
fibonacci_backoff = [1, 1]
|
|
528
|
+
|
|
529
|
+
start_time = time.time()
|
|
530
|
+
|
|
531
|
+
while True:
|
|
532
|
+
import_status = self.make_request(
|
|
533
|
+
method='GET',
|
|
534
|
+
url=f'/api/projects/{self.id}/imports/{response["import"]}',
|
|
535
|
+
).json()
|
|
536
|
+
|
|
537
|
+
if import_status['status'] == 'completed':
|
|
538
|
+
return import_status['task_ids']
|
|
539
|
+
|
|
540
|
+
if import_status['status'] == 'failed':
|
|
541
|
+
raise LabelStudioException(import_status['error'])
|
|
542
|
+
|
|
543
|
+
if time.time() - start_time >= timeout:
|
|
544
|
+
raise LabelStudioException('Import timeout')
|
|
545
|
+
|
|
546
|
+
time.sleep(fibonacci_backoff[0])
|
|
547
|
+
fibonacci_backoff = [
|
|
548
|
+
fibonacci_backoff[1],
|
|
549
|
+
fibonacci_backoff[0] + fibonacci_backoff[1],
|
|
550
|
+
]
|
|
551
|
+
|
|
552
|
+
return response['task_ids']
|
|
522
553
|
|
|
523
554
|
def export_tasks(
|
|
524
555
|
self,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
LABEL_STUDIO_URL = 'https://fb-lsdv-5218.dev.heartex.com'
|
|
2
|
+
API_KEY = 'aaf73cc7ac1e681212a9919d99e907965a32ff37'
|
|
3
|
+
|
|
4
|
+
# Import the SDK and the client module
|
|
5
|
+
from label_studio_sdk import Client
|
|
6
|
+
|
|
7
|
+
# Connect to the Label Studio API and check the connection
|
|
8
|
+
ls = Client(url=LABEL_STUDIO_URL, api_key=API_KEY)
|
|
9
|
+
ls.check_connection()
|
|
10
|
+
|
|
11
|
+
project = ls.start_project(
|
|
12
|
+
title='Image Project',
|
|
13
|
+
label_config='''
|
|
14
|
+
<View>
|
|
15
|
+
|
|
16
|
+
<Header value="Select label and click the image to start"/>
|
|
17
|
+
<Image name="image" value="$image" zoom="true"/>
|
|
18
|
+
|
|
19
|
+
<PolygonLabels name="label" toName="image"
|
|
20
|
+
strokeWidth="3" pointSize="small"
|
|
21
|
+
opacity="0.9">
|
|
22
|
+
<Label value="Airplane" background="red"/>
|
|
23
|
+
<Label value="Car" background="blue"/>
|
|
24
|
+
</PolygonLabels>
|
|
25
|
+
|
|
26
|
+
</View>
|
|
27
|
+
'''
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
project.import_tasks(
|
|
31
|
+
[
|
|
32
|
+
{'image': 'https://data.heartex.net/open-images/train_0/mini/0045dd96bf73936c.jpg'},
|
|
33
|
+
{'image': 'https://data.heartex.net/open-images/train_0/mini/0083d02f6ad18b38.jpg'}
|
|
34
|
+
]
|
|
35
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{label-studio-sdk-0.0.27 → label-studio-sdk-0.0.29}/label_studio_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|