ayon-python-api 1.2.6__tar.gz → 1.2.8__tar.gz

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.
Files changed (49) hide show
  1. {ayon_python_api-1.2.6/ayon_python_api.egg-info → ayon_python_api-1.2.8}/PKG-INFO +1 -1
  2. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/__init__.py +4 -0
  3. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api.py +76 -0
  4. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/lists.py +44 -5
  5. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/constants.py +1 -4
  6. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/entity_hub.py +26 -4
  7. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/server_api.py +133 -17
  8. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/utils.py +16 -0
  9. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/version.py +1 -1
  10. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8/ayon_python_api.egg-info}/PKG-INFO +1 -1
  11. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/pyproject.toml +1 -1
  12. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/LICENSE +0 -0
  13. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/README.md +0 -0
  14. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/__init__.py +0 -0
  15. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/actions.py +0 -0
  16. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/activities.py +0 -0
  17. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/attributes.py +0 -0
  18. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/base.py +0 -0
  19. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/bundles_addons.py +0 -0
  20. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/dependency_packages.py +0 -0
  21. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/events.py +0 -0
  22. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/folders.py +0 -0
  23. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/installers.py +0 -0
  24. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/links.py +0 -0
  25. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/products.py +0 -0
  26. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/projects.py +0 -0
  27. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/representations.py +0 -0
  28. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/secrets.py +0 -0
  29. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/tasks.py +0 -0
  30. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/thumbnails.py +0 -0
  31. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/versions.py +0 -0
  32. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/workfiles.py +0 -0
  33. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/events.py +0 -0
  34. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/exceptions.py +0 -0
  35. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/graphql.py +0 -0
  36. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/graphql_queries.py +0 -0
  37. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/operations.py +0 -0
  38. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/typing.py +0 -0
  39. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  40. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  41. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/requires.txt +0 -0
  42. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/top_level.txt +0 -0
  43. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/setup.cfg +0 -0
  44. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/setup.py +0 -0
  45. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_entity_hub.py +0 -0
  46. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_folder_hierarchy.py +0 -0
  47. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_get_events.py +0 -0
  48. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_graphql_queries.py +0 -0
  49. {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ayon_python_api
3
- Version: 1.2.6
3
+ Version: 1.2.8
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -70,6 +70,8 @@ from ._api import (
70
70
  delete,
71
71
  download_file_to_stream,
72
72
  download_file,
73
+ download_project_file,
74
+ download_project_file_to_stream,
73
75
  upload_file_from_stream,
74
76
  upload_file,
75
77
  upload_reviewable,
@@ -349,6 +351,8 @@ __all__ = (
349
351
  "delete",
350
352
  "download_file_to_stream",
351
353
  "download_file",
354
+ "download_project_file",
355
+ "download_project_file_to_stream",
352
356
  "upload_file_from_stream",
353
357
  "upload_file",
354
358
  "upload_reviewable",
@@ -992,6 +992,80 @@ def download_file(
992
992
  )
993
993
 
994
994
 
995
+ def download_project_file(
996
+ project_name: str,
997
+ file_id: str,
998
+ filepath: str,
999
+ *,
1000
+ chunk_size: Optional[int] = None,
1001
+ progress: Optional[TransferProgress] = None,
1002
+ ) -> TransferProgress:
1003
+ """Download project file to filepath.
1004
+
1005
+ Project files are usually binary files, such as images, videos,
1006
+ or other media files that can be accessed via api endpoint
1007
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1008
+
1009
+ Args:
1010
+ project_name (str): Project name.
1011
+ file_id (str): File id.
1012
+ filepath (str): Path where file will be downloaded.
1013
+ chunk_size (Optional[int]): Size of chunks that are received
1014
+ in single loop.
1015
+ progress (Optional[TransferProgress]): Object that gives ability
1016
+ to track download progress.
1017
+
1018
+ Returns:
1019
+ TransferProgress: Progress object.
1020
+
1021
+ """
1022
+ con = get_server_api_connection()
1023
+ return con.download_project_file(
1024
+ project_name=project_name,
1025
+ file_id=file_id,
1026
+ filepath=filepath,
1027
+ chunk_size=chunk_size,
1028
+ progress=progress,
1029
+ )
1030
+
1031
+
1032
+ def download_project_file_to_stream(
1033
+ project_name: str,
1034
+ file_id: str,
1035
+ stream: StreamType,
1036
+ *,
1037
+ chunk_size: Optional[int] = None,
1038
+ progress: Optional[TransferProgress] = None,
1039
+ ) -> TransferProgress:
1040
+ """Download project file to a stream.
1041
+
1042
+ Project files are usually binary files, such as images, videos,
1043
+ or other media files that can be accessed via api endpoint
1044
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1045
+
1046
+ Args:
1047
+ project_name (str): Project name.
1048
+ file_id (str): File id.
1049
+ stream (StreamType): Stream where output will be stored.
1050
+ chunk_size (Optional[int]): Size of chunks that are received
1051
+ in single loop.
1052
+ progress (Optional[TransferProgress]): Object that gives ability
1053
+ to track download progress.
1054
+
1055
+ Returns:
1056
+ TransferProgress: Progress object.
1057
+
1058
+ """
1059
+ con = get_server_api_connection()
1060
+ return con.download_project_file_to_stream(
1061
+ project_name=project_name,
1062
+ file_id=file_id,
1063
+ stream=stream,
1064
+ chunk_size=chunk_size,
1065
+ progress=progress,
1066
+ )
1067
+
1068
+
995
1069
  def upload_file_from_stream(
996
1070
  endpoint: str,
997
1071
  stream: StreamType,
@@ -4932,6 +5006,8 @@ def get_products(
4932
5006
  Use 'None' if folder is direct child of project.
4933
5007
  product_types (Optional[Iterable[str]]): Product types used for
4934
5008
  filtering.
5009
+ product_base_types (Optional[Iterable[str]]): Product base types
5010
+ used for filtering.
4935
5011
  product_name_regex (Optional[str]): Filter products by name regex.
4936
5012
  product_path_regex (Optional[str]): Filter products by path regex.
4937
5013
  Path starts with folder path and ends with product name.
@@ -26,7 +26,15 @@ class ListsAPI(BaseServerAPI):
26
26
  active: Optional[bool] = None,
27
27
  fields: Optional[Iterable[str]] = None,
28
28
  ) -> Generator[dict[str, Any], None, None]:
29
- """Fetch entity lists from server.
29
+ """Fetch entity lists from AYON server.
30
+
31
+ Warnings:
32
+ You can't get list items for lists with different 'entityType' in
33
+ one call.
34
+
35
+ Notes:
36
+ To get list items, you have to pass 'items' field or
37
+ 'items.{sub-fields you want}' to 'fields' argument.
30
38
 
31
39
  Args:
32
40
  project_name (str): Project name where entity lists are.
@@ -42,7 +50,30 @@ class ListsAPI(BaseServerAPI):
42
50
  """
43
51
  if fields is None:
44
52
  fields = self.get_default_fields_for_type("entityList")
45
- fields = set(fields)
53
+
54
+ # List does not have 'attrib' field but has 'allAttrib' field
55
+ # which is json string and contains only values that are set
56
+ o_fields = tuple(fields)
57
+ fields = set()
58
+ requires_attrib = False
59
+ for field in o_fields:
60
+ if field == "attrib" or field.startswith("attrib."):
61
+ requires_attrib = True
62
+ field = "allAttrib"
63
+ fields.add(field)
64
+
65
+ if "items" in fields:
66
+ fields.discard("items")
67
+ fields |= {
68
+ "items.id",
69
+ "items.entityId",
70
+ "items.entityType",
71
+ "items.position",
72
+ }
73
+
74
+ available_attribs = []
75
+ if requires_attrib:
76
+ available_attribs = self.get_attributes_for_type("list")
46
77
 
47
78
  if active is not None:
48
79
  fields.add("active")
@@ -66,6 +97,15 @@ class ListsAPI(BaseServerAPI):
66
97
  if isinstance(attributes, str):
67
98
  entity_list["attributes"] = json.loads(attributes)
68
99
 
100
+ if requires_attrib:
101
+ all_attrib = json.loads(
102
+ entity_list.get("allAttrib") or "{}"
103
+ )
104
+ entity_list["attrib"] = {
105
+ attrib_name: all_attrib.get(attrib_name)
106
+ for attrib_name in available_attribs
107
+ }
108
+
69
109
  self._convert_entity_data(entity_list)
70
110
 
71
111
  yield entity_list
@@ -170,9 +210,8 @@ class ListsAPI(BaseServerAPI):
170
210
  kwargs[key] = value
171
211
 
172
212
  response = self.post(
173
- f"projects/{project_name}/lists/{list_id}/items",
213
+ f"projects/{project_name}/lists",
174
214
  **kwargs
175
-
176
215
  )
177
216
  response.raise_for_status()
178
217
  return list_id
@@ -343,7 +382,7 @@ class ListsAPI(BaseServerAPI):
343
382
  mode (EntityListItemMode): Mode of items update.
344
383
 
345
384
  """
346
- response = self.post(
385
+ response = self.patch(
347
386
  f"projects/{project_name}/lists/{list_id}/items",
348
387
  items=items,
349
388
  mode=mode,
@@ -247,6 +247,7 @@ DEFAULT_ACTIVITY_FIELDS = {
247
247
  DEFAULT_ENTITY_LIST_FIELDS = {
248
248
  "id",
249
249
  "count",
250
+ "allAttrib",
250
251
  "attributes",
251
252
  "active",
252
253
  "createdBy",
@@ -259,8 +260,4 @@ DEFAULT_ENTITY_LIST_FIELDS = {
259
260
  "tags",
260
261
  "updatedAt",
261
262
  "updatedBy",
262
- "items.id",
263
- "items.entityId",
264
- "items.entityType",
265
- "items.position",
266
263
  }
@@ -1418,7 +1418,9 @@ class EntityData(dict):
1418
1418
  """
1419
1419
  def __init__(self, *args, **kwargs) -> None:
1420
1420
  super().__init__(*args, **kwargs)
1421
- self._orig_data = copy.deepcopy(self)
1421
+ self._orig_data = {}
1422
+ # Fill orig data
1423
+ self.lock()
1422
1424
 
1423
1425
  def get_changes(self) -> dict[str, Any]:
1424
1426
  """Changes in entity data.
@@ -1437,10 +1439,10 @@ class EntityData(dict):
1437
1439
  output[key] = None
1438
1440
  elif key not in self._orig_data:
1439
1441
  # New value was set
1440
- output[key] = self[key]
1442
+ output[key] = copy.deepcopy(self[key])
1441
1443
  elif self[key] != self._orig_data[key]:
1442
1444
  # Value was changed
1443
- output[key] = self[key]
1445
+ output[key] = copy.deepcopy(self[key])
1444
1446
  return output
1445
1447
 
1446
1448
  def get_new_entity_value(self) -> dict[str, AttributeValueType]:
@@ -1460,7 +1462,25 @@ class EntityData(dict):
1460
1462
  def lock(self) -> None:
1461
1463
  """Lock changes of entity data."""
1462
1464
 
1463
- self._orig_data = copy.deepcopy(self)
1465
+ orig_data = {}
1466
+ for key, value in self.items():
1467
+ try:
1468
+ key = copy.deepcopy(key)
1469
+ except RecursionError:
1470
+ raise RuntimeError(
1471
+ f"Failed to create copy of key '{key}'"
1472
+ " because of recursion."
1473
+ )
1474
+
1475
+ try:
1476
+ orig_data[key] = copy.deepcopy(value)
1477
+ except RecursionError:
1478
+ raise RuntimeError(
1479
+ f"Failed to create copy of value '{key}'"
1480
+ " because of recursion."
1481
+ )
1482
+
1483
+ self._orig_data = orig_data
1464
1484
 
1465
1485
 
1466
1486
  class BaseEntity(ABC):
@@ -1562,6 +1582,8 @@ class BaseEntity(ABC):
1562
1582
  self._tags = copy.deepcopy(tags)
1563
1583
  self._thumbnail_id = thumbnail_id
1564
1584
 
1585
+ if name == label:
1586
+ label = None
1565
1587
  self._orig_name = name
1566
1588
  self._orig_label = label
1567
1589
  self._orig_status = status
@@ -1329,7 +1329,7 @@ class ServerAPI(
1329
1329
  def _endpoint_to_url(
1330
1330
  self,
1331
1331
  endpoint: str,
1332
- use_rest: Optional[bool] = True
1332
+ use_rest: bool = True,
1333
1333
  ) -> str:
1334
1334
  """Cleanup endpoint and return full url to AYON server.
1335
1335
 
@@ -1347,25 +1347,53 @@ class ServerAPI(
1347
1347
  endpoint = endpoint.lstrip("/").rstrip("/")
1348
1348
  if endpoint.startswith(self._base_url):
1349
1349
  return endpoint
1350
- base_url = self._rest_url if use_rest else self._graphql_url
1350
+ base_url = self._rest_url if use_rest else self._base_url
1351
1351
  return f"{base_url}/{endpoint}"
1352
1352
 
1353
1353
  def _download_file_to_stream(
1354
- self, url: str, stream, chunk_size, progress
1354
+ self,
1355
+ url: str,
1356
+ stream: StreamType,
1357
+ chunk_size: int,
1358
+ progress: TransferProgress,
1355
1359
  ):
1356
- kwargs = {"stream": True}
1360
+ headers = self.get_headers()
1361
+ kwargs = {
1362
+ "stream": True,
1363
+ "headers": headers,
1364
+ }
1357
1365
  if self._session is None:
1358
- kwargs["headers"] = self.get_headers()
1359
1366
  get_func = self._base_functions_mapping[RequestTypes.get]
1360
1367
  else:
1361
1368
  get_func = self._session_functions_mapping[RequestTypes.get]
1362
1369
 
1363
- with get_func(url, **kwargs) as response:
1364
- response.raise_for_status()
1365
- progress.set_content_size(response.headers["Content-length"])
1366
- for chunk in response.iter_content(chunk_size=chunk_size):
1367
- stream.write(chunk)
1368
- progress.add_transferred_chunk(len(chunk))
1370
+ retries = self.get_default_max_retries()
1371
+ for attempt in range(retries):
1372
+ # Continue in download
1373
+ offset = progress.get_transferred_size()
1374
+ if offset > 0:
1375
+ headers["Range"] = f"bytes={offset}-"
1376
+
1377
+ try:
1378
+ with get_func(url, **kwargs) as response:
1379
+ response.raise_for_status()
1380
+ if progress.get_content_size() is None:
1381
+ progress.set_content_size(
1382
+ response.headers["Content-length"]
1383
+ )
1384
+
1385
+ for chunk in response.iter_content(chunk_size=chunk_size):
1386
+ stream.write(chunk)
1387
+ progress.add_transferred_chunk(len(chunk))
1388
+ break
1389
+
1390
+ except (
1391
+ requests.exceptions.Timeout,
1392
+ requests.exceptions.ConnectionError,
1393
+ ):
1394
+ if attempt == retries:
1395
+ raise
1396
+ progress.next_attempt()
1369
1397
 
1370
1398
  def download_file_to_stream(
1371
1399
  self,
@@ -1399,7 +1427,7 @@ class ServerAPI(
1399
1427
  if not chunk_size:
1400
1428
  chunk_size = self.default_download_chunk_size
1401
1429
 
1402
- url = self._endpoint_to_url(endpoint)
1430
+ url = self._endpoint_to_url(endpoint, use_rest=False)
1403
1431
 
1404
1432
  if progress is None:
1405
1433
  progress = TransferProgress()
@@ -1470,6 +1498,76 @@ class ServerAPI(
1470
1498
 
1471
1499
  return progress
1472
1500
 
1501
+ def download_project_file(
1502
+ self,
1503
+ project_name: str,
1504
+ file_id: str,
1505
+ filepath: str,
1506
+ *,
1507
+ chunk_size: Optional[int] = None,
1508
+ progress: Optional[TransferProgress] = None,
1509
+ ) -> TransferProgress:
1510
+ """Download project file to filepath.
1511
+
1512
+ Project files are usually binary files, such as images, videos,
1513
+ or other media files that can be accessed via api endpoint
1514
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1515
+
1516
+ Args:
1517
+ project_name (str): Project name.
1518
+ file_id (str): File id.
1519
+ filepath (str): Path where file will be downloaded.
1520
+ chunk_size (Optional[int]): Size of chunks that are received
1521
+ in single loop.
1522
+ progress (Optional[TransferProgress]): Object that gives ability
1523
+ to track download progress.
1524
+
1525
+ Returns:
1526
+ TransferProgress: Progress object.
1527
+
1528
+ """
1529
+ return self.download_file(
1530
+ f"api/projects/{project_name}/files/{file_id}",
1531
+ filepath,
1532
+ chunk_size=chunk_size,
1533
+ progress=progress,
1534
+ )
1535
+
1536
+ def download_project_file_to_stream(
1537
+ self,
1538
+ project_name: str,
1539
+ file_id: str,
1540
+ stream: StreamType,
1541
+ *,
1542
+ chunk_size: Optional[int] = None,
1543
+ progress: Optional[TransferProgress] = None,
1544
+ ) -> TransferProgress:
1545
+ """Download project file to a stream.
1546
+
1547
+ Project files are usually binary files, such as images, videos,
1548
+ or other media files that can be accessed via api endpoint
1549
+ '{server url}/api/projects/{project_name}/files/{file_id}'.
1550
+
1551
+ Args:
1552
+ project_name (str): Project name.
1553
+ file_id (str): File id.
1554
+ stream (StreamType): Stream where output will be stored.
1555
+ chunk_size (Optional[int]): Size of chunks that are received
1556
+ in single loop.
1557
+ progress (Optional[TransferProgress]): Object that gives ability
1558
+ to track download progress.
1559
+
1560
+ Returns:
1561
+ TransferProgress: Progress object.
1562
+
1563
+ """
1564
+ return self.download_file_to_stream(
1565
+ f"api/projects/{project_name}/files/{file_id}",
1566
+ stream,
1567
+ chunk_size=chunk_size,
1568
+ progress=progress,
1569
+ )
1570
+
1473
1571
  @staticmethod
1474
1572
  def _upload_chunks_iter(
1475
1573
  file_stream: StreamType,
@@ -1543,11 +1641,27 @@ class ServerAPI(
1543
1641
  if not chunk_size:
1544
1642
  chunk_size = self.default_upload_chunk_size
1545
1643
 
1546
- response = post_func(
1547
- url,
1548
- data=self._upload_chunks_iter(stream, progress, chunk_size),
1549
- **kwargs
1550
- )
1644
+ retries = self.get_default_max_retries()
1645
+ response = None
1646
+ for attempt in range(retries):
1647
+ try:
1648
+ response = post_func(
1649
+ url,
1650
+ data=self._upload_chunks_iter(
1651
+ stream, progress, chunk_size
1652
+ ),
1653
+ **kwargs
1654
+ )
1655
+ break
1656
+
1657
+ except (
1658
+ requests.exceptions.Timeout,
1659
+ requests.exceptions.ConnectionError,
1660
+ ):
1661
+ if attempt == retries:
1662
+ raise
1663
+ progress.next_attempt()
1664
+ progress.reset_transferred()
1551
1665
 
1552
1666
  response.raise_for_status()
1553
1667
  return response
@@ -1842,6 +1956,8 @@ class ServerAPI(
1842
1956
 
1843
1957
  elif entity_type == "entityList":
1844
1958
  entity_type_defaults = set(DEFAULT_ENTITY_LIST_FIELDS)
1959
+ # Attributes scope is 'list'
1960
+ entity_type = "list"
1845
1961
 
1846
1962
  else:
1847
1963
  raise ValueError(f"Unknown entity type \"{entity_type}\"")
@@ -796,6 +796,7 @@ class TransferProgress:
796
796
  """Object to store progress of download/upload from/to server."""
797
797
 
798
798
  def __init__(self):
799
+ self._attempt: int = 0
799
800
  self._started: bool = False
800
801
  self._transfer_done: bool = False
801
802
  self._transferred: int = 0
@@ -850,6 +851,17 @@ class TransferProgress:
850
851
  if self._started:
851
852
  raise ValueError("Progress already started")
852
853
  self._started = True
854
+ self._attempt = 1
855
+
856
+ def get_attempt(self) -> int:
857
+ """Find out which attempt of progress it is."""
858
+ return self._attempt
859
+
860
+ def next_attempt(self) -> None:
861
+ """Start new attempt of progress."""
862
+ if not self._started:
863
+ raise ValueError("Progress did not start yet")
864
+ self._attempt += 1
853
865
 
854
866
  def get_transfer_done(self) -> bool:
855
867
  """Transfer finished.
@@ -921,6 +933,10 @@ class TransferProgress:
921
933
  """
922
934
  self._transferred = transferred
923
935
 
936
+ def reset_transferred(self) -> None:
937
+ """Reset transferred size to initial value."""
938
+ self._transferred = 0
939
+
924
940
  def add_transferred_chunk(self, chunk_size: int):
925
941
  """Add transferred chunk size in bytes.
926
942
 
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for AYON server."""
2
- __version__ = "1.2.6"
2
+ __version__ = "1.2.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ayon_python_api
3
- Version: 1.2.6
3
+ Version: 1.2.8
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ayon_python_api"
3
- version = "1.2.6"
3
+ version = "1.2.8"
4
4
  description = "AYON Python API"
5
5
  license = {file = "LICENSE"}
6
6
  readme = {file = "README.md", content-type = "text/markdown"}
File without changes