supervisely 6.73.227__py3-none-any.whl → 6.73.229__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 supervisely might be problematic. Click here for more details.
- supervisely/__init__.py +1 -1
- supervisely/_utils.py +55 -1
- supervisely/api/annotation_api.py +2 -2
- supervisely/api/api.py +90 -43
- supervisely/api/app_api.py +3 -3
- supervisely/api/dataset_api.py +90 -1
- supervisely/api/file_api.py +6 -6
- supervisely/api/image_api.py +95 -9
- supervisely/api/pointcloud/pointcloud_api.py +4 -4
- supervisely/api/project_api.py +285 -1
- supervisely/api/video/video_annotation_api.py +1 -1
- supervisely/api/video/video_api.py +2 -2
- supervisely/api/volume/volume_api.py +2 -2
- supervisely/io/env.py +15 -0
- supervisely/project/project.py +1 -1
- supervisely/project/project_type.py +4 -1
- supervisely/project/video_project.py +1 -1
- {supervisely-6.73.227.dist-info → supervisely-6.73.229.dist-info}/METADATA +1 -1
- {supervisely-6.73.227.dist-info → supervisely-6.73.229.dist-info}/RECORD +23 -23
- {supervisely-6.73.227.dist-info → supervisely-6.73.229.dist-info}/LICENSE +0 -0
- {supervisely-6.73.227.dist-info → supervisely-6.73.229.dist-info}/WHEEL +0 -0
- {supervisely-6.73.227.dist-info → supervisely-6.73.229.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.227.dist-info → supervisely-6.73.229.dist-info}/top_level.txt +0 -0
supervisely/__init__.py
CHANGED
|
@@ -309,4 +309,4 @@ except Exception as e:
|
|
|
309
309
|
# If new changes in Supervisely Python SDK require upgrade of the Supervisely instance
|
|
310
310
|
# set a new value for the environment variable MINIMUM_INSTANCE_VERSION_FOR_SDK, otherwise
|
|
311
311
|
# users can face compatibility issues, if the instance version is lower than the SDK version.
|
|
312
|
-
os.environ["MINIMUM_INSTANCE_VERSION_FOR_SDK"] = "6.
|
|
312
|
+
os.environ["MINIMUM_INSTANCE_VERSION_FOR_SDK"] = "6.12.5"
|
supervisely/_utils.py
CHANGED
|
@@ -14,7 +14,7 @@ import urllib
|
|
|
14
14
|
from datetime import datetime
|
|
15
15
|
from functools import wraps
|
|
16
16
|
from tempfile import gettempdir
|
|
17
|
-
from typing import List, Literal, Optional
|
|
17
|
+
from typing import Any, Dict, List, Literal, Optional, Tuple
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
from requests.utils import DEFAULT_CA_BUNDLE_PATH
|
|
@@ -316,6 +316,15 @@ def get_readable_datetime(value: str) -> str:
|
|
|
316
316
|
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
317
317
|
|
|
318
318
|
|
|
319
|
+
def get_unix_timestamp() -> int:
|
|
320
|
+
"""Return the current Unix timestamp.
|
|
321
|
+
|
|
322
|
+
:return: Current Unix timestamp.
|
|
323
|
+
:rtype: int
|
|
324
|
+
"""
|
|
325
|
+
return int(time.time())
|
|
326
|
+
|
|
327
|
+
|
|
319
328
|
def get_certificates_list(path: str = DEFAULT_CA_BUNDLE_PATH) -> List[str]:
|
|
320
329
|
with open(path, "r", encoding="ascii") as f:
|
|
321
330
|
content = f.read().strip()
|
|
@@ -385,6 +394,51 @@ def add_callback(func, callback):
|
|
|
385
394
|
return wrapper
|
|
386
395
|
|
|
387
396
|
|
|
397
|
+
def compare_dicts(
|
|
398
|
+
template: Dict[Any, Any], data: Dict[Any, Any], strict: bool = True
|
|
399
|
+
) -> Tuple[List[str], List[str]]:
|
|
400
|
+
"""Compare two dictionaries recursively (by keys only) and return lists of missing and extra fields.
|
|
401
|
+
If strict is True, the keys of the template and data dictionaries must match exactly.
|
|
402
|
+
Otherwise, the data dictionary may contain additional keys that are not in the template dictionary.
|
|
403
|
+
|
|
404
|
+
:param template: The template dictionary.
|
|
405
|
+
:type template: Dict[Any, Any]
|
|
406
|
+
:param data: The data dictionary.
|
|
407
|
+
:type data: Dict[Any, Any]
|
|
408
|
+
:param strict: If True, the keys of the template and data dictionaries must match exactly.
|
|
409
|
+
:type strict: bool, optional
|
|
410
|
+
:return: A tuple containing a list of missing fields and a list of extra fields.
|
|
411
|
+
:rtype: Tuple[List[str], List[str]]
|
|
412
|
+
"""
|
|
413
|
+
missing_fields = []
|
|
414
|
+
extra_fields = []
|
|
415
|
+
|
|
416
|
+
if not isinstance(template, dict) or not isinstance(data, dict):
|
|
417
|
+
return missing_fields, extra_fields
|
|
418
|
+
|
|
419
|
+
if strict:
|
|
420
|
+
template_keys = set(template.keys())
|
|
421
|
+
data_keys = set(data.keys())
|
|
422
|
+
|
|
423
|
+
missing_fields = list(template_keys - data_keys)
|
|
424
|
+
extra_fields = list(data_keys - template_keys)
|
|
425
|
+
|
|
426
|
+
for key in template_keys & data_keys:
|
|
427
|
+
sub_missing, sub_extra = compare_dicts(template[key], data[key], strict)
|
|
428
|
+
missing_fields.extend([f"{key}.{m}" for m in sub_missing])
|
|
429
|
+
extra_fields.extend([f"{key}.{e}" for e in sub_extra])
|
|
430
|
+
else:
|
|
431
|
+
for key in template:
|
|
432
|
+
if key not in data:
|
|
433
|
+
missing_fields.append(key)
|
|
434
|
+
else:
|
|
435
|
+
sub_missing, sub_extra = compare_dicts(template[key], data[key], strict)
|
|
436
|
+
missing_fields.extend([f"{key}.{m}" for m in sub_missing])
|
|
437
|
+
extra_fields.extend([f"{key}.{e}" for e in sub_extra])
|
|
438
|
+
|
|
439
|
+
return missing_fields, extra_fields
|
|
440
|
+
|
|
441
|
+
|
|
388
442
|
def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
|
|
389
443
|
"""
|
|
390
444
|
Get the current event loop or create a new one if it doesn't exist.
|
|
@@ -1388,7 +1388,7 @@ class AnnotationApi(ModuleApi):
|
|
|
1388
1388
|
ann_info = loop.run_until_complete(api.annotation.download_async(image_id))
|
|
1389
1389
|
"""
|
|
1390
1390
|
if semaphore is None:
|
|
1391
|
-
semaphore = self._api.
|
|
1391
|
+
semaphore = self._api.get_default_semaphore()
|
|
1392
1392
|
async with semaphore:
|
|
1393
1393
|
response = await self._api.post_async(
|
|
1394
1394
|
"annotations.info",
|
|
@@ -1501,7 +1501,7 @@ class AnnotationApi(ModuleApi):
|
|
|
1501
1501
|
context["project_meta"] = project_meta
|
|
1502
1502
|
|
|
1503
1503
|
if semaphore is None:
|
|
1504
|
-
semaphore = self._api.
|
|
1504
|
+
semaphore = self._api.get_default_semaphore()
|
|
1505
1505
|
tasks = []
|
|
1506
1506
|
for image in image_ids:
|
|
1507
1507
|
task = self.download_async(
|
supervisely/api/api.py
CHANGED
|
@@ -377,6 +377,7 @@ class Api:
|
|
|
377
377
|
|
|
378
378
|
self.async_httpx_client: httpx.AsyncClient = None
|
|
379
379
|
self.httpx_client: httpx.Client = None
|
|
380
|
+
self._semaphore = None
|
|
380
381
|
|
|
381
382
|
@classmethod
|
|
382
383
|
def normalize_server_address(cls, server_address: str) -> str:
|
|
@@ -999,7 +1000,10 @@ class Api:
|
|
|
999
1000
|
def post_httpx(
|
|
1000
1001
|
self,
|
|
1001
1002
|
method: str,
|
|
1002
|
-
|
|
1003
|
+
json: Dict = None,
|
|
1004
|
+
content: Union[str, bytes, Iterable[bytes], AsyncIterable[bytes]] = None,
|
|
1005
|
+
files: Union[Mapping] = None,
|
|
1006
|
+
params: Union[str, bytes] = None,
|
|
1003
1007
|
headers: Optional[Dict[str, str]] = None,
|
|
1004
1008
|
retries: Optional[int] = None,
|
|
1005
1009
|
raise_error: Optional[bool] = False,
|
|
@@ -1009,8 +1013,14 @@ class Api:
|
|
|
1009
1013
|
|
|
1010
1014
|
:param method: Method name.
|
|
1011
1015
|
:type method: str
|
|
1012
|
-
:param
|
|
1013
|
-
:type
|
|
1016
|
+
:param json: Dictionary to send in the body of request.
|
|
1017
|
+
:type json: dict, optional
|
|
1018
|
+
:param content: Bytes with data content or dictionary with params.
|
|
1019
|
+
:type content: bytes or dict, optional
|
|
1020
|
+
:param files: Files to send in the body of request.
|
|
1021
|
+
:type files: dict, optional
|
|
1022
|
+
:param params: URL query parameters.
|
|
1023
|
+
:type params: str, bytes, optional
|
|
1014
1024
|
:param headers: Custom headers to include in the request.
|
|
1015
1025
|
:type headers: dict, optional
|
|
1016
1026
|
:param retries: The number of attempts to connect to the server.
|
|
@@ -1021,6 +1031,7 @@ class Api:
|
|
|
1021
1031
|
:rtype: :class:`httpx.Response`
|
|
1022
1032
|
"""
|
|
1023
1033
|
self._set_client()
|
|
1034
|
+
|
|
1024
1035
|
if retries is None:
|
|
1025
1036
|
retries = self.retry_count
|
|
1026
1037
|
|
|
@@ -1032,18 +1043,17 @@ class Api:
|
|
|
1032
1043
|
else:
|
|
1033
1044
|
headers = {**self.headers, **headers}
|
|
1034
1045
|
|
|
1035
|
-
if isinstance(data, bytes):
|
|
1036
|
-
request_params = {"content": data}
|
|
1037
|
-
elif isinstance(data, Dict):
|
|
1038
|
-
json_body = {**data, **self.additional_fields}
|
|
1039
|
-
request_params = {"json": json_body}
|
|
1040
|
-
else:
|
|
1041
|
-
request_params = {"params": data}
|
|
1042
|
-
|
|
1043
1046
|
for retry_idx in range(retries):
|
|
1044
1047
|
response = None
|
|
1045
1048
|
try:
|
|
1046
|
-
response = self.httpx_client.post(
|
|
1049
|
+
response = self.httpx_client.post(
|
|
1050
|
+
url,
|
|
1051
|
+
content=content,
|
|
1052
|
+
files=files,
|
|
1053
|
+
json=json,
|
|
1054
|
+
params=params,
|
|
1055
|
+
headers=headers,
|
|
1056
|
+
)
|
|
1047
1057
|
if response.status_code != httpx.codes.OK:
|
|
1048
1058
|
self._check_version()
|
|
1049
1059
|
Api._raise_for_status_httpx(response)
|
|
@@ -1065,9 +1075,8 @@ class Api:
|
|
|
1065
1075
|
)
|
|
1066
1076
|
except Exception as exc:
|
|
1067
1077
|
process_unhandled_request(self.logger, exc)
|
|
1068
|
-
raise httpx.
|
|
1069
|
-
"Retry limit exceeded ({
|
|
1070
|
-
response=response,
|
|
1078
|
+
raise httpx.RequestError(
|
|
1079
|
+
f"Retry limit exceeded ({url})",
|
|
1071
1080
|
request=getattr(response, "request", None),
|
|
1072
1081
|
)
|
|
1073
1082
|
|
|
@@ -1093,6 +1102,7 @@ class Api:
|
|
|
1093
1102
|
:rtype: :class:`Response<Response>`
|
|
1094
1103
|
"""
|
|
1095
1104
|
self._set_client()
|
|
1105
|
+
|
|
1096
1106
|
if retries is None:
|
|
1097
1107
|
retries = self.retry_count
|
|
1098
1108
|
|
|
@@ -1171,8 +1181,8 @@ class Api:
|
|
|
1171
1181
|
:return: Generator object.
|
|
1172
1182
|
:rtype: :class:`Generator`
|
|
1173
1183
|
"""
|
|
1174
|
-
|
|
1175
1184
|
self._set_client()
|
|
1185
|
+
|
|
1176
1186
|
if retries is None:
|
|
1177
1187
|
retries = self.retry_count
|
|
1178
1188
|
|
|
@@ -1408,14 +1418,13 @@ class Api:
|
|
|
1408
1418
|
url = self.api_server_address + "/v3/" + method
|
|
1409
1419
|
if not use_public_api:
|
|
1410
1420
|
url = os.path.join(self.server_address, method)
|
|
1421
|
+
logger.trace(f"{method_type} {url}")
|
|
1411
1422
|
|
|
1412
1423
|
if headers is None:
|
|
1413
1424
|
headers = self.headers.copy()
|
|
1414
1425
|
else:
|
|
1415
1426
|
headers = {**self.headers, **headers}
|
|
1416
1427
|
|
|
1417
|
-
logger.trace(f"{method_type} {url}")
|
|
1418
|
-
|
|
1419
1428
|
if isinstance(data, (bytes, Generator)):
|
|
1420
1429
|
content = data
|
|
1421
1430
|
json_body = None
|
|
@@ -1506,42 +1515,80 @@ class Api:
|
|
|
1506
1515
|
|
|
1507
1516
|
def _set_async_client(self):
|
|
1508
1517
|
"""
|
|
1509
|
-
Set async httpx client if it is not set yet.
|
|
1510
|
-
Switch on HTTP/2 if the server address uses HTTPS.
|
|
1518
|
+
Set async httpx client with HTTP/2 if it is not set yet.
|
|
1511
1519
|
"""
|
|
1512
1520
|
if self.async_httpx_client is None:
|
|
1513
|
-
self.
|
|
1514
|
-
if self.server_address.startswith("https://"):
|
|
1515
|
-
self.async_httpx_client = httpx.AsyncClient(http2=True)
|
|
1516
|
-
else:
|
|
1517
|
-
self.async_httpx_client = httpx.AsyncClient()
|
|
1521
|
+
self.async_httpx_client = httpx.AsyncClient(http2=True)
|
|
1518
1522
|
|
|
1519
1523
|
def _set_client(self):
|
|
1520
1524
|
"""
|
|
1521
|
-
Set httpx client if it is not set yet.
|
|
1522
|
-
Switch on HTTP/2 if the server address uses HTTPS.
|
|
1525
|
+
Set sync httpx client with HTTP/2 if it is not set yet.
|
|
1523
1526
|
"""
|
|
1524
1527
|
if self.httpx_client is None:
|
|
1525
|
-
self.
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1528
|
+
self.httpx_client = httpx.Client(http2=True)
|
|
1529
|
+
|
|
1530
|
+
def get_default_semaphore(self) -> asyncio.Semaphore:
|
|
1531
|
+
"""
|
|
1532
|
+
Get default global API semaphore for async requests.
|
|
1533
|
+
If the semaphore is not set, it will be initialized.
|
|
1534
|
+
|
|
1535
|
+
During initialization, the semaphore size will be set from the environment variable SUPERVISELY_ASYNC_SEMAPHORE.
|
|
1536
|
+
If the environment variable is not set, the default value will be set based on the server address.
|
|
1537
|
+
Depending on the server address, the semaphore size will be set to 10 for HTTPS and 5 for HTTP.
|
|
1538
|
+
|
|
1539
|
+
:return: Semaphore object.
|
|
1540
|
+
:rtype: :class:`asyncio.Semaphore`
|
|
1541
|
+
"""
|
|
1542
|
+
if self._semaphore is None:
|
|
1543
|
+
self._initialize_semaphore()
|
|
1544
|
+
return self._semaphore
|
|
1530
1545
|
|
|
1531
|
-
def
|
|
1546
|
+
def _initialize_semaphore(self):
|
|
1532
1547
|
"""
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
If
|
|
1536
|
-
|
|
1537
|
-
|
|
1548
|
+
Initialize the semaphore for async requests.
|
|
1549
|
+
|
|
1550
|
+
If the environment variable SUPERVISELY_ASYNC_SEMAPHORE is set, create a semaphore with the given value.
|
|
1551
|
+
|
|
1552
|
+
Otherwise, create a semaphore with a default value:
|
|
1553
|
+
|
|
1554
|
+
- If server supports HTTPS, create a semaphore with value 10.
|
|
1555
|
+
- If server supports HTTP, create a semaphore with value 5.
|
|
1538
1556
|
"""
|
|
1539
|
-
|
|
1540
|
-
if
|
|
1541
|
-
|
|
1557
|
+
semaphore_size = sly_env.semaphore_size()
|
|
1558
|
+
if semaphore_size is not None:
|
|
1559
|
+
self._semaphore = asyncio.Semaphore(semaphore_size)
|
|
1560
|
+
logger.debug(
|
|
1561
|
+
f"Setting global API semaphore size to {semaphore_size} from environment variable"
|
|
1562
|
+
)
|
|
1542
1563
|
else:
|
|
1543
1564
|
self._check_https_redirect()
|
|
1544
1565
|
if self.server_address.startswith("https://"):
|
|
1545
|
-
|
|
1566
|
+
self._semaphore = asyncio.Semaphore(10)
|
|
1567
|
+
logger.debug("Setting global API semaphore size to 10 for HTTPS")
|
|
1546
1568
|
else:
|
|
1547
|
-
|
|
1569
|
+
self._semaphore = asyncio.Semaphore(5)
|
|
1570
|
+
logger.debug("Setting global API semaphore size to 5 for HTTP")
|
|
1571
|
+
|
|
1572
|
+
def set_semaphore_size(self, size: int = None):
|
|
1573
|
+
"""
|
|
1574
|
+
Set the global API semaphore with the given size. Will replace the existing semaphore.
|
|
1575
|
+
If the size is not set, will set from the environment variable SUPERVISELY_ASYNC_SEMAPHORE.
|
|
1576
|
+
If the environment variable is not set, will set the default value.
|
|
1577
|
+
|
|
1578
|
+
:param size: Size of the semaphore.
|
|
1579
|
+
:type size: int, optional
|
|
1580
|
+
"""
|
|
1581
|
+
if size is not None:
|
|
1582
|
+
self._semaphore = asyncio.Semaphore(size)
|
|
1583
|
+
else:
|
|
1584
|
+
self._initialize_semaphore()
|
|
1585
|
+
|
|
1586
|
+
@property
|
|
1587
|
+
def semaphore(self) -> asyncio.Semaphore:
|
|
1588
|
+
"""
|
|
1589
|
+
Get the global API semaphore for async requests.
|
|
1590
|
+
|
|
1591
|
+
:return: Semaphore object.
|
|
1592
|
+
:rtype: :class:`asyncio.Semaphore`
|
|
1593
|
+
"""
|
|
1594
|
+
return self._semaphore
|
supervisely/api/app_api.py
CHANGED
|
@@ -551,7 +551,7 @@ class AppApi(TaskApi):
|
|
|
551
551
|
|
|
552
552
|
def is_enabled(self) -> bool:
|
|
553
553
|
"""Check if the workflow functionality is enabled."""
|
|
554
|
-
logger.
|
|
554
|
+
logger.debug(f"Workflow check: is {'enabled' if self._enabled else 'disabled'}.")
|
|
555
555
|
return self._enabled
|
|
556
556
|
|
|
557
557
|
# pylint: disable=no-self-argument
|
|
@@ -579,8 +579,8 @@ class AppApi(TaskApi):
|
|
|
579
579
|
):
|
|
580
580
|
self.__last_warning_time = time.monotonic()
|
|
581
581
|
logger.warning(
|
|
582
|
-
"Workflow is disabled
|
|
583
|
-
"To enable it, use `api.app.workflow.enable()
|
|
582
|
+
"Workflow is disabled. "
|
|
583
|
+
"To enable it, use `api.app.workflow.enable()`."
|
|
584
584
|
)
|
|
585
585
|
return
|
|
586
586
|
if not check_workflow_compatibility(self._api, version_to_check):
|
supervisely/api/dataset_api.py
CHANGED
|
@@ -4,7 +4,17 @@
|
|
|
4
4
|
# docs
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from typing import
|
|
7
|
+
from typing import (
|
|
8
|
+
Any,
|
|
9
|
+
Dict,
|
|
10
|
+
Generator,
|
|
11
|
+
List,
|
|
12
|
+
Literal,
|
|
13
|
+
NamedTuple,
|
|
14
|
+
Optional,
|
|
15
|
+
Tuple,
|
|
16
|
+
Union,
|
|
17
|
+
)
|
|
8
18
|
|
|
9
19
|
from supervisely._utils import abs_url, compress_image_url, is_development
|
|
10
20
|
from supervisely.api.module_api import (
|
|
@@ -61,6 +71,7 @@ class DatasetInfo(NamedTuple):
|
|
|
61
71
|
team_id: int
|
|
62
72
|
workspace_id: int
|
|
63
73
|
parent_id: Union[int, None]
|
|
74
|
+
custom_data: dict
|
|
64
75
|
|
|
65
76
|
@property
|
|
66
77
|
def image_preview_url(self):
|
|
@@ -135,6 +146,7 @@ class DatasetApi(UpdateableModule, RemoveableModuleApi):
|
|
|
135
146
|
ApiField.TEAM_ID,
|
|
136
147
|
ApiField.WORKSPACE_ID,
|
|
137
148
|
ApiField.PARENT_ID,
|
|
149
|
+
ApiField.CUSTOM_DATA,
|
|
138
150
|
]
|
|
139
151
|
|
|
140
152
|
@staticmethod
|
|
@@ -362,6 +374,83 @@ class DatasetApi(UpdateableModule, RemoveableModuleApi):
|
|
|
362
374
|
)
|
|
363
375
|
return dataset_info
|
|
364
376
|
|
|
377
|
+
def update(
|
|
378
|
+
self,
|
|
379
|
+
id: int,
|
|
380
|
+
name: Optional[str] = None,
|
|
381
|
+
description: Optional[str] = None,
|
|
382
|
+
custom_data: Optional[Dict[Any, Any]] = None,
|
|
383
|
+
) -> DatasetInfo:
|
|
384
|
+
"""Update Dataset information by given ID.
|
|
385
|
+
|
|
386
|
+
:param id: Dataset ID in Supervisely.
|
|
387
|
+
:type id: int
|
|
388
|
+
:param name: New Dataset name.
|
|
389
|
+
:type name: str, optional
|
|
390
|
+
:param description: New Dataset description.
|
|
391
|
+
:type description: str, optional
|
|
392
|
+
:param custom_data: New custom data.
|
|
393
|
+
:type custom_data: Dict[Any, Any], optional
|
|
394
|
+
:return: Information about Dataset. See :class:`info_sequence<info_sequence>`
|
|
395
|
+
:rtype: :class:`DatasetInfo`
|
|
396
|
+
|
|
397
|
+
:Usage example:
|
|
398
|
+
|
|
399
|
+
.. code-block:: python
|
|
400
|
+
|
|
401
|
+
import supervisely as sly
|
|
402
|
+
|
|
403
|
+
dataset_id = 384126
|
|
404
|
+
|
|
405
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
406
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
407
|
+
api = sly.Api.from_env()
|
|
408
|
+
|
|
409
|
+
new_ds = api.dataset.update(dataset_id, name='new_ds', description='new description')
|
|
410
|
+
"""
|
|
411
|
+
fields = [name, description, custom_data] # Extend later if needed.
|
|
412
|
+
if all(f is None for f in fields):
|
|
413
|
+
raise ValueError(f"At least one of the fields must be specified: {fields}")
|
|
414
|
+
|
|
415
|
+
payload = {
|
|
416
|
+
ApiField.ID: id,
|
|
417
|
+
ApiField.NAME: name,
|
|
418
|
+
ApiField.DESCRIPTION: description,
|
|
419
|
+
ApiField.CUSTOM_DATA: custom_data,
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
payload = {k: v for k, v in payload.items() if v is not None}
|
|
423
|
+
|
|
424
|
+
response = self._api.post(self._get_update_method(), payload)
|
|
425
|
+
return self._convert_json_info(response.json())
|
|
426
|
+
|
|
427
|
+
def update_custom_data(self, id: int, custom_data: Dict[Any, Any]) -> DatasetInfo:
|
|
428
|
+
"""Update custom data for Dataset by given ID.
|
|
429
|
+
Custom data is a dictionary that can store any additional information about the Dataset.
|
|
430
|
+
|
|
431
|
+
:param id: Dataset ID in Supervisely.
|
|
432
|
+
:type id: int
|
|
433
|
+
:param custom_data: New custom data.
|
|
434
|
+
:type custom_data: Dict[Any, Any]
|
|
435
|
+
:return: Information about Dataset. See :class:`info_sequence<info_sequence>`
|
|
436
|
+
:rtype: :class:`DatasetInfo`
|
|
437
|
+
|
|
438
|
+
:Usage example:
|
|
439
|
+
|
|
440
|
+
.. code-block:: python
|
|
441
|
+
|
|
442
|
+
import supervisely as sly
|
|
443
|
+
|
|
444
|
+
dataset_id = 384126
|
|
445
|
+
|
|
446
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
447
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
448
|
+
api = sly.Api.from_env()
|
|
449
|
+
|
|
450
|
+
new_ds = api.dataset.update_custom_data(dataset_id, custom_data={'key': 'value'})
|
|
451
|
+
"""
|
|
452
|
+
return self.update(id, custom_data=custom_data)
|
|
453
|
+
|
|
365
454
|
def _get_update_method(self):
|
|
366
455
|
""" """
|
|
367
456
|
return "datasets.editInfo"
|
supervisely/api/file_api.py
CHANGED
|
@@ -1646,7 +1646,7 @@ class FileApi(ModuleApiBase):
|
|
|
1646
1646
|
loop.run_until_complete(api.file.download_async(8, path_to_file, local_save_path))
|
|
1647
1647
|
"""
|
|
1648
1648
|
if semaphore is None:
|
|
1649
|
-
semaphore = self._api.
|
|
1649
|
+
semaphore = self._api.get_default_semaphore()
|
|
1650
1650
|
async with semaphore:
|
|
1651
1651
|
if self.is_on_agent(remote_path):
|
|
1652
1652
|
# for optimized download from agent
|
|
@@ -1774,7 +1774,7 @@ class FileApi(ModuleApiBase):
|
|
|
1774
1774
|
)
|
|
1775
1775
|
|
|
1776
1776
|
if semaphore is None:
|
|
1777
|
-
semaphore = self._api.
|
|
1777
|
+
semaphore = self._api.get_default_semaphore()
|
|
1778
1778
|
|
|
1779
1779
|
tasks = []
|
|
1780
1780
|
for remote_path, local_path, cache in zip(
|
|
@@ -1836,7 +1836,7 @@ class FileApi(ModuleApiBase):
|
|
|
1836
1836
|
"""
|
|
1837
1837
|
|
|
1838
1838
|
if semaphore is None:
|
|
1839
|
-
semaphore = self._api.
|
|
1839
|
+
semaphore = self._api.get_default_semaphore()
|
|
1840
1840
|
|
|
1841
1841
|
if not remote_path.endswith("/"):
|
|
1842
1842
|
remote_path += "/"
|
|
@@ -1925,7 +1925,7 @@ class FileApi(ModuleApiBase):
|
|
|
1925
1925
|
"""
|
|
1926
1926
|
|
|
1927
1927
|
if semaphore is None:
|
|
1928
|
-
semaphore = self._api.
|
|
1928
|
+
semaphore = self._api.get_default_semaphore()
|
|
1929
1929
|
|
|
1930
1930
|
remote_file_path = env.file(raise_not_found=False)
|
|
1931
1931
|
remote_folder_path = env.folder(raise_not_found=False)
|
|
@@ -2061,7 +2061,7 @@ class FileApi(ModuleApiBase):
|
|
|
2061
2061
|
# "sha256": sha256, #TODO add with resumaple api
|
|
2062
2062
|
}
|
|
2063
2063
|
if semaphore is None:
|
|
2064
|
-
semaphore = self._api.
|
|
2064
|
+
semaphore = self._api.get_default_semaphore()
|
|
2065
2065
|
async with semaphore:
|
|
2066
2066
|
async with aiofiles.open(src, "rb") as fd:
|
|
2067
2067
|
item = await fd.read()
|
|
@@ -2129,7 +2129,7 @@ class FileApi(ModuleApiBase):
|
|
|
2129
2129
|
)
|
|
2130
2130
|
"""
|
|
2131
2131
|
if semaphore is None:
|
|
2132
|
-
semaphore = self._api.
|
|
2132
|
+
semaphore = self._api.get_default_semaphore()
|
|
2133
2133
|
tasks = []
|
|
2134
2134
|
for s, d in zip(src_paths, dst_paths):
|
|
2135
2135
|
task = self.upload_async(
|