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
@@ -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, is_development, rand_str
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, handle_original_tqdm, tqdm_sly
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, team_id, remote_path, local_save_path, progress_cb=None
418
- ): # TODO: progress bar
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 is data is a file or a directory and saves it to the specified directory.
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: if both file and folder paths not found in environment variables
620
- :raises RuntimeError: if both file and folder paths found in environment variables (debug)
621
- :raises RuntimeError: if team id not found in environment variables
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
- resp = self._api.post(
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
+ )