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.
- {ayon_python_api-1.2.6/ayon_python_api.egg-info → ayon_python_api-1.2.8}/PKG-INFO +1 -1
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/__init__.py +4 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api.py +76 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/lists.py +44 -5
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/constants.py +1 -4
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/entity_hub.py +26 -4
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/server_api.py +133 -17
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/utils.py +16 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/version.py +1 -1
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8/ayon_python_api.egg-info}/PKG-INFO +1 -1
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/pyproject.toml +1 -1
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/LICENSE +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/README.md +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/__init__.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/actions.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/activities.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/attributes.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/base.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/bundles_addons.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/dependency_packages.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/events.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/folders.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/installers.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/links.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/products.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/projects.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/representations.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/secrets.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/tasks.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/thumbnails.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/versions.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/workfiles.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/events.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/exceptions.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/graphql.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/graphql_queries.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/operations.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/typing.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/SOURCES.txt +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/dependency_links.txt +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/requires.txt +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/top_level.txt +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/setup.cfg +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/setup.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_entity_hub.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_folder_hierarchy.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_get_events.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_graphql_queries.py +0 -0
- {ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/tests/test_server.py +0 -0
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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 =
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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,
|
|
1354
|
+
self,
|
|
1355
|
+
url: str,
|
|
1356
|
+
stream: StreamType,
|
|
1357
|
+
chunk_size: int,
|
|
1358
|
+
progress: TransferProgress,
|
|
1355
1359
|
):
|
|
1356
|
-
|
|
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
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
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
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
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.
|
|
2
|
+
__version__ = "1.2.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_api/_api_helpers/dependency_packages.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ayon_python_api-1.2.6 → ayon_python_api-1.2.8}/ayon_python_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|