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.
- supervisely/api/api.py +609 -3
- supervisely/api/file_api.py +574 -14
- supervisely/api/image_api.py +469 -0
- supervisely/api/pointcloud/pointcloud_api.py +390 -1
- supervisely/api/video/video_api.py +231 -1
- supervisely/api/volume/volume_api.py +223 -2
- supervisely/app/development/__init__.py +1 -0
- supervisely/app/development/development.py +96 -2
- supervisely/app/fastapi/subapp.py +19 -4
- supervisely/convert/base_converter.py +53 -4
- supervisely/convert/converter.py +6 -5
- supervisely/convert/image/image_converter.py +26 -13
- supervisely/convert/image/sly/fast_sly_image_converter.py +4 -0
- supervisely/convert/image/sly/sly_image_converter.py +9 -4
- supervisely/convert/pointcloud_episodes/sly/sly_pointcloud_episodes_converter.py +7 -1
- supervisely/convert/video/sly/sly_video_converter.py +9 -1
- supervisely/convert/video/video_converter.py +44 -23
- supervisely/io/fs.py +125 -0
- supervisely/io/fs_cache.py +19 -1
- supervisely/io/network_exceptions.py +20 -3
- supervisely/task/progress.py +1 -1
- {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/METADATA +3 -1
- {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/RECORD +27 -27
- {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/LICENSE +0 -0
- {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/WHEEL +0 -0
- {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.220.dist-info → supervisely-6.73.222.dist-info}/top_level.txt +0 -0
supervisely/api/file_api.py
CHANGED
|
@@ -3,30 +3,32 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
import asyncio
|
|
6
7
|
import mimetypes
|
|
7
8
|
import os
|
|
8
|
-
import re
|
|
9
9
|
import shutil
|
|
10
10
|
import tarfile
|
|
11
11
|
import tempfile
|
|
12
|
-
import urllib
|
|
13
12
|
from pathlib import Path
|
|
13
|
+
from time import time
|
|
14
14
|
from typing import Callable, Dict, List, NamedTuple, Optional, Union
|
|
15
15
|
|
|
16
|
+
import aiofiles
|
|
16
17
|
import requests
|
|
18
|
+
from dotenv import load_dotenv
|
|
17
19
|
from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
|
|
18
20
|
from tqdm import tqdm
|
|
19
21
|
from typing_extensions import Literal
|
|
20
22
|
|
|
21
23
|
import supervisely.io.env as env
|
|
22
24
|
import supervisely.io.fs as sly_fs
|
|
23
|
-
from supervisely._utils import batched,
|
|
25
|
+
from supervisely._utils import batched, rand_str
|
|
24
26
|
from supervisely.api.module_api import ApiField, ModuleApiBase
|
|
25
|
-
from supervisely.imaging.image import get_hash, write_bytes
|
|
26
27
|
from supervisely.io.fs import (
|
|
27
28
|
ensure_base_path,
|
|
28
29
|
get_file_ext,
|
|
29
30
|
get_file_hash,
|
|
31
|
+
get_file_hash_async,
|
|
30
32
|
get_file_name,
|
|
31
33
|
get_file_name_with_ext,
|
|
32
34
|
get_file_size,
|
|
@@ -36,7 +38,7 @@ from supervisely.io.fs import (
|
|
|
36
38
|
from supervisely.io.fs_cache import FileCache
|
|
37
39
|
from supervisely.io.json import load_json_file
|
|
38
40
|
from supervisely.sly_logger import logger
|
|
39
|
-
from supervisely.task.progress import Progress,
|
|
41
|
+
from supervisely.task.progress import Progress, tqdm_sly
|
|
40
42
|
|
|
41
43
|
|
|
42
44
|
class FileInfo(NamedTuple):
|
|
@@ -414,13 +416,29 @@ class FileApi(ModuleApiBase):
|
|
|
414
416
|
return dir_size
|
|
415
417
|
|
|
416
418
|
def _download(
|
|
417
|
-
self,
|
|
418
|
-
|
|
419
|
+
self,
|
|
420
|
+
team_id,
|
|
421
|
+
remote_path,
|
|
422
|
+
local_save_path,
|
|
423
|
+
progress_cb=None,
|
|
424
|
+
show_progress: bool = False,
|
|
425
|
+
):
|
|
419
426
|
response = self._api.post(
|
|
420
427
|
"file-storage.download",
|
|
421
428
|
{ApiField.TEAM_ID: team_id, ApiField.PATH: remote_path},
|
|
422
429
|
stream=True,
|
|
423
430
|
)
|
|
431
|
+
if show_progress is False:
|
|
432
|
+
progress_cb = None
|
|
433
|
+
elif show_progress and progress_cb is None:
|
|
434
|
+
total_size = int(response.headers.get("Content-Length", 0))
|
|
435
|
+
progress_cb = tqdm_sly(
|
|
436
|
+
total=total_size,
|
|
437
|
+
unit="B",
|
|
438
|
+
unit_scale=True,
|
|
439
|
+
desc="Downloading file",
|
|
440
|
+
leave=True,
|
|
441
|
+
)
|
|
424
442
|
# print(response.headers)
|
|
425
443
|
# print(response.headers['Content-Length'])
|
|
426
444
|
ensure_base_path(local_save_path)
|
|
@@ -500,6 +518,7 @@ class FileApi(ModuleApiBase):
|
|
|
500
518
|
def parse_agent_id_and_path(self, remote_path: str) -> int:
|
|
501
519
|
return sly_fs.parse_agent_id_and_path(remote_path)
|
|
502
520
|
|
|
521
|
+
# TODO replace with download_async
|
|
503
522
|
def download_from_agent(
|
|
504
523
|
self,
|
|
505
524
|
remote_path: str,
|
|
@@ -530,6 +549,7 @@ class FileApi(ModuleApiBase):
|
|
|
530
549
|
if progress_cb is not None:
|
|
531
550
|
progress_cb(len(chunk))
|
|
532
551
|
|
|
552
|
+
# TODO replace with download_directory_async
|
|
533
553
|
def download_directory(
|
|
534
554
|
self,
|
|
535
555
|
team_id: int,
|
|
@@ -603,7 +623,7 @@ class FileApi(ModuleApiBase):
|
|
|
603
623
|
log_progress: bool = False,
|
|
604
624
|
) -> None:
|
|
605
625
|
"""Downloads data for application from input using environment variables.
|
|
606
|
-
Automatically detects
|
|
626
|
+
Automatically detects if data is a file or a directory and saves it to the specified directory.
|
|
607
627
|
If data is an archive, it will be unpacked to the specified directory if unpack_if_archive is True.
|
|
608
628
|
|
|
609
629
|
:param save_path: path to a directory where data will be saved
|
|
@@ -616,9 +636,11 @@ class FileApi(ModuleApiBase):
|
|
|
616
636
|
:type force: Optional[bool]
|
|
617
637
|
:param log_progress: if True, progress bar will be displayed
|
|
618
638
|
:type log_progress: bool
|
|
619
|
-
:raises RuntimeError:
|
|
620
|
-
|
|
621
|
-
|
|
639
|
+
:raises RuntimeError:
|
|
640
|
+
- if both file and folder paths not found in environment variables \n
|
|
641
|
+
- if both file and folder paths found in environment variables (debug)
|
|
642
|
+
- if team id not found in environment variables
|
|
643
|
+
|
|
622
644
|
:Usage example:
|
|
623
645
|
|
|
624
646
|
.. code-block:: python
|
|
@@ -958,9 +980,7 @@ class FileApi(ModuleApiBase):
|
|
|
958
980
|
)
|
|
959
981
|
return
|
|
960
982
|
|
|
961
|
-
|
|
962
|
-
"file-storage.remove", {ApiField.TEAM_ID: team_id, ApiField.PATH: path}
|
|
963
|
-
)
|
|
983
|
+
self._api.post("file-storage.remove", {ApiField.TEAM_ID: team_id, ApiField.PATH: path})
|
|
964
984
|
|
|
965
985
|
def remove_file(self, team_id: int, path: str) -> None:
|
|
966
986
|
"""
|
|
@@ -1401,6 +1421,55 @@ class FileApi(ModuleApiBase):
|
|
|
1401
1421
|
self.upload_bulk(team_id, local_paths_batch, remote_files_batch, progress_size_cb)
|
|
1402
1422
|
return res_remote_dir
|
|
1403
1423
|
|
|
1424
|
+
def load_dotenv_from_teamfiles(
|
|
1425
|
+
self, remote_path: str = None, team_id: int = None, override: int = False
|
|
1426
|
+
) -> None:
|
|
1427
|
+
"""Loads .env file from Team Files into environment variables.
|
|
1428
|
+
If remote_path or team_id is not specified, it will be taken from environment variables.
|
|
1429
|
+
|
|
1430
|
+
:param remote_path: Path to .env file in Team Files.
|
|
1431
|
+
:type remote_path: str, optional
|
|
1432
|
+
:param team_id: Team ID in Supervisely.
|
|
1433
|
+
:type team_id: int, optional
|
|
1434
|
+
:param override: If True, existing environment variables will be overridden.
|
|
1435
|
+
:type override: bool, optional
|
|
1436
|
+
|
|
1437
|
+
:Usage example:
|
|
1438
|
+
|
|
1439
|
+
.. code-block:: python
|
|
1440
|
+
|
|
1441
|
+
import supervisely as sly
|
|
1442
|
+
|
|
1443
|
+
api = sly.Api.from_env()
|
|
1444
|
+
|
|
1445
|
+
api.file.load_dotenv_from_teamfiles()
|
|
1446
|
+
# All variables from .env file are loaded into environment variables.
|
|
1447
|
+
"""
|
|
1448
|
+
# If remote_path or team_id is not specified, it will be taken from environment variables.
|
|
1449
|
+
remote_path = remote_path or env.file(raise_not_found=False)
|
|
1450
|
+
team_id = team_id or env.team_id(raise_not_found=False)
|
|
1451
|
+
|
|
1452
|
+
if not remote_path or not team_id or not remote_path.endswith(".env"):
|
|
1453
|
+
return
|
|
1454
|
+
|
|
1455
|
+
try:
|
|
1456
|
+
file_name = sly_fs.get_file_name(remote_path)
|
|
1457
|
+
|
|
1458
|
+
# Use timestamp to avoid conflicts with existing files.
|
|
1459
|
+
timestamp = int(time())
|
|
1460
|
+
local_save_path = os.path.join(os.getcwd(), f"{file_name}_{timestamp}.env")
|
|
1461
|
+
|
|
1462
|
+
# Download .env file from Team Files.
|
|
1463
|
+
self.download(team_id=team_id, remote_path=remote_path, local_save_path=local_save_path)
|
|
1464
|
+
|
|
1465
|
+
# Load .env file into environment variables and then remove it.
|
|
1466
|
+
load_dotenv(local_save_path, override=override)
|
|
1467
|
+
sly_fs.silent_remove(local_save_path)
|
|
1468
|
+
|
|
1469
|
+
logger.debug(f"Loaded .env file from team files: {remote_path}")
|
|
1470
|
+
except Exception as e:
|
|
1471
|
+
logger.debug(f"Failed to load .env file from team files: {remote_path}. Error: {e}")
|
|
1472
|
+
|
|
1404
1473
|
def get_json_file_content(self, team_id: int, remote_path: str, download: bool = False) -> dict:
|
|
1405
1474
|
"""
|
|
1406
1475
|
Get JSON file content.
|
|
@@ -1447,3 +1516,494 @@ class FileApi(ModuleApiBase):
|
|
|
1447
1516
|
return content
|
|
1448
1517
|
else:
|
|
1449
1518
|
raise FileNotFoundError(f"File not found: {remote_path}")
|
|
1519
|
+
|
|
1520
|
+
async def _download_async(
|
|
1521
|
+
self,
|
|
1522
|
+
team_id: int,
|
|
1523
|
+
remote_path: str,
|
|
1524
|
+
local_save_path: str,
|
|
1525
|
+
range_start: Optional[int] = None,
|
|
1526
|
+
range_end: Optional[int] = None,
|
|
1527
|
+
headers: dict = None,
|
|
1528
|
+
check_hash: bool = True,
|
|
1529
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1530
|
+
progress_cb_type: Literal["number", "size"] = "size",
|
|
1531
|
+
):
|
|
1532
|
+
"""
|
|
1533
|
+
Download file from Team Files or connected Cloud Storage.
|
|
1534
|
+
|
|
1535
|
+
:param team_id: Team ID in Supervisely.
|
|
1536
|
+
:type team_id: int
|
|
1537
|
+
:param remote_path: Path to File in Team Files.
|
|
1538
|
+
:type remote_path: str
|
|
1539
|
+
:param local_save_path: Local save path.
|
|
1540
|
+
:type local_save_path: str
|
|
1541
|
+
:param range_start: Start byte position for partial download.
|
|
1542
|
+
:type range_start: int, optional
|
|
1543
|
+
:param range_end: End byte position for partial download.
|
|
1544
|
+
:type range_end: int, optional
|
|
1545
|
+
:param headers: Additional headers for request.
|
|
1546
|
+
:type headers: dict, optional
|
|
1547
|
+
:param check_hash: If True, checks hash of downloaded file.
|
|
1548
|
+
Check is not supported for partial downloads.
|
|
1549
|
+
When range is set, hash check is disabled.
|
|
1550
|
+
:type check_hash: bool
|
|
1551
|
+
:param progress_cb: Function for tracking download progress.
|
|
1552
|
+
:type progress_cb: tqdm or callable, optional
|
|
1553
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "size".
|
|
1554
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
1555
|
+
:return: None
|
|
1556
|
+
:rtype: :class:`NoneType`
|
|
1557
|
+
"""
|
|
1558
|
+
api_method = "file-storage.download"
|
|
1559
|
+
|
|
1560
|
+
if range_start is not None or range_end is not None:
|
|
1561
|
+
check_hash = False
|
|
1562
|
+
headers = headers or {}
|
|
1563
|
+
headers["Range"] = f"bytes={range_start or ''}-{range_end or ''}"
|
|
1564
|
+
logger.debug(f"File: {remote_path}. Setting Range header: {headers['Range']}")
|
|
1565
|
+
|
|
1566
|
+
json_body = {
|
|
1567
|
+
ApiField.TEAM_ID: team_id,
|
|
1568
|
+
ApiField.PATH: remote_path,
|
|
1569
|
+
**self._api.additional_fields,
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
writing_method = "ab" if range_start not in [0, None] else "wb"
|
|
1573
|
+
|
|
1574
|
+
ensure_base_path(local_save_path)
|
|
1575
|
+
hash_to_check = None
|
|
1576
|
+
async with aiofiles.open(local_save_path, writing_method) as fd:
|
|
1577
|
+
async for chunk, hhash in self._api.stream_async(
|
|
1578
|
+
method=api_method,
|
|
1579
|
+
method_type="POST",
|
|
1580
|
+
data=json_body,
|
|
1581
|
+
headers=headers,
|
|
1582
|
+
range_start=range_start,
|
|
1583
|
+
range_end=range_end,
|
|
1584
|
+
):
|
|
1585
|
+
await fd.write(chunk)
|
|
1586
|
+
hash_to_check = hhash
|
|
1587
|
+
if progress_cb is not None and progress_cb_type == "size":
|
|
1588
|
+
progress_cb(len(chunk))
|
|
1589
|
+
await fd.flush()
|
|
1590
|
+
|
|
1591
|
+
if check_hash:
|
|
1592
|
+
if hash_to_check is not None:
|
|
1593
|
+
downloaded_file_hash = await get_file_hash_async(local_save_path)
|
|
1594
|
+
if hash_to_check != downloaded_file_hash:
|
|
1595
|
+
raise RuntimeError(
|
|
1596
|
+
f"Downloaded hash of image with ID:{id} does not match the expected hash: {downloaded_file_hash} != {hash_to_check}"
|
|
1597
|
+
)
|
|
1598
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
1599
|
+
progress_cb(1)
|
|
1600
|
+
|
|
1601
|
+
async def download_async(
|
|
1602
|
+
self,
|
|
1603
|
+
team_id: int,
|
|
1604
|
+
remote_path: str,
|
|
1605
|
+
local_save_path: str,
|
|
1606
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1607
|
+
cache: Optional[FileCache] = None,
|
|
1608
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1609
|
+
progress_cb_type: Literal["number", "size"] = "size",
|
|
1610
|
+
) -> None:
|
|
1611
|
+
"""
|
|
1612
|
+
Download File from Team Files.
|
|
1613
|
+
|
|
1614
|
+
:param team_id: Team ID in Supervisely.
|
|
1615
|
+
:type team_id: int
|
|
1616
|
+
:param remote_path: Path to File in Team Files.
|
|
1617
|
+
:type remote_path: str
|
|
1618
|
+
:param local_save_path: Local save path.
|
|
1619
|
+
:type local_save_path: str
|
|
1620
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1621
|
+
:type semaphore: asyncio.Semaphore
|
|
1622
|
+
:param cache: Cache object for storing files.
|
|
1623
|
+
:type cache: FileCache, optional
|
|
1624
|
+
:param progress_cb: Function for tracking download progress.
|
|
1625
|
+
:type progress_cb: tqdm or callable, optional
|
|
1626
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "size".
|
|
1627
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
1628
|
+
:return: None
|
|
1629
|
+
:rtype: :class:`NoneType`
|
|
1630
|
+
:Usage example:
|
|
1631
|
+
|
|
1632
|
+
.. code-block:: python
|
|
1633
|
+
|
|
1634
|
+
import supervisely as sly
|
|
1635
|
+
import asyncio
|
|
1636
|
+
|
|
1637
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1638
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1639
|
+
api = sly.Api.from_env()
|
|
1640
|
+
|
|
1641
|
+
path_to_file = "/999_App_Test/ds1/01587.json"
|
|
1642
|
+
local_save_path = "/path/to/save/999_App_Test/ds1/01587.json"
|
|
1643
|
+
loop = asyncio.new_event_loop()
|
|
1644
|
+
asyncio.set_event_loop(loop)
|
|
1645
|
+
loop.run_until_complete(api.file.download_async(8, path_to_file, local_save_path))
|
|
1646
|
+
"""
|
|
1647
|
+
if semaphore is None:
|
|
1648
|
+
semaphore = self._api._get_default_semaphore()
|
|
1649
|
+
async with semaphore:
|
|
1650
|
+
if self.is_on_agent(remote_path):
|
|
1651
|
+
# for optimized download from agent
|
|
1652
|
+
# in other agent cases download will be performed as usual
|
|
1653
|
+
agent_id, path_in_agent_folder = self.parse_agent_id_and_path(remote_path)
|
|
1654
|
+
if (
|
|
1655
|
+
agent_id == env.agent_id(raise_not_found=False)
|
|
1656
|
+
and env.agent_storage(raise_not_found=False) is not None
|
|
1657
|
+
):
|
|
1658
|
+
path_on_agent = os.path.normpath(env.agent_storage() + path_in_agent_folder)
|
|
1659
|
+
logger.info(f"Optimized download from agent: {path_on_agent}")
|
|
1660
|
+
await sly_fs.copy_file_async(
|
|
1661
|
+
path_on_agent, local_save_path, progress_cb, progress_cb_type
|
|
1662
|
+
)
|
|
1663
|
+
return
|
|
1664
|
+
|
|
1665
|
+
if cache is None:
|
|
1666
|
+
await self._download_async(
|
|
1667
|
+
team_id,
|
|
1668
|
+
remote_path,
|
|
1669
|
+
local_save_path,
|
|
1670
|
+
progress_cb=progress_cb,
|
|
1671
|
+
progress_cb_type=progress_cb_type,
|
|
1672
|
+
)
|
|
1673
|
+
else:
|
|
1674
|
+
file_info = self.get_info_by_path(team_id, remote_path)
|
|
1675
|
+
if file_info.hash is None:
|
|
1676
|
+
await self._download_async(
|
|
1677
|
+
team_id,
|
|
1678
|
+
remote_path,
|
|
1679
|
+
local_save_path,
|
|
1680
|
+
progress_cb=progress_cb,
|
|
1681
|
+
progress_cb_type=progress_cb_type,
|
|
1682
|
+
)
|
|
1683
|
+
else:
|
|
1684
|
+
cache_path = cache.check_storage_object(
|
|
1685
|
+
file_info.hash, get_file_ext(remote_path)
|
|
1686
|
+
)
|
|
1687
|
+
if cache_path is None:
|
|
1688
|
+
# file not in cache
|
|
1689
|
+
await self._download_async(
|
|
1690
|
+
team_id,
|
|
1691
|
+
remote_path,
|
|
1692
|
+
local_save_path,
|
|
1693
|
+
progress_cb=progress_cb,
|
|
1694
|
+
progress_cb_type=progress_cb_type,
|
|
1695
|
+
)
|
|
1696
|
+
if file_info.hash != await get_file_hash_async(local_save_path):
|
|
1697
|
+
raise KeyError(
|
|
1698
|
+
f"Remote and local hashes are different (team id: {team_id}, file: {remote_path})"
|
|
1699
|
+
)
|
|
1700
|
+
await cache.write_object_async(local_save_path, file_info.hash)
|
|
1701
|
+
else:
|
|
1702
|
+
await cache.read_object_async(file_info.hash, local_save_path)
|
|
1703
|
+
if progress_cb is not None and progress_cb_type == "size":
|
|
1704
|
+
progress_cb(get_file_size(local_save_path))
|
|
1705
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
1706
|
+
progress_cb(1)
|
|
1707
|
+
|
|
1708
|
+
async def download_bulk_async(
|
|
1709
|
+
self,
|
|
1710
|
+
team_id: int,
|
|
1711
|
+
remote_paths: List[str],
|
|
1712
|
+
local_save_paths: List[str],
|
|
1713
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1714
|
+
caches: Optional[List[FileCache]] = None,
|
|
1715
|
+
progress_cb: Optional[Union[tqdm, Callable]] = None,
|
|
1716
|
+
progress_cb_type: Literal["number", "size"] = "size",
|
|
1717
|
+
):
|
|
1718
|
+
"""
|
|
1719
|
+
Download multiple Files from Team Files.
|
|
1720
|
+
|
|
1721
|
+
:param team_id: Team ID in Supervisely.
|
|
1722
|
+
:type team_id: int
|
|
1723
|
+
:param remote_paths: List of paths to Files in Team Files.
|
|
1724
|
+
:type remote_paths: List[str]
|
|
1725
|
+
:param local_save_paths: List of local save paths.
|
|
1726
|
+
:type local_save_paths: List[str]
|
|
1727
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1728
|
+
:type semaphore: asyncio.Semaphore
|
|
1729
|
+
:param caches: List of cache objects for storing files.
|
|
1730
|
+
:type caches: List[FileCache], optional
|
|
1731
|
+
:param progress_cb: Function for tracking download progress.
|
|
1732
|
+
:type progress_cb: tqdm or callable, optional
|
|
1733
|
+
:param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "size".
|
|
1734
|
+
:type progress_cb_type: Literal["number", "size"], optional
|
|
1735
|
+
:return: None
|
|
1736
|
+
:rtype: :class:`NoneType`
|
|
1737
|
+
:Usage example:
|
|
1738
|
+
|
|
1739
|
+
.. code-block:: python
|
|
1740
|
+
|
|
1741
|
+
import supervisely as sly
|
|
1742
|
+
import asyncio
|
|
1743
|
+
|
|
1744
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1745
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1746
|
+
api = sly.Api.from_env()
|
|
1747
|
+
|
|
1748
|
+
paths_to_files = [
|
|
1749
|
+
"/999_App_Test/ds1/01587.json",
|
|
1750
|
+
"/999_App_Test/ds1/01588.json",
|
|
1751
|
+
"/999_App_Test/ds1/01587.json"
|
|
1752
|
+
]
|
|
1753
|
+
local_paths = [
|
|
1754
|
+
"/path/to/save/999_App_Test/ds1/01587.json",
|
|
1755
|
+
"/path/to/save/999_App_Test/ds1/01588.json",
|
|
1756
|
+
"/path/to/save/999_App_Test/ds1/01587.json"
|
|
1757
|
+
]
|
|
1758
|
+
loop = asyncio.new_event_loop()
|
|
1759
|
+
asyncio.set_event_loop(loop)
|
|
1760
|
+
loop.run_until_complete(
|
|
1761
|
+
api.file.download_bulk_async(8, paths_to_files, local_paths)
|
|
1762
|
+
)
|
|
1763
|
+
"""
|
|
1764
|
+
if len(remote_paths) == 0:
|
|
1765
|
+
return
|
|
1766
|
+
|
|
1767
|
+
if len(remote_paths) != len(local_save_paths):
|
|
1768
|
+
raise ValueError(
|
|
1769
|
+
f"Length of remote_paths and local_save_paths must be equal: {len(remote_paths)} != {len(local_save_paths)}"
|
|
1770
|
+
)
|
|
1771
|
+
elif caches is not None and len(remote_paths) != len(caches):
|
|
1772
|
+
raise ValueError(
|
|
1773
|
+
f"Length of remote_paths and caches must be equal: {len(remote_paths)} != {len(caches)}"
|
|
1774
|
+
)
|
|
1775
|
+
|
|
1776
|
+
if semaphore is None:
|
|
1777
|
+
semaphore = self._api._get_default_semaphore()
|
|
1778
|
+
|
|
1779
|
+
tasks = []
|
|
1780
|
+
for remote_path, local_path, cache in zip(
|
|
1781
|
+
remote_paths, local_save_paths, caches or [None] * len(remote_paths)
|
|
1782
|
+
):
|
|
1783
|
+
task = self.download_async(
|
|
1784
|
+
team_id,
|
|
1785
|
+
remote_path,
|
|
1786
|
+
local_path,
|
|
1787
|
+
semaphore=semaphore,
|
|
1788
|
+
cache=cache,
|
|
1789
|
+
progress_cb=progress_cb,
|
|
1790
|
+
progress_cb_type=progress_cb_type,
|
|
1791
|
+
)
|
|
1792
|
+
tasks.append(task)
|
|
1793
|
+
await asyncio.gather(*tasks)
|
|
1794
|
+
|
|
1795
|
+
async def download_directory_async(
|
|
1796
|
+
self,
|
|
1797
|
+
team_id: int,
|
|
1798
|
+
remote_path: str,
|
|
1799
|
+
local_save_path: str,
|
|
1800
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1801
|
+
show_progress: bool = True,
|
|
1802
|
+
) -> None:
|
|
1803
|
+
"""
|
|
1804
|
+
Download Directory from Team Files to local path asynchronously.
|
|
1805
|
+
|
|
1806
|
+
:param team_id: Team ID in Supervisely.
|
|
1807
|
+
:type team_id: int
|
|
1808
|
+
:param remote_path: Path to Directory in Team Files.
|
|
1809
|
+
:type remote_path: str
|
|
1810
|
+
:param local_save_path: Local save path.
|
|
1811
|
+
:type local_save_path: str
|
|
1812
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads.
|
|
1813
|
+
:type semaphore: asyncio.Semaphore
|
|
1814
|
+
:param show_progress: If True show download progress.
|
|
1815
|
+
:type show_progress: bool
|
|
1816
|
+
:return: None
|
|
1817
|
+
:rtype: :class:`NoneType`
|
|
1818
|
+
:Usage example:
|
|
1819
|
+
|
|
1820
|
+
.. code-block:: python
|
|
1821
|
+
|
|
1822
|
+
import supervisely as sly
|
|
1823
|
+
import asyncio
|
|
1824
|
+
|
|
1825
|
+
os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
|
|
1826
|
+
os.environ['API_TOKEN'] = 'Your Supervisely API Token'
|
|
1827
|
+
api = sly.Api.from_env()
|
|
1828
|
+
|
|
1829
|
+
path_to_dir = "/files/folder"
|
|
1830
|
+
local_path = "path/to/local/folder"
|
|
1831
|
+
|
|
1832
|
+
loop = asyncio.new_event_loop()
|
|
1833
|
+
asyncio.set_event_loop(loop)
|
|
1834
|
+
loop.run_until_complete(
|
|
1835
|
+
api.file.download_directory_async(9, path_to_dir, local_path)
|
|
1836
|
+
)
|
|
1837
|
+
"""
|
|
1838
|
+
|
|
1839
|
+
if semaphore is None:
|
|
1840
|
+
semaphore = self._api._get_default_semaphore()
|
|
1841
|
+
|
|
1842
|
+
if not remote_path.endswith("/"):
|
|
1843
|
+
remote_path += "/"
|
|
1844
|
+
|
|
1845
|
+
tasks = []
|
|
1846
|
+
files = self._api.storage.list( # to avoid method duplication in storage api
|
|
1847
|
+
team_id,
|
|
1848
|
+
remote_path,
|
|
1849
|
+
recursive=True,
|
|
1850
|
+
include_folders=False,
|
|
1851
|
+
with_metadata=False,
|
|
1852
|
+
)
|
|
1853
|
+
sizeb = sum([file.sizeb for file in files])
|
|
1854
|
+
if show_progress:
|
|
1855
|
+
progress_cb = tqdm_sly(
|
|
1856
|
+
total=sizeb, desc=f"Downloading files from directory", unit="B", unit_scale=True
|
|
1857
|
+
)
|
|
1858
|
+
else:
|
|
1859
|
+
progress_cb = None
|
|
1860
|
+
|
|
1861
|
+
for file in files:
|
|
1862
|
+
task = self.download_async(
|
|
1863
|
+
team_id,
|
|
1864
|
+
file.path,
|
|
1865
|
+
os.path.join(local_save_path, file.path[len(remote_path) :]),
|
|
1866
|
+
semaphore=semaphore,
|
|
1867
|
+
progress_cb=progress_cb,
|
|
1868
|
+
)
|
|
1869
|
+
tasks.append(task)
|
|
1870
|
+
await asyncio.gather(*tasks)
|
|
1871
|
+
|
|
1872
|
+
async def download_input_async(
|
|
1873
|
+
self,
|
|
1874
|
+
save_path: str,
|
|
1875
|
+
semaphore: Optional[asyncio.Semaphore] = None,
|
|
1876
|
+
unpack_if_archive: Optional[bool] = True,
|
|
1877
|
+
remove_archive: Optional[bool] = True,
|
|
1878
|
+
force: Optional[bool] = False,
|
|
1879
|
+
show_progress: bool = False,
|
|
1880
|
+
) -> None:
|
|
1881
|
+
"""Asynchronously downloads data for the application, using a path from file/folder selector.
|
|
1882
|
+
The application adds this path to environment variables, which the method then reads.
|
|
1883
|
+
Automatically detects if data is a file or a directory and saves it to the specified directory.
|
|
1884
|
+
If data is an archive, it will be unpacked to the specified directory if unpack_if_archive is True.
|
|
1885
|
+
|
|
1886
|
+
:param save_path: path to a directory where data will be saved
|
|
1887
|
+
:type save_path: str
|
|
1888
|
+
:param semaphore: Semaphore for limiting the number of simultaneous downloads
|
|
1889
|
+
:type semaphore: asyncio.Semaphore
|
|
1890
|
+
:param unpack_if_archive: if True, archive will be unpacked to the specified directory
|
|
1891
|
+
:type unpack_if_archive: Optional[bool]
|
|
1892
|
+
:param remove_archive: if True, archive will be removed after unpacking
|
|
1893
|
+
:type remove_archive: Optional[bool]
|
|
1894
|
+
:param force: if True, data will be downloaded even if it already exists in the specified directory
|
|
1895
|
+
:type force: Optional[bool]
|
|
1896
|
+
:param show_progress: if True, progress bar will be displayed
|
|
1897
|
+
:type show_progress: bool
|
|
1898
|
+
:raises RuntimeError:
|
|
1899
|
+
- if both file and folder paths not found in environment variables \n
|
|
1900
|
+
- if both file and folder paths found in environment variables (debug)
|
|
1901
|
+
- if team id not found in environment variables
|
|
1902
|
+
|
|
1903
|
+
:Usage example:
|
|
1904
|
+
|
|
1905
|
+
.. code-block:: python
|
|
1906
|
+
|
|
1907
|
+
import os
|
|
1908
|
+
from dotenv import load_dotenv
|
|
1909
|
+
|
|
1910
|
+
import supervisely as sly
|
|
1911
|
+
import asyncio
|
|
1912
|
+
|
|
1913
|
+
# Load secrets and create API object from .env file (recommended)
|
|
1914
|
+
# Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication
|
|
1915
|
+
load_dotenv(os.path.expanduser("~/supervisely.env"))
|
|
1916
|
+
api = sly.Api.from_env()
|
|
1917
|
+
|
|
1918
|
+
# Application is started...
|
|
1919
|
+
save_path = "/my_app_data"
|
|
1920
|
+
loop = asyncio.new_event_loop()
|
|
1921
|
+
asyncio.set_event_loop(loop)
|
|
1922
|
+
loop.run_until_complete(
|
|
1923
|
+
api.file.download_input_async(save_path)
|
|
1924
|
+
)
|
|
1925
|
+
|
|
1926
|
+
# The data is downloaded to the specified directory.
|
|
1927
|
+
"""
|
|
1928
|
+
|
|
1929
|
+
if semaphore is None:
|
|
1930
|
+
semaphore = self._api._get_default_semaphore()
|
|
1931
|
+
|
|
1932
|
+
remote_file_path = env.file(raise_not_found=False)
|
|
1933
|
+
remote_folder_path = env.folder(raise_not_found=False)
|
|
1934
|
+
team_id = env.team_id()
|
|
1935
|
+
|
|
1936
|
+
sly_fs.mkdir(save_path)
|
|
1937
|
+
|
|
1938
|
+
if remote_file_path is None and remote_folder_path is None:
|
|
1939
|
+
raise RuntimeError(
|
|
1940
|
+
"Both file and folder paths not found in environment variables. "
|
|
1941
|
+
"Please, specify one of them."
|
|
1942
|
+
)
|
|
1943
|
+
elif remote_file_path is not None and remote_folder_path is not None:
|
|
1944
|
+
raise RuntimeError(
|
|
1945
|
+
"Both file and folder paths found in environment variables. "
|
|
1946
|
+
"Please, specify only one of them."
|
|
1947
|
+
)
|
|
1948
|
+
if team_id is None:
|
|
1949
|
+
raise RuntimeError("Team id not found in environment variables.")
|
|
1950
|
+
|
|
1951
|
+
if remote_file_path is not None:
|
|
1952
|
+
file_name = sly_fs.get_file_name_with_ext(remote_file_path)
|
|
1953
|
+
local_file_path = os.path.join(save_path, file_name)
|
|
1954
|
+
|
|
1955
|
+
if os.path.isfile(local_file_path) and not force:
|
|
1956
|
+
logger.info(
|
|
1957
|
+
f"The file {local_file_path} already exists. "
|
|
1958
|
+
"Download is skipped, if you want to download it again, "
|
|
1959
|
+
"use force=True."
|
|
1960
|
+
)
|
|
1961
|
+
return
|
|
1962
|
+
|
|
1963
|
+
sly_fs.silent_remove(local_file_path)
|
|
1964
|
+
|
|
1965
|
+
progress_cb = None
|
|
1966
|
+
file_info = self.get_info_by_path(team_id, remote_file_path)
|
|
1967
|
+
if show_progress is True and file_info is not None:
|
|
1968
|
+
progress_cb = tqdm_sly(
|
|
1969
|
+
desc=f"Downloading {remote_file_path}",
|
|
1970
|
+
total=file_info.sizeb,
|
|
1971
|
+
unit="B",
|
|
1972
|
+
unit_scale=True,
|
|
1973
|
+
)
|
|
1974
|
+
await self.download_async(
|
|
1975
|
+
team_id,
|
|
1976
|
+
remote_file_path,
|
|
1977
|
+
local_file_path,
|
|
1978
|
+
semaphore=semaphore,
|
|
1979
|
+
progress_cb=progress_cb,
|
|
1980
|
+
)
|
|
1981
|
+
if unpack_if_archive and sly_fs.is_archive(local_file_path):
|
|
1982
|
+
await sly_fs.unpack_archive_async(local_file_path, save_path)
|
|
1983
|
+
if remove_archive:
|
|
1984
|
+
sly_fs.silent_remove(local_file_path)
|
|
1985
|
+
else:
|
|
1986
|
+
logger.info(
|
|
1987
|
+
f"Achive {local_file_path} was unpacked, but not removed. "
|
|
1988
|
+
"If you want to remove it, use remove_archive=True."
|
|
1989
|
+
)
|
|
1990
|
+
elif remote_folder_path is not None:
|
|
1991
|
+
folder_name = os.path.basename(os.path.normpath(remote_folder_path))
|
|
1992
|
+
local_folder_path = os.path.join(save_path, folder_name)
|
|
1993
|
+
if os.path.isdir(local_folder_path) and not force:
|
|
1994
|
+
logger.info(
|
|
1995
|
+
f"The folder {folder_name} already exists. "
|
|
1996
|
+
"Download is skipped, if you want to download it again, "
|
|
1997
|
+
"use force=True."
|
|
1998
|
+
)
|
|
1999
|
+
return
|
|
2000
|
+
|
|
2001
|
+
sly_fs.remove_dir(local_folder_path)
|
|
2002
|
+
|
|
2003
|
+
await self.download_directory_async(
|
|
2004
|
+
team_id,
|
|
2005
|
+
remote_folder_path,
|
|
2006
|
+
local_folder_path,
|
|
2007
|
+
semaphore=semaphore,
|
|
2008
|
+
show_progress=show_progress,
|
|
2009
|
+
)
|