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.

@@ -4,7 +4,9 @@
4
4
  # docs
5
5
  from __future__ import annotations
6
6
 
7
+ import os
7
8
  from collections import defaultdict
9
+ from copy import deepcopy
8
10
  from typing import (
9
11
  TYPE_CHECKING,
10
12
  Any,
@@ -25,7 +27,13 @@ if TYPE_CHECKING:
25
27
  from datetime import datetime, timedelta
26
28
 
27
29
  from supervisely import logger
28
- from supervisely._utils import abs_url, compress_image_url, is_development
30
+ from supervisely._utils import (
31
+ abs_url,
32
+ compare_dicts,
33
+ compress_image_url,
34
+ get_unix_timestamp,
35
+ is_development,
36
+ )
29
37
  from supervisely.annotation.annotation import TagCollection
30
38
  from supervisely.annotation.obj_class import ObjClass
31
39
  from supervisely.annotation.obj_class_collection import ObjClassCollection
@@ -36,6 +44,7 @@ from supervisely.api.module_api import (
36
44
  RemoveableModuleApi,
37
45
  UpdateableModule,
38
46
  )
47
+ from supervisely.io.json import dump_json_file, load_json_file
39
48
  from supervisely.project.project_meta import ProjectMeta
40
49
  from supervisely.project.project_meta import ProjectMetaJsonFields as MetaJsonF
41
50
  from supervisely.project.project_settings import (
@@ -43,6 +52,9 @@ from supervisely.project.project_settings import (
43
52
  ProjectSettingsJsonFields,
44
53
  )
45
54
  from supervisely.project.project_type import (
55
+ _METADATA_SYSTEM_KEY,
56
+ _METADATA_TIMESTAMP_KEY,
57
+ _METADATA_VALIDATION_SCHEMA_KEY,
46
58
  _MULTISPECTRAL_TAG_NAME,
47
59
  _MULTIVIEW_TAG_NAME,
48
60
  ProjectType,
@@ -969,6 +981,278 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
969
981
  )
970
982
  return response.json()
971
983
 
984
+ def get_custom_data(self, id: int) -> Dict[Any, Any]:
985
+ """Returns custom data of the Project by ID.
986
+ Custom data is a dictionary that can be used to store any additional information.
987
+
988
+ :param id: Project ID in Supervisely.
989
+ :type id: int
990
+ :return: Custom data of the Project
991
+ :rtype: :class:`dict`
992
+
993
+ :Usage example:
994
+
995
+ .. code-block:: python
996
+
997
+ import supervisely as sly
998
+
999
+ api = sly.Api.from_env()
1000
+
1001
+ project_id = 123456
1002
+
1003
+ custom_data = api.project.get_custom_data(project_id)
1004
+
1005
+ print(custom_data) # Output: {'key': 'value'}
1006
+ """
1007
+ return self.get_info_by_id(id).custom_data
1008
+
1009
+ def _get_system_custom_data(self, id: int) -> Dict[Any, Any]:
1010
+ """Returns system custom data of the Project by ID.
1011
+ System custom data is just a part of custom data that is used to store system information
1012
+ and obtained by the key `_METADATA_SYSTEM_KEY`.
1013
+
1014
+ :param id: Project ID in Supervisely.
1015
+ :type id: int
1016
+ :return: System custom data of the Project
1017
+ :rtype: :class:`dict`
1018
+
1019
+ :Usage example:
1020
+
1021
+ .. code-block:: python
1022
+
1023
+ import supervisely as sly
1024
+
1025
+ api = sly.Api.from_env()
1026
+
1027
+ project_id = 123456
1028
+
1029
+ system_custom_data = api.project._get_system_custom_data(project_id)
1030
+
1031
+ print(system_custom_data)
1032
+ """
1033
+ return self.get_info_by_id(id).custom_data.get(_METADATA_SYSTEM_KEY, {})
1034
+
1035
+ def get_validation_schema(self, id: int, use_caching: bool = False) -> Optional[Dict[Any, Any]]:
1036
+ """Returns validation schema of the Project by ID.
1037
+ Validation schema is a dictionary that can be used to validate metadata of each entity in the project
1038
+ if corresnpoding schema is provided.
1039
+ If using caching, the schema will be loaded from the cache if available.
1040
+ Use cached version only in scenarios when the schema is not expected to change,
1041
+ otherwise it may lead to checks with outdated schema.
1042
+
1043
+ :param id: Project ID in Supervisely.
1044
+ :type id: int
1045
+ :param use_caching: If True, uses cached version of the schema if available.
1046
+ NOTE: This may lead to checks with outdated schema. Use with caution.
1047
+ And only in scenarios when the schema is not expected to change.
1048
+ :return: Validation schema of the Project
1049
+ :rtype: :class:`dict`
1050
+
1051
+ :Usage example:
1052
+
1053
+ .. code-block:: python
1054
+
1055
+ import supervisely as sly
1056
+
1057
+ api = sly.Api.from_env()
1058
+
1059
+ project_id = 123456
1060
+
1061
+ validation_schema = api.project.get_validation_schema(project_id)
1062
+
1063
+ print(validation_schema) # Output: {'key': 'Description of the field'}
1064
+ """
1065
+ SCHEMA_DIFF_THRESHOLD = 60 * 60 # 1 hour
1066
+ json_cache_filename = os.path.join(os.getcwd(), f"{id}_validation_schema.json")
1067
+
1068
+ if use_caching:
1069
+ if os.path.isfile(json_cache_filename):
1070
+ try:
1071
+ schema = load_json_file(json_cache_filename)
1072
+ timestamp = schema.pop(_METADATA_TIMESTAMP_KEY, 0)
1073
+
1074
+ if get_unix_timestamp() - timestamp < SCHEMA_DIFF_THRESHOLD:
1075
+ return schema
1076
+ except RuntimeError:
1077
+ pass
1078
+
1079
+ schema = self._get_system_custom_data(id).get(_METADATA_VALIDATION_SCHEMA_KEY)
1080
+ if schema and use_caching:
1081
+ schema_with_timestamp = deepcopy(schema)
1082
+ schema_with_timestamp[_METADATA_TIMESTAMP_KEY] = get_unix_timestamp()
1083
+ dump_json_file(schema_with_timestamp, json_cache_filename)
1084
+
1085
+ return schema
1086
+
1087
+ def _edit_validation_schema(
1088
+ self, id: int, schema: Optional[Dict[Any, Any]] = None
1089
+ ) -> Dict[Any, Any]:
1090
+ """Edits validation schema of the Project by ID.
1091
+ Do not use this method directly, use `set_validation_schema` or `remove_validation_schema` instead.
1092
+
1093
+ :param id: Project ID in Supervisely.
1094
+ :type id: int
1095
+ :param schema: Validation schema to set. If None, removes validation schema.
1096
+ :type schema: dict, optional
1097
+ :return: Project information in dict format
1098
+ :rtype: :class:`dict`
1099
+
1100
+ :Usage example:
1101
+
1102
+ .. code-block:: python
1103
+
1104
+ import supervisely as sly
1105
+
1106
+ api = sly.Api.from_env()
1107
+
1108
+ project_id = 123456
1109
+
1110
+ schema = {'key': 'Description of the field'}
1111
+
1112
+ api.project._edit_validation_schema(project_id, schema) #Set new validation schema.
1113
+ api.project._edit_validation_schema(project_id) #Remove validation schema.
1114
+ """
1115
+ custom_data = self.get_custom_data(id)
1116
+ system_data = custom_data.setdefault(_METADATA_SYSTEM_KEY, {})
1117
+
1118
+ if not schema:
1119
+ system_data.pop(_METADATA_VALIDATION_SCHEMA_KEY, None)
1120
+ else:
1121
+ system_data[_METADATA_VALIDATION_SCHEMA_KEY] = schema
1122
+ return self.update_custom_data(id, custom_data)
1123
+
1124
+ def set_validation_schema(self, id: int, schema: Dict[Any, Any]) -> Dict[Any, Any]:
1125
+ """Sets validation schema of the Project by ID.
1126
+ NOTE: This method will overwrite existing validation schema. To extend existing schema,
1127
+ use `get_validation_schema` first to get current schema, then update it and use this method to set new schema.
1128
+
1129
+ :param id: Project ID in Supervisely.
1130
+ :type id: int
1131
+ :param schema: Validation schema to set.
1132
+ :type schema: dict
1133
+ :return: Project information in dict format
1134
+ :rtype: :class:`dict`
1135
+
1136
+ :Usage example:
1137
+
1138
+ .. code-block:: python
1139
+
1140
+ import supervisely as sly
1141
+
1142
+ api = sly.Api.from_env()
1143
+
1144
+ project_id = 123456
1145
+
1146
+ schema = {'key': 'Description of the field'}
1147
+
1148
+ api.project.set_validation_schema(project_id, schema)
1149
+ """
1150
+ return self._edit_validation_schema(id, schema)
1151
+
1152
+ def remove_validation_schema(self, id: int) -> Dict[Any, Any]:
1153
+ """Removes validation schema of the Project by ID.
1154
+
1155
+ :param id: Project ID in Supervisely.
1156
+ :type id: int
1157
+ :return: Project information in dict format
1158
+ :rtype: :class:`dict`
1159
+
1160
+ :Usage example:
1161
+
1162
+ .. code-block:: python
1163
+
1164
+ import supervisely as sly
1165
+
1166
+ api = sly.Api.from_env()
1167
+
1168
+ project_id = 123456
1169
+
1170
+ api.project.remove_validation_schema(project_id)
1171
+ """
1172
+ return self._edit_validation_schema(id)
1173
+
1174
+ def validate_entities_schema(
1175
+ self, id: int, strict: bool = False
1176
+ ) -> List[Dict[str, Union[id, str, List[str], List[Any]]]]:
1177
+ """Validates entities of the Project by ID using validation schema.
1178
+ Returns list of entities that do not match the schema.
1179
+
1180
+ Example of the returned list:
1181
+
1182
+ [
1183
+ {
1184
+ "entity_id": 123456,
1185
+ "entity_name": "image.jpg",
1186
+ "missing_fields": ["location"],
1187
+ "extra_fields": ["city.name"] <- Nested field (field "name" of the field "city")
1188
+ }
1189
+ ]
1190
+
1191
+ :param id: Project ID in Supervisely.
1192
+ :type id: int
1193
+ :param strict: If strict is disabled, only checks if the entity has all the fields from the schema.
1194
+ Any extra fields in the entity will be ignored and will not be considered as an error.
1195
+ If strict is enabled, checks that the entity custom data is an exact match to the schema.
1196
+ :type strict: bool, optional
1197
+ :return: List of dictionaries with information about entities that do not match the schema.
1198
+ :rtype: :class:`List[Dict[str, Union[id, str, List[str], List[Any]]]`
1199
+
1200
+ :Usage example:
1201
+
1202
+ .. code-block:: python
1203
+
1204
+ import supervisely as sly
1205
+
1206
+ api = sly.Api.from_env()
1207
+
1208
+ project_id = 123456
1209
+
1210
+ incorrect_entities = api.project.validate_entities_schema(project_id)
1211
+
1212
+ for entity in incorrect_entities:
1213
+ print(entity.id, entity.name) # Output: 123456, 'image.jpg'
1214
+ """
1215
+ validation_schema = self.get_validation_schema(id)
1216
+ if not validation_schema:
1217
+ raise ValueError("Validation schema is not set for this project.")
1218
+
1219
+ info = self.get_info_by_id(id)
1220
+ listing_method = {
1221
+ # Mapping of project type to listing method.
1222
+ ProjectType.IMAGES.value: self._api.image.get_list,
1223
+ }
1224
+ custom_data_properties = {
1225
+ # Mapping of project type to custom data property name.
1226
+ # Can be different for different project types.
1227
+ ProjectType.IMAGES.value: "meta"
1228
+ }
1229
+
1230
+ listing_method = listing_method.get(info.type)
1231
+ custom_data_property = custom_data_properties.get(info.type)
1232
+
1233
+ if not listing_method or not custom_data_property:
1234
+ # TODO: Add support for other project types.
1235
+ raise NotImplementedError("Validation schema is not supported for this project type.")
1236
+
1237
+ entities = listing_method(project_id=id)
1238
+ incorrect_entities = []
1239
+
1240
+ for entity in entities:
1241
+ custom_data = getattr(entity, custom_data_property)
1242
+ missing_fields, extra_fields = compare_dicts(
1243
+ validation_schema, custom_data, strict=strict
1244
+ )
1245
+ if missing_fields or extra_fields:
1246
+ entry = {
1247
+ "entity_id": entity.id,
1248
+ "entity_name": entity.name,
1249
+ "missing_fields": missing_fields,
1250
+ "extra_fields": extra_fields,
1251
+ }
1252
+ incorrect_entities.append(entry)
1253
+
1254
+ return incorrect_entities
1255
+
972
1256
  def get_settings(self, id: int) -> Dict[str, str]:
973
1257
  info = self._get_info_by_id(id, "projects.info")
974
1258
  return info.settings
@@ -1,6 +1,7 @@
1
1
  # coding: utf-8
2
2
  from __future__ import annotations
3
3
 
4
+ import asyncio
4
5
  import json
5
6
  from typing import Callable, Dict, List, Optional, Union
6
7
 
@@ -247,3 +248,47 @@ class VideoAnnotationAPI(EntityAnnotationAPI):
247
248
  self.append(dst_id, ann)
248
249
  if progress_cb is not None:
249
250
  progress_cb(1)
251
+
252
+ async def download_async(
253
+ self,
254
+ video_id: int,
255
+ video_info=None,
256
+ semaphore: Optional[asyncio.Semaphore] = None,
257
+ ) -> Dict:
258
+ """
259
+ Download information about VideoAnnotation by video ID from API asynchronously.
260
+
261
+ :param video_id: Video ID in Supervisely.
262
+ :type video_id: int
263
+ :param video_info: VideoInfo object. Use it to avoid additional request to the server.
264
+ :type video_info: VideoInfo, optional
265
+ :param semaphore: Semaphore to limit the number of parallel downloads.
266
+ :type semaphore: asyncio.Semaphore, optional
267
+ :return: Information about VideoAnnotation in json format
268
+ :rtype: :class:`dict`
269
+ :Usage example:
270
+
271
+ .. code-block:: python
272
+
273
+ import supervisely as sly
274
+
275
+ os.environ['SERVER_ADDRESS'] = 'https://app.supervisely.com'
276
+ os.environ['API_TOKEN'] = 'Your Supervisely API Token'
277
+ api = sly.Api.from_env()
278
+
279
+ video_id = 198702499
280
+ loop = sly.utils.get_or_create_event_loop()
281
+ ann_info = loop.run_until_complete(api.video.annotation.download_async(video_id))
282
+ """
283
+ if video_info is None:
284
+ video_info = self._api.video.get_info_by_id(video_id)
285
+
286
+ if semaphore is None:
287
+ semaphore = self._api._get_default_semaphore()
288
+
289
+ async with semaphore:
290
+ response = await self._api.post_async(
291
+ self._method_download_bulk,
292
+ {ApiField.DATASET_ID: video_info.dataset_id, self._entity_ids_str: [video_info.id]},
293
+ )
294
+ return response.json()
@@ -2466,8 +2466,7 @@ class VideoApi(RemoveableBulkModuleApi):
2466
2466
  save_path = os.path.join("/path/to/save/", video_info.name)
2467
2467
 
2468
2468
  semaphore = asyncio.Semaphore(100)
2469
- loop = asyncio.new_event_loop()
2470
- asyncio.set_event_loop(loop)
2469
+ loop = sly.utils.get_or_create_event_loop()
2471
2470
  loop.run_until_complete(
2472
2471
  api.video.download_path_async(video_info.id, save_path, semaphore)
2473
2472
  )
@@ -2555,8 +2554,7 @@ class VideoApi(RemoveableBulkModuleApi):
2555
2554
 
2556
2555
  ids = [770914, 770915]
2557
2556
  paths = ["/path/to/save/video1.mp4", "/path/to/save/video2.mp4"]
2558
- loop = asyncio.new_event_loop()
2559
- asyncio.set_event_loop(loop)
2557
+ loop = sly.utils.get_or_create_event_loop()
2560
2558
  loop.run_until_complete(api.video.download_paths_async(ids, paths))
2561
2559
  """
2562
2560
  if len(ids) == 0:
@@ -1343,8 +1343,7 @@ class VolumeApi(RemoveableBulkModuleApi):
1343
1343
  save_path = os.path.join("/path/to/save/", volume_info.name)
1344
1344
 
1345
1345
  semaphore = asyncio.Semaphore(100)
1346
- loop = asyncio.new_event_loop()
1347
- asyncio.set_event_loop(loop)
1346
+ loop = sly.utils.get_or_create_event_loop()
1348
1347
  loop.run_until_complete(
1349
1348
  api.volume.download_path_async(volume_info.id, save_path, semaphore)
1350
1349
  )
@@ -1433,8 +1432,7 @@ class VolumeApi(RemoveableBulkModuleApi):
1433
1432
 
1434
1433
  ids = [770914, 770915]
1435
1434
  paths = ["/path/to/save/volume1.nrrd", "/path/to/save/volume2.nrrd"]
1436
- loop = asyncio.new_event_loop()
1437
- asyncio.set_event_loop(loop)
1435
+ loop = sly.utils.get_or_create_event_loop()
1438
1436
  loop.run_until_complete(api.volume.download_paths_async(ids, paths))
1439
1437
  """
1440
1438
  if len(ids) == 0:
@@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Tuple, Union
6
6
 
7
7
  from tqdm import tqdm
8
8
 
9
- from supervisely._utils import batched, is_production
9
+ from supervisely._utils import batched, get_or_create_event_loop, is_production
10
10
  from supervisely.annotation.annotation import Annotation
11
11
  from supervisely.annotation.tag_meta import TagValueType
12
12
  from supervisely.api.api import Api
@@ -468,7 +468,7 @@ class BaseConverter:
468
468
  for remote_path in files.values()
469
469
  )
470
470
 
471
- loop = asyncio.get_event_loop()
471
+ loop = get_or_create_event_loop()
472
472
  _, progress_cb = self.get_progress(
473
473
  len(files) if not is_archive_type else file_size,
474
474
  f"Downloading {files_type} from remote storage",
@@ -479,15 +479,19 @@ class BaseConverter:
479
479
  silent_remove(local_path)
480
480
 
481
481
  logger.info(f"Downloading {files_type} from remote storage...")
482
- loop.run_until_complete(
483
- self._api.storage.download_bulk_async(
484
- team_id=self._team_id,
485
- remote_paths=list(files.values()),
486
- local_save_paths=list(files.keys()),
487
- progress_cb=progress_cb,
488
- progress_cb_type="number" if not is_archive_type else "size",
489
- )
482
+ download_coro = self._api.storage.download_bulk_async(
483
+ team_id=self._team_id,
484
+ remote_paths=list(files.values()),
485
+ local_save_paths=list(files.keys()),
486
+ progress_cb=progress_cb,
487
+ progress_cb_type="number" if not is_archive_type else "size",
490
488
  )
489
+
490
+ if loop.is_running():
491
+ future = asyncio.run_coroutine_threadsafe(download_coro, loop=loop)
492
+ future.result()
493
+ else:
494
+ loop.run_until_complete(download_coro)
491
495
  logger.info("Possible annotations downloaded successfully.")
492
496
 
493
497
  if is_archive_type:
supervisely/io/fs.py CHANGED
@@ -1,7 +1,6 @@
1
1
  # coding: utf-8
2
2
 
3
3
  # docs
4
- import asyncio
5
4
  import errno
6
5
  import mimetypes
7
6
  import os
@@ -16,7 +15,7 @@ import requests
16
15
  from requests.structures import CaseInsensitiveDict
17
16
  from tqdm import tqdm
18
17
 
19
- from supervisely._utils import get_bytes_hash, get_string_hash
18
+ from supervisely._utils import get_bytes_hash, get_or_create_event_loop, get_string_hash
20
19
  from supervisely.io.fs_cache import FileCache
21
20
  from supervisely.sly_logger import logger
22
21
  from supervisely.task.progress import Progress
@@ -1375,8 +1374,15 @@ async def copy_file_async(
1375
1374
 
1376
1375
  .. code-block:: python
1377
1376
 
1378
- from supervisely.io.fs import async_copy_file
1379
- await async_copy_file('/home/admin/work/projects/example/1.png', '/home/admin/work/tests/2.png')
1377
+ import supervisely as sly
1378
+
1379
+ loop = sly.utils.get_or_create_event_loop()
1380
+ coro = sly.fs.copy_file_async('/home/admin/work/projects/example/1.png', '/home/admin/work/tests/2.png')
1381
+ if loop.is_running():
1382
+ future = asyncio.run_coroutine_threadsafe(coro, loop)
1383
+ future.result()
1384
+ else:
1385
+ loop.run_until_complete(coro)
1380
1386
  """
1381
1387
  ensure_base_path(dst)
1382
1388
  async with aiofiles.open(dst, "wb") as out_f:
@@ -1404,8 +1410,15 @@ async def get_file_hash_async(path: str) -> str:
1404
1410
 
1405
1411
  .. code-block:: python
1406
1412
 
1407
- from supervisely.io.fs import get_file_hash_async
1408
- hash = await get_file_hash_async('/home/admin/work/projects/examples/1.jpeg') # rKLYA/p/P64dzidaQ/G7itxIz3ZCVnyUhEE9fSMGxU4=
1413
+ import supervisely as sly
1414
+
1415
+ loop = sly.utils.get_or_create_event_loop()
1416
+ coro = sly.fs.get_file_hash_async('/home/admin/work/projects/examples/1.jpeg')
1417
+ if loop.is_running():
1418
+ future = asyncio.run_coroutine_threadsafe(coro, loop)
1419
+ hash = future.result()
1420
+ else:
1421
+ hash = loop.run_until_complete(coro)
1409
1422
  """
1410
1423
  async with aiofiles.open(path, "rb") as file:
1411
1424
  file_bytes = await file.read()
@@ -1442,7 +1455,13 @@ async def unpack_archive_async(
1442
1455
  archive_path = '/home/admin/work/examples.tar'
1443
1456
  target_dir = '/home/admin/work/projects'
1444
1457
 
1445
- await sly.fs.unpack_archive(archive_path, target_dir)
1458
+ loop = sly.utils.get_or_create_event_loop()
1459
+ coro = sly.fs.unpack_archive_async(archive_path, target_dir)
1460
+ if loop.is_running():
1461
+ future = asyncio.run_coroutine_threadsafe(coro, loop)
1462
+ future.result()
1463
+ else:
1464
+ loop.run_until_complete(coro)
1446
1465
  """
1447
1466
  if is_split:
1448
1467
  chunk = chunk_size_mb * 1024 * 1024
@@ -1467,9 +1486,37 @@ async def unpack_archive_async(
1467
1486
  await output_file.write(data)
1468
1487
  archive_path = combined
1469
1488
 
1470
- loop = asyncio.get_running_loop()
1489
+ loop = get_or_create_event_loop()
1471
1490
  await loop.run_in_executor(None, shutil.unpack_archive, archive_path, target_dir)
1472
1491
  if is_split:
1473
1492
  silent_remove(archive_path)
1474
1493
  if remove_junk:
1475
1494
  remove_junk_from_dir(target_dir)
1495
+
1496
+
1497
+ async def touch_async(path: str) -> None:
1498
+ """
1499
+ Sets access and modification times for a file asynchronously.
1500
+
1501
+ :param path: Target file path.
1502
+ :type path: str
1503
+ :returns: None
1504
+ :rtype: :class:`NoneType`
1505
+ :Usage example:
1506
+
1507
+ .. code-block:: python
1508
+
1509
+ import supervisely as sly
1510
+
1511
+ loop = sly.utils.get_or_create_event_loop()
1512
+ coro = sly.fs.touch_async('/home/admin/work/projects/examples/1.jpeg')
1513
+ if loop.is_running():
1514
+ future = asyncio.run_coroutine_threadsafe(coro, loop)
1515
+ future.result()
1516
+ else:
1517
+ loop.run_until_complete(coro)
1518
+ """
1519
+ ensure_base_path(path)
1520
+ async with aiofiles.open(path, "a"):
1521
+ loop = get_or_create_event_loop()
1522
+ await loop.run_in_executor(None, os.utime, path, None)
supervisely/io/json.py CHANGED
@@ -3,6 +3,7 @@ import json
3
3
  import os
4
4
  from typing import Dict, Optional
5
5
 
6
+ import aiofiles
6
7
  import jsonschema
7
8
 
8
9
 
@@ -230,3 +231,34 @@ def validate_json(data: Dict, schema: Dict, raise_error: bool = False) -> bool:
230
231
  if raise_error:
231
232
  raise ValueError("JSON data is invalid. See error message for more details.") from err
232
233
  return False
234
+
235
+
236
+ async def dump_json_file_async(data: Dict, filename: str, indent: Optional[int] = 4) -> None:
237
+ """
238
+ Write given data in json format in file with given name asynchronously.
239
+
240
+ :param data: Data in json format as a dict.
241
+ :type data: dict
242
+ :param filename: Target file path to write data.
243
+ :type filename: str
244
+ :param indent: Json array elements and object members will be pretty-printed with that indent level.
245
+ :type indent: int, optional
246
+ :returns: None
247
+ :rtype: :class:`NoneType`
248
+ :Usage example:
249
+
250
+ .. code-block:: python
251
+
252
+ import supervisely as sly
253
+
254
+ data = {1: 'example'}
255
+ loop = sly.utils.get_or_create_event_loop()
256
+ coro = sly.json.dump_json_file_async(data, '/home/admin/work/projects/examples/1.json')
257
+ if loop.is_running():
258
+ future = asyncio.run_coroutine_threadsafe(coro, loop)
259
+ future.result()
260
+ else:
261
+ loop.run_until_complete(coro)
262
+ """
263
+ async with aiofiles.open(filename, "w") as fout:
264
+ await fout.write(json.dumps(data, indent=indent))