supervisely 6.73.220__py3-none-any.whl → 6.73.222__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.

Files changed (27) hide show
  1. supervisely/api/api.py +609 -3
  2. supervisely/api/file_api.py +574 -14
  3. supervisely/api/image_api.py +469 -0
  4. supervisely/api/pointcloud/pointcloud_api.py +390 -1
  5. supervisely/api/video/video_api.py +231 -1
  6. supervisely/api/volume/volume_api.py +223 -2
  7. supervisely/app/development/__init__.py +1 -0
  8. supervisely/app/development/development.py +96 -2
  9. supervisely/app/fastapi/subapp.py +19 -4
  10. supervisely/convert/base_converter.py +53 -4
  11. supervisely/convert/converter.py +6 -5
  12. supervisely/convert/image/image_converter.py +26 -13
  13. supervisely/convert/image/sly/fast_sly_image_converter.py +4 -0
  14. supervisely/convert/image/sly/sly_image_converter.py +9 -4
  15. supervisely/convert/pointcloud_episodes/sly/sly_pointcloud_episodes_converter.py +7 -1
  16. supervisely/convert/video/sly/sly_video_converter.py +9 -1
  17. supervisely/convert/video/video_converter.py +44 -23
  18. supervisely/io/fs.py +125 -0
  19. supervisely/io/fs_cache.py +19 -1
  20. supervisely/io/network_exceptions.py +20 -3
  21. supervisely/task/progress.py +1 -1
  22. {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/METADATA +3 -1
  23. {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/RECORD +27 -27
  24. {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/LICENSE +0 -0
  25. {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/WHEEL +0 -0
  26. {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/entry_points.txt +0 -0
  27. {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,21 @@
1
1
  # coding: utf-8
2
2
 
3
3
  # docs
4
+ import asyncio
4
5
  import os
5
6
  from collections import defaultdict
6
- from typing import Callable, Dict, List, NamedTuple, Optional, Union
7
+ from typing import (
8
+ AsyncGenerator,
9
+ Callable,
10
+ Dict,
11
+ List,
12
+ Literal,
13
+ NamedTuple,
14
+ Optional,
15
+ Union,
16
+ )
7
17
 
18
+ import aiofiles
8
19
  from requests import Response
9
20
  from requests_toolbelt import MultipartEncoder
10
21
  from tqdm import tqdm
@@ -18,11 +29,13 @@ from supervisely.api.pointcloud.pointcloud_tag_api import PointcloudTagApi
18
29
  from supervisely.io.fs import (
19
30
  ensure_base_path,
20
31
  get_file_hash,
32
+ get_file_hash_async,
21
33
  get_file_name_with_ext,
22
34
  list_files,
23
35
  list_files_recursively,
24
36
  )
25
37
  from supervisely.pointcloud.pointcloud import is_valid_format
38
+ from supervisely.sly_logger import logger
26
39
 
27
40
 
28
41
  class PointcloudInfo(NamedTuple):
@@ -1032,3 +1045,379 @@ class PointcloudApi(RemoveableBulkModuleApi):
1032
1045
  )
1033
1046
  )
1034
1047
  return pcds_infos
1048
+
1049
+ async def _download_async(
1050
+ self,
1051
+ id: int,
1052
+ is_stream: bool = False,
1053
+ range_start: Optional[int] = None,
1054
+ range_end: Optional[int] = None,
1055
+ headers: dict = None,
1056
+ chunk_size: int = 1024 * 1024,
1057
+ ) -> AsyncGenerator:
1058
+ """
1059
+ Download Point cloud with given ID asynchronously.
1060
+ If is_stream is True, returns stream of bytes, otherwise returns response object.
1061
+ For streaming, returns tuple of chunk and hash.
1062
+
1063
+ :param id: Point cloud ID in Supervisely.
1064
+ :type id: int
1065
+ :param is_stream: If True, returns stream of bytes, otherwise returns response object.
1066
+ :type is_stream: bool, optional
1067
+ :param range_start: Start byte of range for partial download.
1068
+ :type range_start: int, optional
1069
+ :param range_end: End byte of range for partial download.
1070
+ :type range_end: int, optional
1071
+ :param headers: Headers for request.
1072
+ :type headers: dict, optional
1073
+ :param chunk_size: Size of chunk for partial download. Default is 1MB.
1074
+ :type chunk_size: int, optional
1075
+ :return: Stream of bytes or response object.
1076
+ :rtype: AsyncGenerator
1077
+ """
1078
+ api_method_name = "point-clouds.download"
1079
+
1080
+ json_body = {ApiField.ID: id}
1081
+
1082
+ if is_stream:
1083
+ async for chunk, hhash in self._api.stream_async(
1084
+ api_method_name,
1085
+ "POST",
1086
+ json_body,
1087
+ headers=headers,
1088
+ range_start=range_start,
1089
+ range_end=range_end,
1090
+ chunk_size=chunk_size,
1091
+ ):
1092
+ yield chunk, hhash
1093
+ else:
1094
+ response = await self._api.post_async(api_method_name, json_body, headers=headers)
1095
+ yield response
1096
+
1097
+ async def download_path_async(
1098
+ self,
1099
+ id: int,
1100
+ path: str,
1101
+ semaphore: Optional[asyncio.Semaphore] = None,
1102
+ range_start: Optional[int] = None,
1103
+ range_end: Optional[int] = None,
1104
+ headers: Optional[dict] = None,
1105
+ chunk_size: int = 1024 * 1024,
1106
+ check_hash: bool = True,
1107
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
1108
+ progress_cb_type: Literal["number", "size"] = "number",
1109
+ ) -> None:
1110
+ """
1111
+ Downloads Point cloud with given ID to local path.
1112
+
1113
+ :param id: Point cloud ID in Supervisely.
1114
+ :type id: int
1115
+ :param path: Local save path for Point cloud.
1116
+ :type path: str
1117
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
1118
+ :type semaphore: :class:`asyncio.Semaphore`, optional
1119
+ :param range_start: Start byte of range for partial download.
1120
+ :type range_start: int, optional
1121
+ :param range_end: End byte of range for partial download.
1122
+ :type range_end: int, optional
1123
+ :param headers: Headers for request.
1124
+ :type headers: dict, optional
1125
+ :param chunk_size: Size of chunk for partial download. Default is 1MB.
1126
+ :type chunk_size: int, optional
1127
+ :param check_hash: If True, checks hash of downloaded file.
1128
+ Check is not supported for partial downloads.
1129
+ When range is set, hash check is disabled.
1130
+ :type check_hash: bool, optional
1131
+ :param progress_cb: Function for tracking download progress.
1132
+ :type progress_cb: Optional[Union[tqdm, Callable]]
1133
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
1134
+ :type progress_cb_type: Literal["number", "size"], optional
1135
+ :return: None
1136
+ :rtype: :class:`NoneType`
1137
+ :Usage example:
1138
+
1139
+ .. code-block:: python
1140
+
1141
+ import supervisely as sly
1142
+ import asyncio
1143
+
1144
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
1145
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
1146
+ api = sly.Api.from_env()
1147
+
1148
+ pcd_info = api.pointcloud.get_info_by_id(19373403)
1149
+ save_path = os.path.join("/path/to/save/", pcd_info.name)
1150
+
1151
+ semaphore = asyncio.Semaphore(100)
1152
+ loop = asyncio.new_event_loop()
1153
+ asyncio.set_event_loop(loop)
1154
+ loop.run_until_complete(
1155
+ api.pointcloud.download_path_async(pcd_info.id, save_path, semaphore)
1156
+ )
1157
+ """
1158
+
1159
+ if range_start is not None or range_end is not None:
1160
+ check_hash = False # Hash check is not supported for partial downloads
1161
+ headers = headers or {}
1162
+ headers["Range"] = f"bytes={range_start or ''}-{range_end or ''}"
1163
+ logger.debug(f"Image ID: {id}. Setting Range header: {headers['Range']}")
1164
+
1165
+ writing_method = "ab" if range_start not in [0, None] else "wb"
1166
+
1167
+ ensure_base_path(path)
1168
+ hash_to_check = None
1169
+ if semaphore is None:
1170
+ semaphore = self._api._get_default_semaphore()
1171
+ async with semaphore:
1172
+ async with aiofiles.open(path, writing_method) as fd:
1173
+ async for chunk, hhash in self._download_async(
1174
+ id,
1175
+ is_stream=True,
1176
+ headers=headers,
1177
+ range_start=range_start,
1178
+ range_end=range_end,
1179
+ chunk_size=chunk_size,
1180
+ ):
1181
+ await fd.write(chunk)
1182
+ hash_to_check = hhash
1183
+ if progress_cb is not None and progress_cb_type == "size":
1184
+ progress_cb(len(chunk))
1185
+ if check_hash:
1186
+ if hash_to_check is not None:
1187
+ downloaded_bytes_hash = await get_file_hash_async(path)
1188
+ if hash_to_check != downloaded_bytes_hash:
1189
+ raise RuntimeError(
1190
+ f"Downloaded hash of point cloud with ID:{id} does not match the expected hash: {downloaded_bytes_hash} != {hash_to_check}"
1191
+ )
1192
+ if progress_cb is not None and progress_cb_type == "number":
1193
+ progress_cb(1)
1194
+
1195
+ async def download_paths_async(
1196
+ self,
1197
+ ids: List[int],
1198
+ paths: List[str],
1199
+ semaphore: Optional[asyncio.Semaphore] = None,
1200
+ headers: dict = None,
1201
+ chunk_size: int = 1024 * 1024,
1202
+ check_hash: bool = True,
1203
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
1204
+ progress_cb_type: Literal["number", "size"] = "number",
1205
+ ) -> None:
1206
+ """
1207
+ Download Point clouds with given IDs and saves them to given local paths asynchronously.
1208
+
1209
+ :param ids: List of Point cloud IDs in Supervisely.
1210
+ :type ids: :class:`List[int]`
1211
+ :param paths: Local save paths for Point clouds.
1212
+ :type paths: :class:`List[str]`
1213
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
1214
+ :type semaphore: :class:`asyncio.Semaphore`, optional
1215
+ :param headers: Headers for request.
1216
+ :type headers: dict, optional
1217
+ :param show_progress: If True, shows progress bar.
1218
+ :type show_progress: bool, optional
1219
+ :param chunk_size: Size of chunk for partial download. Default is 1MB.
1220
+ :type chunk_size: int, optional
1221
+ :param check_hash: If True, checks hash of downloaded file.
1222
+ :type check_hash: bool, optional
1223
+ :param progress_cb: Function for tracking download progress.
1224
+ :type progress_cb: Optional[Union[tqdm, Callable]]
1225
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
1226
+ :type progress_cb_type: Literal["number", "size"], optional
1227
+ :raises: :class:`ValueError` if len(ids) != len(paths)
1228
+ :return: None
1229
+ :rtype: :class:`NoneType`
1230
+
1231
+ :Usage example:
1232
+
1233
+ .. code-block:: python
1234
+
1235
+ import supervisely as sly
1236
+ import asyncio
1237
+
1238
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
1239
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
1240
+ api = sly.Api.from_env()
1241
+
1242
+ ids = [19373403, 19373404]
1243
+ paths = ["/path/to/save/000063.pcd", "/path/to/save/000064.pcd"]
1244
+ loop = asyncio.new_event_loop()
1245
+ asyncio.set_event_loop(loop)
1246
+ loop.run_until_complete(api.pointcloud.download_paths_async(ids, paths))
1247
+ """
1248
+ if len(ids) == 0:
1249
+ return
1250
+ if len(ids) != len(paths):
1251
+ raise ValueError('Can not match "ids" and "paths" lists, len(ids) != len(paths)')
1252
+ if semaphore is None:
1253
+ semaphore = self._api._get_default_semaphore()
1254
+ tasks = []
1255
+ for img_id, img_path in zip(ids, paths):
1256
+ task = self.download_path_async(
1257
+ img_id,
1258
+ img_path,
1259
+ semaphore,
1260
+ headers=headers,
1261
+ chunk_size=chunk_size,
1262
+ check_hash=check_hash,
1263
+ progress_cb=progress_cb,
1264
+ progress_cb_type=progress_cb_type,
1265
+ )
1266
+ tasks.append(task)
1267
+ await asyncio.gather(*tasks)
1268
+
1269
+ async def download_related_image_async(
1270
+ self,
1271
+ id: int,
1272
+ path: str,
1273
+ semaphore: Optional[asyncio.Semaphore] = None,
1274
+ headers: dict = None,
1275
+ chunk_size: int = 1024 * 1024,
1276
+ check_hash: bool = True,
1277
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
1278
+ progress_cb_type: Literal["number", "size"] = "number",
1279
+ ) -> None:
1280
+ """
1281
+ Downloads a related context image from Supervisely to local directory by image id.
1282
+
1283
+ :param id: Point cloud ID in Supervisely.
1284
+ :type id: int
1285
+ :param path: Local save path for Point cloud.
1286
+ :type path: str
1287
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
1288
+ :type semaphore: :class:`asyncio.Semaphore`, optional
1289
+ :param headers: Headers for request.
1290
+ :type headers: dict, optional
1291
+ :param chunk_size: Size of chunk for partial download. Default is 1MB.
1292
+ :type chunk_size: int, optional
1293
+ :param check_hash: If True, checks hash of downloaded file.
1294
+ :type check_hash: bool, optional
1295
+ :param progress_cb: Function for tracking download progress.
1296
+ :type progress_cb: Optional[Union[tqdm, Callable]]
1297
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
1298
+ :type progress_cb_type: Literal["number", "size"], optional
1299
+ :return: None
1300
+ :rtype: :class:`NoneType`
1301
+ :Usage example:
1302
+
1303
+ .. code-block:: python
1304
+
1305
+ import supervisely as sly
1306
+ import asyncio
1307
+
1308
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
1309
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
1310
+ api = sly.Api.from_env()
1311
+
1312
+ img_info = api.pointcloud.get_list_related_images(19373403)[0]
1313
+ save_path = os.path.join("/path/to/save/", img_info.name)
1314
+
1315
+ semaphore = asyncio.Semaphore(100)
1316
+ loop = asyncio.new_event_loop()
1317
+ asyncio.set_event_loop(loop)
1318
+ loop.run_until_complete(
1319
+ api.pointcloud.download_related_image_async(19373403, save_path, semaphore)
1320
+ )
1321
+ """
1322
+
1323
+ api_method_name = "point-clouds.images.download"
1324
+
1325
+ ensure_base_path(path)
1326
+ hash_to_check = None
1327
+ if semaphore is None:
1328
+ semaphore = self._api._get_default_semaphore()
1329
+ async with semaphore:
1330
+ async with aiofiles.open(path, "wb") as fd:
1331
+ async for chunk, hhash in self._api.stream_async(
1332
+ api_method_name,
1333
+ "POST",
1334
+ {ApiField.ID: id},
1335
+ headers=headers,
1336
+ chunk_size=chunk_size,
1337
+ ):
1338
+ await fd.write(chunk)
1339
+ hash_to_check = hhash
1340
+ if progress_cb is not None and progress_cb_type == "size":
1341
+ progress_cb(len(chunk))
1342
+ if check_hash:
1343
+ if hash_to_check is not None:
1344
+ downloaded_bytes_hash = await get_file_hash_async(path)
1345
+ if hash_to_check != downloaded_bytes_hash:
1346
+ raise RuntimeError(
1347
+ f"Downloaded hash of point cloud related image with ID:{id} does not match the expected hash: {downloaded_bytes_hash} != {hash_to_check}"
1348
+ )
1349
+ if progress_cb is not None and progress_cb_type == "number":
1350
+ progress_cb(1)
1351
+
1352
+ async def download_related_images_async(
1353
+ self,
1354
+ ids: List[int],
1355
+ paths: List[str],
1356
+ semaphore: Optional[asyncio.Semaphore] = None,
1357
+ headers: dict = None,
1358
+ check_hash: bool = True,
1359
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
1360
+ progress_cb_type: Literal["number", "size"] = "number",
1361
+ ) -> None:
1362
+ """
1363
+ Downloads a related context image from Supervisely to local directory by image id.
1364
+
1365
+ :param ids: Related context imgage IDs in Supervisely.
1366
+ :type ids: int
1367
+ :param paths: Local save paths for Point clouds.
1368
+ :type paths: str
1369
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
1370
+ :type semaphore: :class:`asyncio.Semaphore`, optional
1371
+ :param headers: Headers for request.
1372
+ :type headers: dict, optional
1373
+ :param check_hash: If True, checks hash of downloaded file.
1374
+ :type check_hash: bool, optional
1375
+ :param progress_cb: Function for tracking download progress.
1376
+ :type progress_cb: Optional[Union[tqdm, Callable]]
1377
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
1378
+ :type progress_cb_type: Literal["number", "size"], optional
1379
+ :return: None
1380
+ :rtype: :class:`NoneType`
1381
+
1382
+ :Usage example:
1383
+
1384
+ .. code-block:: python
1385
+
1386
+ import supervisely as sly
1387
+ import asyncio
1388
+
1389
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
1390
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
1391
+ api = sly.Api.from_env()
1392
+
1393
+ img_infos = api.pointcloud.get_list_related_images(19373403)
1394
+ ids = [img_info.id for img_info in img_infos]
1395
+ save_paths = [os.path.join("/path/to/save/", img_info.name) for img_info in img_infos]
1396
+
1397
+ semaphore = asyncio.Semaphore(100)
1398
+ loop = asyncio.new_event_loop()
1399
+ asyncio.set_event_loop(loop)
1400
+ loop.run_until_complete(
1401
+ api.pointcloud.download_related_images_async(ids, save_paths, semaphore)
1402
+ )
1403
+ """
1404
+
1405
+ if len(ids) == 0:
1406
+ return
1407
+ if len(ids) != len(paths):
1408
+ raise ValueError('Can not match "ids" and "paths" lists, len(ids) != len(paths)')
1409
+ if semaphore is None:
1410
+ semaphore = self._api._get_default_semaphore()
1411
+ tasks = []
1412
+ for img_id, img_path in zip(ids, paths):
1413
+ task = self.download_related_image_async(
1414
+ img_id,
1415
+ img_path,
1416
+ semaphore,
1417
+ headers=headers,
1418
+ check_hash=check_hash,
1419
+ progress_cb=progress_cb,
1420
+ progress_cb_type=progress_cb_type,
1421
+ )
1422
+ tasks.append(task)
1423
+ await asyncio.gather(*tasks)
@@ -1,13 +1,26 @@
1
1
  # coding: utf-8
2
2
  from __future__ import annotations
3
3
 
4
+ import asyncio
4
5
  import datetime
5
6
  import json
6
7
  import os
7
8
  import urllib.parse
8
9
  from functools import partial
9
- from typing import Callable, Dict, Iterator, List, NamedTuple, Optional, Tuple, Union
10
+ from typing import (
11
+ AsyncGenerator,
12
+ Callable,
13
+ Dict,
14
+ Iterator,
15
+ List,
16
+ Literal,
17
+ NamedTuple,
18
+ Optional,
19
+ Tuple,
20
+ Union,
21
+ )
10
22
 
23
+ import aiofiles
11
24
  from numerize.numerize import numerize
12
25
  from requests import Response
13
26
  from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
@@ -31,6 +44,7 @@ from supervisely.io.fs import (
31
44
  ensure_base_path,
32
45
  get_file_ext,
33
46
  get_file_hash,
47
+ get_file_hash_async,
34
48
  get_file_name_with_ext,
35
49
  get_file_size,
36
50
  list_files,
@@ -2305,3 +2319,219 @@ class VideoApi(RemoveableBulkModuleApi):
2305
2319
  data = {ApiField.VIDEOS: videos_list, ApiField.CLEAR_LOCAL_DATA_SOURCE: True}
2306
2320
  r = self._api.post("videos.update.links", data)
2307
2321
  return r.json()
2322
+
2323
+ async def _download_async(
2324
+ self,
2325
+ id: int,
2326
+ is_stream: bool = False,
2327
+ range_start: Optional[int] = None,
2328
+ range_end: Optional[int] = None,
2329
+ headers: Optional[dict] = None,
2330
+ chunk_size: int = 1024 * 1024,
2331
+ ) -> AsyncGenerator:
2332
+ """
2333
+ Download Video with given ID asynchronously.
2334
+
2335
+ :param id: Video ID in Supervisely.
2336
+ :type id: int
2337
+ :param is_stream: If True, returns stream of bytes, otherwise returns response object.
2338
+ :type is_stream: bool, optional
2339
+ :param range_start: Start byte of range for partial download.
2340
+ :type range_start: int, optional
2341
+ :param range_end: End byte of range for partial download.
2342
+ :type range_end: int, optional
2343
+ :param headers: Headers for request.
2344
+ :type headers: dict, optional
2345
+ :param chunk_size: Size of chunk for partial download. Default is 1MB.
2346
+ :type chunk_size: int, optional
2347
+ :return: Stream of bytes or response object.
2348
+ :rtype: AsyncGenerator
2349
+ """
2350
+ api_method_name = "videos.download"
2351
+
2352
+ json_body = {ApiField.ID: id}
2353
+
2354
+ if is_stream:
2355
+ async for chunk, hhash in self._api.stream_async(
2356
+ api_method_name,
2357
+ "POST",
2358
+ json_body,
2359
+ headers=headers,
2360
+ range_start=range_start,
2361
+ range_end=range_end,
2362
+ chunk_size=chunk_size,
2363
+ ):
2364
+ yield chunk, hhash
2365
+ else:
2366
+ response = await self._api.post_async(api_method_name, json_body, headers=headers)
2367
+ yield response
2368
+
2369
+ async def download_path_async(
2370
+ self,
2371
+ id: int,
2372
+ path: str,
2373
+ semaphore: Optional[asyncio.Semaphore] = None,
2374
+ range_start: Optional[int] = None,
2375
+ range_end: Optional[int] = None,
2376
+ headers: Optional[dict] = None,
2377
+ chunk_size: int = 1024 * 1024,
2378
+ check_hash: bool = True,
2379
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
2380
+ progress_cb_type: Literal["number", "size"] = "number",
2381
+ ) -> None:
2382
+ """
2383
+ Downloads Video with given ID to local path.
2384
+
2385
+ :param id: Video ID in Supervisely.
2386
+ :type id: int
2387
+ :param path: Local save path for Video.
2388
+ :type path: str
2389
+ :param semaphore: Semaphore for limiting the number of simultaneous downloads.
2390
+ :type semaphore: :class:`asyncio.Semaphore`, optional
2391
+ :param range_start: Start byte of range for partial download.
2392
+ :type range_start: int, optional
2393
+ :param range_end: End byte of range for partial download.
2394
+ :type range_end: int, optional
2395
+ :param headers: Headers for request.
2396
+ :type headers: dict, optional
2397
+ :param chunk_size: Size of chunk for partial download. Default is 1MB.
2398
+ :type chunk_size: int, optional
2399
+ :param check_hash: If True, checks hash of downloaded file.
2400
+ Check is not supported for partial downloads.
2401
+ When range is set, hash check is disabled.
2402
+ :type check_hash: bool, optional
2403
+ :param progress_cb: Function for tracking download progress.
2404
+ :type progress_cb: Optional[Union[tqdm, Callable]]
2405
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
2406
+ :type progress_cb_type: Literal["number", "size"], optional
2407
+ :return: None
2408
+ :rtype: :class:`NoneType`
2409
+ :Usage example:
2410
+
2411
+ .. code-block:: python
2412
+
2413
+ import supervisely as sly
2414
+ import asyncio
2415
+
2416
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
2417
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
2418
+ api = sly.Api.from_env()
2419
+
2420
+ video_info = api.video.get_info_by_id(770918)
2421
+ save_path = os.path.join("/path/to/save/", video_info.name)
2422
+
2423
+ semaphore = asyncio.Semaphore(100)
2424
+ loop = asyncio.new_event_loop()
2425
+ asyncio.set_event_loop(loop)
2426
+ loop.run_until_complete(
2427
+ api.video.download_path_async(video_info.id, save_path, semaphore)
2428
+ )
2429
+ """
2430
+ if range_start is not None or range_end is not None:
2431
+ check_hash = False # hash check is not supported for partial downloads
2432
+ headers = headers or {}
2433
+ headers["Range"] = f"bytes={range_start or ''}-{range_end or ''}"
2434
+ logger.debug(f"Image ID: {id}. Setting Range header: {headers['Range']}")
2435
+
2436
+ writing_method = "ab" if range_start not in [0, None] else "wb"
2437
+
2438
+ ensure_base_path(path)
2439
+ hash_to_check = None
2440
+ if semaphore is None:
2441
+ semaphore = self._api._get_default_semaphore()
2442
+ async with semaphore:
2443
+ async with aiofiles.open(path, writing_method) as fd:
2444
+ async for chunk, hhash in self._download_async(
2445
+ id,
2446
+ is_stream=True,
2447
+ range_start=range_start,
2448
+ range_end=range_end,
2449
+ headers=headers,
2450
+ chunk_size=chunk_size,
2451
+ ):
2452
+ await fd.write(chunk)
2453
+ hash_to_check = hhash
2454
+ if progress_cb is not None and progress_cb_type == "size":
2455
+ progress_cb(len(chunk))
2456
+ if check_hash:
2457
+ if hash_to_check is not None:
2458
+ downloaded_file_hash = await get_file_hash_async(path)
2459
+ if hash_to_check != downloaded_file_hash:
2460
+ raise RuntimeError(
2461
+ f"Downloaded hash of video with ID:{id} does not match the expected hash: {downloaded_file_hash} != {hash_to_check}"
2462
+ )
2463
+ if progress_cb is not None and progress_cb_type == "number":
2464
+ progress_cb(1)
2465
+
2466
+ async def download_paths_async(
2467
+ self,
2468
+ ids: List[int],
2469
+ paths: List[str],
2470
+ semaphore: Optional[asyncio.Semaphore] = None,
2471
+ headers: Optional[dict] = None,
2472
+ chunk_size: int = 1024 * 1024,
2473
+ check_hash: bool = True,
2474
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
2475
+ progress_cb_type: Literal["number", "size"] = "number",
2476
+ ) -> None:
2477
+ """
2478
+ Download Videos with given IDs and saves them to given local paths asynchronously.
2479
+
2480
+ :param ids: List of Video IDs in Supervisely.
2481
+ :type ids: :class:`List[int]`
2482
+ :param paths: Local save paths for Videos.
2483
+ :type paths: :class:`List[str]`
2484
+ :param semaphore: Semaphore
2485
+ :type semaphore: :class:`asyncio.Semaphore`, optional
2486
+ :param headers: Headers for request.
2487
+ :type headers: dict, optional
2488
+ :param chunk_size: Size of chunk for partial download. Default is 1MB.
2489
+ :type chunk_size: int, optional
2490
+ :param check_hash: If True, checks hash of downloaded files.
2491
+ :type check_hash: bool, optional
2492
+ :param progress_cb: Function for tracking download progress.
2493
+ :type progress_cb: Optional[Union[tqdm, Callable]]
2494
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "number".
2495
+ :type progress_cb_type: Literal["number", "size"], optional
2496
+ :raises: :class:`ValueError` if len(ids) != len(paths)
2497
+ :return: None
2498
+ :rtype: :class:`NoneType`
2499
+
2500
+ :Usage example:
2501
+
2502
+ .. code-block:: python
2503
+
2504
+ import supervisely as sly
2505
+ import asyncio
2506
+
2507
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
2508
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
2509
+ api = sly.Api.from_env()
2510
+
2511
+ ids = [770914, 770915]
2512
+ paths = ["/path/to/save/video1.mp4", "/path/to/save/video2.mp4"]
2513
+ loop = asyncio.new_event_loop()
2514
+ asyncio.set_event_loop(loop)
2515
+ loop.run_until_complete(api.video.download_paths_async(ids, paths))
2516
+ """
2517
+ if len(ids) == 0:
2518
+ return
2519
+ if len(ids) != len(paths):
2520
+ raise ValueError('Can not match "ids" and "paths" lists, len(ids) != len(paths)')
2521
+ if semaphore is None:
2522
+ semaphore = self._api._get_default_semaphore()
2523
+ tasks = []
2524
+ for img_id, img_path in zip(ids, paths):
2525
+ task = self.download_path_async(
2526
+ img_id,
2527
+ img_path,
2528
+ semaphore=semaphore,
2529
+ headers=headers,
2530
+ chunk_size=chunk_size,
2531
+ check_hash=check_hash,
2532
+ progress_cb=progress_cb,
2533
+ progress_cb_type=progress_cb_type,
2534
+ )
2535
+
2536
+ tasks.append(task)
2537
+ await asyncio.gather(*tasks)