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

@@ -14,6 +14,7 @@ from time import time
14
14
  from typing import Callable, Dict, List, NamedTuple, Optional, Union
15
15
 
16
16
  import aiofiles
17
+ import httpx
17
18
  import requests
18
19
  from dotenv import load_dotenv
19
20
  from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
@@ -1641,8 +1642,7 @@ class FileApi(ModuleApiBase):
1641
1642
 
1642
1643
  path_to_file = "/999_App_Test/ds1/01587.json"
1643
1644
  local_save_path = "/path/to/save/999_App_Test/ds1/01587.json"
1644
- loop = asyncio.new_event_loop()
1645
- asyncio.set_event_loop(loop)
1645
+ loop = sly.utils.get_or_create_event_loop()
1646
1646
  loop.run_until_complete(api.file.download_async(8, path_to_file, local_save_path))
1647
1647
  """
1648
1648
  if semaphore is None:
@@ -1756,8 +1756,7 @@ class FileApi(ModuleApiBase):
1756
1756
  "/path/to/save/999_App_Test/ds1/01588.json",
1757
1757
  "/path/to/save/999_App_Test/ds1/01587.json"
1758
1758
  ]
1759
- loop = asyncio.new_event_loop()
1760
- asyncio.set_event_loop(loop)
1759
+ loop = sly.utils.get_or_create_event_loop()
1761
1760
  loop.run_until_complete(
1762
1761
  api.file.download_bulk_async(8, paths_to_files, local_paths)
1763
1762
  )
@@ -1830,8 +1829,7 @@ class FileApi(ModuleApiBase):
1830
1829
  path_to_dir = "/files/folder"
1831
1830
  local_path = "path/to/local/folder"
1832
1831
 
1833
- loop = asyncio.new_event_loop()
1834
- asyncio.set_event_loop(loop)
1832
+ loop = sly.utils.get_or_create_event_loop()
1835
1833
  loop.run_until_complete(
1836
1834
  api.file.download_directory_async(9, path_to_dir, local_path)
1837
1835
  )
@@ -1918,8 +1916,7 @@ class FileApi(ModuleApiBase):
1918
1916
 
1919
1917
  # Application is started...
1920
1918
  save_path = "/my_app_data"
1921
- loop = asyncio.new_event_loop()
1922
- asyncio.set_event_loop(loop)
1919
+ loop = sly.utils.get_or_create_event_loop()
1923
1920
  loop.run_until_complete(
1924
1921
  api.file.download_input_async(save_path)
1925
1922
  )
@@ -2008,3 +2005,142 @@ class FileApi(ModuleApiBase):
2008
2005
  semaphore=semaphore,
2009
2006
  show_progress=show_progress,
2010
2007
  )
2008
+
2009
+ async def upload_async(
2010
+ self,
2011
+ team_id: int,
2012
+ src: str,
2013
+ dst: str,
2014
+ semaphore: Optional[asyncio.Semaphore] = None,
2015
+ # chunk_size: int = 1024 * 1024, #TODO add with resumaple api
2016
+ # check_hash: bool = True, #TODO add with resumaple api
2017
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
2018
+ progress_cb_type: Literal["number", "size"] = "size",
2019
+ ) -> httpx.Response:
2020
+ """
2021
+ Upload file from local path to Team Files asynchronously.
2022
+
2023
+ :param team_id: Team ID in Supervisely.
2024
+ :type team_id: int
2025
+ :param src: Local path to file.
2026
+ :type src: str
2027
+ :param dst: Path to save file in Team Files.
2028
+ :type dst: str
2029
+ :param semaphore: Semaphore for limiting the number of simultaneous uploads.
2030
+ :type semaphore: asyncio.Semaphore, optional
2031
+ :param progress_cb: Function for tracking download progress.
2032
+ :type progress_cb: tqdm or callable, optional
2033
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "size".
2034
+ :type progress_cb_type: Literal["number", "size"], optional
2035
+ :return: Response from API.
2036
+ :rtype: :class:`httpx.Response`
2037
+ :Usage example:
2038
+
2039
+ .. code-block:: python
2040
+
2041
+ import supervisely as sly
2042
+ import asyncio
2043
+
2044
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
2045
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
2046
+ api = sly.Api.from_env()
2047
+
2048
+ path_to_file = "/path/to/local/file/01587.json"
2049
+ path_to_save = "/files/01587.json"
2050
+ loop = sly.utils.get_or_create_event_loop()
2051
+ loop.run_until_complete(
2052
+ api.file.upload_async(8, path_to_file, path_to_save)
2053
+ )
2054
+ """
2055
+ api_method = "file-storage.upload"
2056
+ headers = {"Content-Type": "application/octet-stream"}
2057
+ # sha256 = await get_file_hash_async(src) #TODO add with resumaple api
2058
+ json_body = {
2059
+ ApiField.TEAM_ID: team_id,
2060
+ ApiField.PATH: dst,
2061
+ # "sha256": sha256, #TODO add with resumaple api
2062
+ }
2063
+ if semaphore is None:
2064
+ semaphore = self._api._get_default_semaphore()
2065
+ async with semaphore:
2066
+ async with aiofiles.open(src, "rb") as fd:
2067
+ item = await fd.read()
2068
+ response = await self._api.post_async(
2069
+ api_method, content=item, params=json_body, headers=headers
2070
+ )
2071
+ if progress_cb is not None and progress_cb_type == "size":
2072
+ progress_cb(len(item))
2073
+ if progress_cb is not None and progress_cb_type == "number":
2074
+ progress_cb(1)
2075
+ return response
2076
+
2077
+ async def upload_bulk_async(
2078
+ self,
2079
+ team_id: int,
2080
+ src_paths: List[str],
2081
+ dst_paths: List[str],
2082
+ semaphore: Optional[asyncio.Semaphore] = None,
2083
+ # chunk_size: int = 1024 * 1024, #TODO add with resumaple api
2084
+ # check_hash: bool = True, #TODO add with resumaple api
2085
+ progress_cb: Optional[Union[tqdm, Callable]] = None,
2086
+ progress_cb_type: Literal["number", "size"] = "size",
2087
+ ) -> None:
2088
+ """
2089
+ Upload multiple files from local paths to Team Files asynchronously.
2090
+
2091
+ :param team_id: Team ID in Supervisely.
2092
+ :type team_id: int
2093
+ :param src_paths: List of local paths to files.
2094
+ :type src_paths: List[str]
2095
+ :param dst_paths: List of paths to save files in Team Files.
2096
+ :type dst_paths: List[str]
2097
+ :param semaphore: Semaphore for limiting the number of simultaneous uploads.
2098
+ :type semaphore: asyncio.Semaphore, optional
2099
+ :param progress_cb: Function for tracking download progress.
2100
+ :type progress_cb: tqdm or callable, optional
2101
+ :param progress_cb_type: Type of progress callback. Can be "number" or "size". Default is "size".
2102
+ :type progress_cb_type: Literal["number", "size"], optional
2103
+ :return: None
2104
+ :rtype: :class:`NoneType`
2105
+ :Usage example:
2106
+
2107
+ .. code-block:: python
2108
+
2109
+ import supervisely as sly
2110
+ import asyncio
2111
+
2112
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
2113
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
2114
+ api = sly.Api.from_env()
2115
+
2116
+ paths_to_files = [
2117
+ "/path/to/local/file/01587.json",
2118
+ "/path/to/local/file/01588.json",
2119
+ "/path/to/local/file/01589.json"
2120
+ ]
2121
+ paths_to_save = [
2122
+ "/files/01587.json",
2123
+ "/files/01588.json",
2124
+ "/files/01589.json"
2125
+ ]
2126
+ loop = sly.utils.get_or_create_event_loop()
2127
+ loop.run_until_complete(
2128
+ api.file.upload_bulk_async(8, paths_to_files, paths_to_save)
2129
+ )
2130
+ """
2131
+ if semaphore is None:
2132
+ semaphore = self._api._get_default_semaphore()
2133
+ tasks = []
2134
+ for s, d in zip(src_paths, dst_paths):
2135
+ task = self.upload_async(
2136
+ team_id,
2137
+ s,
2138
+ d,
2139
+ semaphore=semaphore,
2140
+ # chunk_size=chunk_size, #TODO add with resumaple api
2141
+ # check_hash=check_hash, #TODO add with resumaple api
2142
+ progress_cb=progress_cb,
2143
+ progress_cb_type=progress_cb_type,
2144
+ )
2145
+ tasks.append(task)
2146
+ await asyncio.gather(*tasks)
@@ -40,6 +40,7 @@ from tqdm import tqdm
40
40
 
41
41
  from supervisely._utils import (
42
42
  batched,
43
+ compare_dicts,
43
44
  generate_free_name,
44
45
  get_bytes_hash,
45
46
  resize_image_url,
@@ -1135,7 +1136,14 @@ class ImageApi(RemoveableBulkModuleApi):
1135
1136
  )
1136
1137
 
1137
1138
  def upload_path(
1138
- self, dataset_id: int, name: str, path: str, meta: Optional[Dict] = None
1139
+ self,
1140
+ dataset_id: int,
1141
+ name: str,
1142
+ path: str,
1143
+ meta: Optional[Dict] = None,
1144
+ validate_meta: Optional[bool] = False,
1145
+ use_strict_validation: Optional[bool] = False,
1146
+ use_caching_for_validation: Optional[bool] = False,
1139
1147
  ) -> ImageInfo:
1140
1148
  """
1141
1149
  Uploads Image with given name from given local path to Dataset.
@@ -1148,6 +1156,12 @@ class ImageApi(RemoveableBulkModuleApi):
1148
1156
  :type path: str
1149
1157
  :param meta: Image metadata.
1150
1158
  :type meta: dict, optional
1159
+ :param validate_meta: If True, validates provided meta with saved JSON schema.
1160
+ :type validate_meta: bool, optional
1161
+ :param use_strict_validation: If True, uses strict validation.
1162
+ :type use_strict_validation: bool, optional
1163
+ :param use_caching_for_validation: If True, uses caching for validation.
1164
+ :type use_caching_for_validation: bool, optional
1151
1165
  :return: Information about Image. See :class:`info_sequence<info_sequence>`
1152
1166
  :rtype: :class:`ImageInfo`
1153
1167
  :Usage example:
@@ -1163,7 +1177,15 @@ class ImageApi(RemoveableBulkModuleApi):
1163
1177
  img_info = api.image.upload_path(dataset_id, name="7777.jpeg", path="/home/admin/Downloads/7777.jpeg")
1164
1178
  """
1165
1179
  metas = None if meta is None else [meta]
1166
- return self.upload_paths(dataset_id, [name], [path], metas=metas)[0]
1180
+ return self.upload_paths(
1181
+ dataset_id,
1182
+ [name],
1183
+ [path],
1184
+ metas=metas,
1185
+ validate_meta=validate_meta,
1186
+ use_strict_validation=use_strict_validation,
1187
+ use_caching_for_validation=use_caching_for_validation,
1188
+ )[0]
1167
1189
 
1168
1190
  def upload_paths(
1169
1191
  self,
@@ -1173,6 +1195,9 @@ class ImageApi(RemoveableBulkModuleApi):
1173
1195
  progress_cb: Optional[Union[tqdm, Callable]] = None,
1174
1196
  metas: Optional[List[Dict]] = None,
1175
1197
  conflict_resolution: Optional[Literal["rename", "skip", "replace"]] = None,
1198
+ validate_meta: Optional[bool] = False,
1199
+ use_strict_validation: Optional[bool] = False,
1200
+ use_caching_for_validation: Optional[bool] = False,
1176
1201
  ) -> List[ImageInfo]:
1177
1202
  """
1178
1203
  Uploads Images with given names from given local path to Dataset.
@@ -1189,6 +1214,12 @@ class ImageApi(RemoveableBulkModuleApi):
1189
1214
  :type metas: List[dict], optional
1190
1215
  :param conflict_resolution: The strategy to resolve upload conflicts. 'Replace' option will replace the existing images in the dataset with the new images. The images that are being deleted are logged. 'Skip' option will ignore the upload of new images that would result in a conflict. An original image's ImageInfo list will be returned instead. 'Rename' option will rename the new images to prevent any conflict.
1191
1216
  :type conflict_resolution: Optional[Literal["rename", "skip", "replace"]]
1217
+ :param validate_meta: If True, validates provided meta with saved JSON schema.
1218
+ :type validate_meta: bool, optional
1219
+ :param use_strict_validation: If True, uses strict validation.
1220
+ :type use_strict_validation: bool, optional
1221
+ :param use_caching_for_validation: If True, uses caching for validation.
1222
+ :type use_caching_for_validation: bool, optional
1192
1223
  :raises: :class:`ValueError` if len(names) != len(paths)
1193
1224
  :return: List with information about Images. See :class:`info_sequence<info_sequence>`
1194
1225
  :rtype: :class:`List[ImageInfo]`
@@ -1214,7 +1245,14 @@ class ImageApi(RemoveableBulkModuleApi):
1214
1245
  self._upload_data_bulk(path_to_bytes_stream, zip(paths, hashes), progress_cb=progress_cb)
1215
1246
 
1216
1247
  return self.upload_hashes(
1217
- dataset_id, names, hashes, metas=metas, conflict_resolution=conflict_resolution
1248
+ dataset_id,
1249
+ names,
1250
+ hashes,
1251
+ metas=metas,
1252
+ conflict_resolution=conflict_resolution,
1253
+ validate_meta=validate_meta,
1254
+ use_strict_validation=use_strict_validation,
1255
+ use_caching_for_validation=use_caching_for_validation,
1218
1256
  )
1219
1257
 
1220
1258
  def upload_np(
@@ -1492,6 +1530,9 @@ class ImageApi(RemoveableBulkModuleApi):
1492
1530
  batch_size: Optional[int] = 50,
1493
1531
  skip_validation: Optional[bool] = False,
1494
1532
  conflict_resolution: Optional[Literal["rename", "skip", "replace"]] = None,
1533
+ validate_meta: Optional[bool] = False,
1534
+ use_strict_validation: Optional[bool] = False,
1535
+ use_caching_for_validation: Optional[bool] = False,
1495
1536
  ) -> List[ImageInfo]:
1496
1537
  """
1497
1538
  Upload images from given hashes to Dataset.
@@ -1512,6 +1553,12 @@ class ImageApi(RemoveableBulkModuleApi):
1512
1553
  :type skip_validation: bool, optional
1513
1554
  :param conflict_resolution: The strategy to resolve upload conflicts. 'Replace' option will replace the existing images in the dataset with the new images. The images that are being deleted are logged. 'Skip' option will ignore the upload of new images that would result in a conflict. An original image's ImageInfo list will be returned instead. 'Rename' option will rename the new images to prevent any conflict.
1514
1555
  :type conflict_resolution: Optional[Literal["rename", "skip", "replace"]]
1556
+ :param validate_meta: If True, validates provided meta with saved JSON schema.
1557
+ :type validate_meta: bool, optional
1558
+ :param use_strict_validation: If True, uses strict validation.
1559
+ :type use_strict_validation: bool, optional
1560
+ :param use_caching_for_validation: If True, uses caching for validation.
1561
+ :type use_caching_for_validation: bool, optional
1515
1562
  :return: List with information about Images. See :class:`info_sequence<info_sequence>`
1516
1563
  :rtype: :class:`List[ImageInfo]`
1517
1564
  :Usage example:
@@ -1553,6 +1600,9 @@ class ImageApi(RemoveableBulkModuleApi):
1553
1600
  batch_size=batch_size,
1554
1601
  skip_validation=skip_validation,
1555
1602
  conflict_resolution=conflict_resolution,
1603
+ validate_meta=validate_meta,
1604
+ use_strict_validation=use_strict_validation,
1605
+ use_caching_for_validation=use_caching_for_validation,
1556
1606
  )
1557
1607
 
1558
1608
  def upload_id(
@@ -1750,8 +1800,44 @@ class ImageApi(RemoveableBulkModuleApi):
1750
1800
  force_metadata_for_links=True,
1751
1801
  skip_validation=False,
1752
1802
  conflict_resolution: Optional[Literal["rename", "skip", "replace"]] = None,
1803
+ validate_meta: Optional[bool] = False,
1804
+ use_strict_validation: Optional[bool] = False,
1805
+ use_caching_for_validation: Optional[bool] = False,
1753
1806
  ):
1754
1807
  """ """
1808
+ if use_strict_validation and not validate_meta:
1809
+ raise ValueError(
1810
+ "use_strict_validation is set to True, while validate_meta is set to False. "
1811
+ "Please set validate_meta to True to use strict validation "
1812
+ "or disable strict validation by setting use_strict_validation to False."
1813
+ )
1814
+ if validate_meta:
1815
+ dataset_info = self._api.dataset.get_info_by_id(dataset_id)
1816
+
1817
+ validation_schema = self._api.project.get_validation_schema(
1818
+ dataset_info.project_id, use_caching=use_caching_for_validation
1819
+ )
1820
+
1821
+ if validation_schema is None:
1822
+ raise ValueError(
1823
+ "Validation schema is not set for the project, while "
1824
+ "validate_meta is set to True. Either disable the validation "
1825
+ "or set the validation schema for the project using the "
1826
+ "api.project.set_validation_schema method."
1827
+ )
1828
+
1829
+ for idx, meta in enumerate(metas):
1830
+ missing_fields, extra_fields = compare_dicts(
1831
+ validation_schema, meta, strict=use_strict_validation
1832
+ )
1833
+
1834
+ if missing_fields or extra_fields:
1835
+ raise ValueError(
1836
+ f"Validation failed for the metadata of the image with index {idx} and name {names[idx]}. "
1837
+ "Please check the metadata and try again. "
1838
+ f"Missing fields: {missing_fields}, Extra fields: {extra_fields}"
1839
+ )
1840
+
1755
1841
  if (
1756
1842
  conflict_resolution is not None
1757
1843
  and conflict_resolution not in SUPPORTED_CONFLICT_RESOLUTIONS
@@ -3604,8 +3690,7 @@ class ImageApi(RemoveableBulkModuleApi):
3604
3690
  semaphore = asyncio.Semaphore(100)
3605
3691
  images = api.image.get_list(DATASET_ID)
3606
3692
  img_ids = [image.id for image in images]
3607
- loop = asyncio.new_event_loop()
3608
- asyncio.set_event_loop(loop)
3693
+ loop = sly.utils.get_or_create_event_loop()
3609
3694
  results = loop.run_until_complete(
3610
3695
  api.image.download_nps_async(img_ids, semaphore)
3611
3696
  )
@@ -3671,8 +3756,7 @@ class ImageApi(RemoveableBulkModuleApi):
3671
3756
  save_path = os.path.join("/path/to/save/", img_info.name)
3672
3757
 
3673
3758
  semaphore = asyncio.Semaphore(100)
3674
- loop = asyncio.new_event_loop()
3675
- asyncio.set_event_loop(loop)
3759
+ loop = sly.utils.get_or_create_event_loop()
3676
3760
  loop.run_until_complete(
3677
3761
  api.image.download_path_async(img_info.id, save_path, semaphore)
3678
3762
  )
@@ -3756,8 +3840,7 @@ class ImageApi(RemoveableBulkModuleApi):
3756
3840
 
3757
3841
  ids = [770918, 770919]
3758
3842
  paths = ["/path/to/save/image1.png", "/path/to/save/image2.png"]
3759
- loop = asyncio.new_event_loop()
3760
- asyncio.set_event_loop(loop)
3843
+ loop = sly.utils.get_or_create_event_loop()
3761
3844
  loop.run_until_complete(api.image.download_paths_async(ids, paths))
3762
3845
  """
3763
3846
  if len(ids) == 0:
@@ -3828,8 +3911,7 @@ class ImageApi(RemoveableBulkModuleApi):
3828
3911
  api = sly.Api.from_env()
3829
3912
 
3830
3913
  img_id = 770918
3831
- loop = asyncio.new_event_loop()
3832
- asyncio.set_event_loop(loop)
3914
+ loop = sly.utils.get_or_create_event_loop()
3833
3915
  img_bytes = loop.run_until_complete(api.image.download_bytes_async(img_id))
3834
3916
 
3835
3917
  """
@@ -3906,8 +3988,7 @@ class ImageApi(RemoveableBulkModuleApi):
3906
3988
  os.environ['API_TOKEN
3907
3989
  api = sly.Api.from_env()
3908
3990
 
3909
- loop = asyncio.new_event_loop()
3910
- asyncio.set_event_loop(loop)
3991
+ loop = sly.utils.get_or_create_event_loop()
3911
3992
  semaphore = asyncio.Semaphore(100)
3912
3993
  img_bytes_list = loop.run_until_complete(api.image.download_bytes_imgs_async(ids, semaphore))
3913
3994
  """
@@ -1149,8 +1149,7 @@ class PointcloudApi(RemoveableBulkModuleApi):
1149
1149
  save_path = os.path.join("/path/to/save/", pcd_info.name)
1150
1150
 
1151
1151
  semaphore = asyncio.Semaphore(100)
1152
- loop = asyncio.new_event_loop()
1153
- asyncio.set_event_loop(loop)
1152
+ loop = sly.utils.get_or_create_event_loop()
1154
1153
  loop.run_until_complete(
1155
1154
  api.pointcloud.download_path_async(pcd_info.id, save_path, semaphore)
1156
1155
  )
@@ -1241,8 +1240,7 @@ class PointcloudApi(RemoveableBulkModuleApi):
1241
1240
 
1242
1241
  ids = [19373403, 19373404]
1243
1242
  paths = ["/path/to/save/000063.pcd", "/path/to/save/000064.pcd"]
1244
- loop = asyncio.new_event_loop()
1245
- asyncio.set_event_loop(loop)
1243
+ loop = sly.utils.get_or_create_event_loop()
1246
1244
  loop.run_until_complete(api.pointcloud.download_paths_async(ids, paths))
1247
1245
  """
1248
1246
  if len(ids) == 0:
@@ -1313,8 +1311,7 @@ class PointcloudApi(RemoveableBulkModuleApi):
1313
1311
  save_path = os.path.join("/path/to/save/", img_info.name)
1314
1312
 
1315
1313
  semaphore = asyncio.Semaphore(100)
1316
- loop = asyncio.new_event_loop()
1317
- asyncio.set_event_loop(loop)
1314
+ loop = sly.utils.get_or_create_event_loop()
1318
1315
  loop.run_until_complete(
1319
1316
  api.pointcloud.download_related_image_async(19373403, save_path, semaphore)
1320
1317
  )
@@ -1395,8 +1392,7 @@ class PointcloudApi(RemoveableBulkModuleApi):
1395
1392
  save_paths = [os.path.join("/path/to/save/", img_info.name) for img_info in img_infos]
1396
1393
 
1397
1394
  semaphore = asyncio.Semaphore(100)
1398
- loop = asyncio.new_event_loop()
1399
- asyncio.set_event_loop(loop)
1395
+ loop = sly.utils.get_or_create_event_loop()
1400
1396
  loop.run_until_complete(
1401
1397
  api.pointcloud.download_related_images_async(ids, save_paths, semaphore)
1402
1398
  )