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