ayon-python-api 1.2.4__tar.gz → 1.2.5__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.4 → ayon_python_api-1.2.5}/PKG-INFO +7 -2
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/__init__.py +4 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api.py +41 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/base.py +9 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/products.py +35 -3
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/projects.py +165 -39
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/constants.py +7 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/entity_hub.py +44 -1
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/graphql_queries.py +3 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/operations.py +47 -18
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/server_api.py +13 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/typing.py +8 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/version.py +1 -1
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_python_api.egg-info/PKG-INFO +8 -3
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_python_api.egg-info/SOURCES.txt +6 -1
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/pyproject.toml +1 -1
- ayon_python_api-1.2.5/tests/test_entity_hub.py +1289 -0
- ayon_python_api-1.2.5/tests/test_folder_hierarchy.py +545 -0
- ayon_python_api-1.2.5/tests/test_get_events.py +159 -0
- ayon_python_api-1.2.5/tests/test_graphql_queries.py +127 -0
- ayon_python_api-1.2.5/tests/test_server.py +940 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/LICENSE +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/README.md +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/__init__.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/actions.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/activities.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/attributes.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/bundles_addons.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/dependency_packages.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/events.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/folders.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/installers.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/links.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/lists.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/representations.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/secrets.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/tasks.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/thumbnails.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/versions.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/_api_helpers/workfiles.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/events.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/exceptions.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/graphql.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_api/utils.py +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_python_api.egg-info/dependency_links.txt +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_python_api.egg-info/requires.txt +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/ayon_python_api.egg-info/top_level.txt +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/setup.cfg +0 -0
- {ayon_python_api-1.2.4 → ayon_python_api-1.2.5}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ayon_python_api
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.5
|
|
4
4
|
Summary: AYON Python API
|
|
5
5
|
Home-page: https://github.com/ynput/ayon-python-api
|
|
6
6
|
Author: ynput.io
|
|
@@ -215,6 +215,11 @@ Classifier: Programming Language :: Python
|
|
|
215
215
|
Classifier: Programming Language :: Python :: 3
|
|
216
216
|
Description-Content-Type: text/markdown
|
|
217
217
|
License-File: LICENSE
|
|
218
|
+
Requires-Dist: requests>=2.27.1
|
|
219
|
+
Requires-Dist: Unidecode>=1.3.0
|
|
220
|
+
Dynamic: author
|
|
221
|
+
Dynamic: home-page
|
|
222
|
+
Dynamic: license-file
|
|
218
223
|
|
|
219
224
|
# AYON server API
|
|
220
225
|
Python client for connection server. The client is using REST and GraphQl to communicate with server with `requests` module.
|
|
@@ -54,6 +54,7 @@ from ._api import (
|
|
|
54
54
|
get_info,
|
|
55
55
|
get_server_version,
|
|
56
56
|
get_server_version_tuple,
|
|
57
|
+
is_product_base_type_supported,
|
|
57
58
|
get_users,
|
|
58
59
|
get_user_by_name,
|
|
59
60
|
get_user,
|
|
@@ -152,6 +153,7 @@ from ._api import (
|
|
|
152
153
|
get_build_in_anatomy_preset,
|
|
153
154
|
get_rest_project,
|
|
154
155
|
get_rest_projects,
|
|
156
|
+
get_rest_projects_list,
|
|
155
157
|
get_project_names,
|
|
156
158
|
get_projects,
|
|
157
159
|
get_project,
|
|
@@ -331,6 +333,7 @@ __all__ = (
|
|
|
331
333
|
"get_info",
|
|
332
334
|
"get_server_version",
|
|
333
335
|
"get_server_version_tuple",
|
|
336
|
+
"is_product_base_type_supported",
|
|
334
337
|
"get_users",
|
|
335
338
|
"get_user_by_name",
|
|
336
339
|
"get_user",
|
|
@@ -429,6 +432,7 @@ __all__ = (
|
|
|
429
432
|
"get_build_in_anatomy_preset",
|
|
430
433
|
"get_rest_project",
|
|
431
434
|
"get_rest_projects",
|
|
435
|
+
"get_rest_projects_list",
|
|
432
436
|
"get_project_names",
|
|
433
437
|
"get_projects",
|
|
434
438
|
"get_project",
|
|
@@ -64,6 +64,7 @@ if typing.TYPE_CHECKING:
|
|
|
64
64
|
BundlesInfoDict,
|
|
65
65
|
AnatomyPresetDict,
|
|
66
66
|
SecretDict,
|
|
67
|
+
ProjectListDict,
|
|
67
68
|
AnyEntityDict,
|
|
68
69
|
ProjectDict,
|
|
69
70
|
FolderDict,
|
|
@@ -720,6 +721,13 @@ def get_server_version_tuple() -> ServerVersion:
|
|
|
720
721
|
return con.get_server_version_tuple()
|
|
721
722
|
|
|
722
723
|
|
|
724
|
+
def is_product_base_type_supported() -> bool:
|
|
725
|
+
"""Product base types are available on server.
|
|
726
|
+
"""
|
|
727
|
+
con = get_server_api_connection()
|
|
728
|
+
return con.is_product_base_type_supported()
|
|
729
|
+
|
|
730
|
+
|
|
723
731
|
def get_users(
|
|
724
732
|
project_name: Optional[str] = None,
|
|
725
733
|
usernames: Optional[Iterable[str]] = None,
|
|
@@ -3573,6 +3581,31 @@ def get_rest_projects(
|
|
|
3573
3581
|
)
|
|
3574
3582
|
|
|
3575
3583
|
|
|
3584
|
+
def get_rest_projects_list(
|
|
3585
|
+
active: Optional[bool] = True,
|
|
3586
|
+
library: Optional[bool] = None,
|
|
3587
|
+
) -> list[ProjectListDict]:
|
|
3588
|
+
"""Receive available projects.
|
|
3589
|
+
|
|
3590
|
+
User must be logged in.
|
|
3591
|
+
|
|
3592
|
+
Args:
|
|
3593
|
+
active (Optional[bool]): Filter active/inactive projects. Both
|
|
3594
|
+
are returned if 'None' is passed.
|
|
3595
|
+
library (Optional[bool]): Filter standard/library projects. Both
|
|
3596
|
+
are returned if 'None' is passed.
|
|
3597
|
+
|
|
3598
|
+
Returns:
|
|
3599
|
+
list[ProjectListDict]: List of available projects.
|
|
3600
|
+
|
|
3601
|
+
"""
|
|
3602
|
+
con = get_server_api_connection()
|
|
3603
|
+
return con.get_rest_projects_list(
|
|
3604
|
+
active=active,
|
|
3605
|
+
library=library,
|
|
3606
|
+
)
|
|
3607
|
+
|
|
3608
|
+
|
|
3576
3609
|
def get_project_names(
|
|
3577
3610
|
active: Optional[bool] = True,
|
|
3578
3611
|
library: Optional[bool] = None,
|
|
@@ -4874,6 +4907,7 @@ def get_products(
|
|
|
4874
4907
|
product_names: Optional[Iterable[str]] = None,
|
|
4875
4908
|
folder_ids: Optional[Iterable[str]] = None,
|
|
4876
4909
|
product_types: Optional[Iterable[str]] = None,
|
|
4910
|
+
product_base_types: Optional[Iterable[str]] = None,
|
|
4877
4911
|
product_name_regex: Optional[str] = None,
|
|
4878
4912
|
product_path_regex: Optional[str] = None,
|
|
4879
4913
|
names_by_folder_ids: Optional[dict[str, Iterable[str]]] = None,
|
|
@@ -4926,6 +4960,7 @@ def get_products(
|
|
|
4926
4960
|
product_names=product_names,
|
|
4927
4961
|
folder_ids=folder_ids,
|
|
4928
4962
|
product_types=product_types,
|
|
4963
|
+
product_base_types=product_base_types,
|
|
4929
4964
|
product_name_regex=product_name_regex,
|
|
4930
4965
|
product_path_regex=product_path_regex,
|
|
4931
4966
|
names_by_folder_ids=names_by_folder_ids,
|
|
@@ -5084,6 +5119,7 @@ def create_product(
|
|
|
5084
5119
|
tags: Optional[Iterable[str]] = None,
|
|
5085
5120
|
status: Optional[str] = None,
|
|
5086
5121
|
active: Optional[bool] = None,
|
|
5122
|
+
product_base_type: Optional[str] = None,
|
|
5087
5123
|
product_id: Optional[str] = None,
|
|
5088
5124
|
) -> str:
|
|
5089
5125
|
"""Create new product.
|
|
@@ -5098,6 +5134,7 @@ def create_product(
|
|
|
5098
5134
|
tags (Optional[Iterable[str]]): Product tags.
|
|
5099
5135
|
status (Optional[str]): Product status.
|
|
5100
5136
|
active (Optional[bool]): Product active state.
|
|
5137
|
+
product_base_type (Optional[str]): Product base type.
|
|
5101
5138
|
product_id (Optional[str]): Product id. If not passed new id is
|
|
5102
5139
|
generated.
|
|
5103
5140
|
|
|
@@ -5116,6 +5153,7 @@ def create_product(
|
|
|
5116
5153
|
tags=tags,
|
|
5117
5154
|
status=status,
|
|
5118
5155
|
active=active,
|
|
5156
|
+
product_base_type=product_base_type,
|
|
5119
5157
|
product_id=product_id,
|
|
5120
5158
|
)
|
|
5121
5159
|
|
|
@@ -5126,6 +5164,7 @@ def update_product(
|
|
|
5126
5164
|
name: Optional[str] = None,
|
|
5127
5165
|
folder_id: Optional[str] = None,
|
|
5128
5166
|
product_type: Optional[str] = None,
|
|
5167
|
+
product_base_type: Optional[str] = None,
|
|
5129
5168
|
attrib: Optional[dict[str, Any]] = None,
|
|
5130
5169
|
data: Optional[dict[str, Any]] = None,
|
|
5131
5170
|
tags: Optional[Iterable[str]] = None,
|
|
@@ -5145,6 +5184,7 @@ def update_product(
|
|
|
5145
5184
|
name (Optional[str]): New product name.
|
|
5146
5185
|
folder_id (Optional[str]): New product id.
|
|
5147
5186
|
product_type (Optional[str]): New product type.
|
|
5187
|
+
product_base_type (Optional[str]): New product base type.
|
|
5148
5188
|
attrib (Optional[dict[str, Any]]): New product attributes.
|
|
5149
5189
|
data (Optional[dict[str, Any]]): New product data.
|
|
5150
5190
|
tags (Optional[Iterable[str]]): New product tags.
|
|
@@ -5159,6 +5199,7 @@ def update_product(
|
|
|
5159
5199
|
name=name,
|
|
5160
5200
|
folder_id=folder_id,
|
|
5161
5201
|
product_type=product_type,
|
|
5202
|
+
product_base_type=product_base_type,
|
|
5162
5203
|
attrib=attrib,
|
|
5163
5204
|
data=data,
|
|
5164
5205
|
tags=tags,
|
|
@@ -14,6 +14,7 @@ if typing.TYPE_CHECKING:
|
|
|
14
14
|
ServerVersion,
|
|
15
15
|
ProjectDict,
|
|
16
16
|
StreamType,
|
|
17
|
+
AttributeScope,
|
|
17
18
|
)
|
|
18
19
|
|
|
19
20
|
_PLACEHOLDER = object()
|
|
@@ -24,6 +25,9 @@ class BaseServerAPI:
|
|
|
24
25
|
def log(self) -> logging.Logger:
|
|
25
26
|
raise NotImplementedError()
|
|
26
27
|
|
|
28
|
+
def is_product_base_type_supported(self) -> bool:
|
|
29
|
+
raise NotImplementedError()
|
|
30
|
+
|
|
27
31
|
def get_server_version(self) -> str:
|
|
28
32
|
raise NotImplementedError()
|
|
29
33
|
|
|
@@ -125,6 +129,11 @@ class BaseServerAPI:
|
|
|
125
129
|
) -> Optional[dict[str, Any]]:
|
|
126
130
|
raise NotImplementedError()
|
|
127
131
|
|
|
132
|
+
def get_attributes_fields_for_type(
|
|
133
|
+
self, entity_type: AttributeScope
|
|
134
|
+
) -> set[str]:
|
|
135
|
+
raise NotImplementedError()
|
|
136
|
+
|
|
128
137
|
def _prepare_fields(
|
|
129
138
|
self,
|
|
130
139
|
entity_type: str,
|
|
@@ -5,6 +5,7 @@ import warnings
|
|
|
5
5
|
import typing
|
|
6
6
|
from typing import Optional, Iterable, Generator, Any
|
|
7
7
|
|
|
8
|
+
from ayon_api.exceptions import UnsupportedServerVersion
|
|
8
9
|
from ayon_api.utils import (
|
|
9
10
|
prepare_list_filters,
|
|
10
11
|
create_entity_id,
|
|
@@ -32,9 +33,10 @@ class ProductsAPI(BaseServerAPI):
|
|
|
32
33
|
self,
|
|
33
34
|
project_name: str,
|
|
34
35
|
product_ids: Optional[Iterable[str]] = None,
|
|
35
|
-
product_names: Optional[Iterable[str]]=None,
|
|
36
|
-
folder_ids: Optional[Iterable[str]]=None,
|
|
37
|
-
product_types: Optional[Iterable[str]]=None,
|
|
36
|
+
product_names: Optional[Iterable[str]] = None,
|
|
37
|
+
folder_ids: Optional[Iterable[str]] = None,
|
|
38
|
+
product_types: Optional[Iterable[str]] = None,
|
|
39
|
+
product_base_types: Optional[Iterable[str]] = None,
|
|
38
40
|
product_name_regex: Optional[str] = None,
|
|
39
41
|
product_path_regex: Optional[str] = None,
|
|
40
42
|
names_by_folder_ids: Optional[dict[str, Iterable[str]]] = None,
|
|
@@ -59,6 +61,8 @@ class ProductsAPI(BaseServerAPI):
|
|
|
59
61
|
Use 'None' if folder is direct child of project.
|
|
60
62
|
product_types (Optional[Iterable[str]]): Product types used for
|
|
61
63
|
filtering.
|
|
64
|
+
product_base_types (Optional[Iterable[str]]): Product base types
|
|
65
|
+
used for filtering.
|
|
62
66
|
product_name_regex (Optional[str]): Filter products by name regex.
|
|
63
67
|
product_path_regex (Optional[str]): Filter products by path regex.
|
|
64
68
|
Path starts with folder path and ends with product name.
|
|
@@ -83,6 +87,11 @@ class ProductsAPI(BaseServerAPI):
|
|
|
83
87
|
if not project_name:
|
|
84
88
|
return
|
|
85
89
|
|
|
90
|
+
if product_base_types and not self.is_product_base_type_supported():
|
|
91
|
+
raise UnsupportedServerVersion(
|
|
92
|
+
"Product base type is not supported for your server version."
|
|
93
|
+
)
|
|
94
|
+
|
|
86
95
|
# Prepare these filters before 'name_by_filter_ids' filter
|
|
87
96
|
filter_product_names = None
|
|
88
97
|
if product_names is not None:
|
|
@@ -150,6 +159,7 @@ class ProductsAPI(BaseServerAPI):
|
|
|
150
159
|
filters,
|
|
151
160
|
("productIds", product_ids),
|
|
152
161
|
("productTypes", product_types),
|
|
162
|
+
("productBaseTypes", product_base_types),
|
|
153
163
|
("productStatuses", statuses),
|
|
154
164
|
("productTags", tags),
|
|
155
165
|
):
|
|
@@ -378,6 +388,7 @@ class ProductsAPI(BaseServerAPI):
|
|
|
378
388
|
tags: Optional[Iterable[str]] =None,
|
|
379
389
|
status: Optional[str] = None,
|
|
380
390
|
active: Optional[bool] = None,
|
|
391
|
+
product_base_type: Optional[str] = None,
|
|
381
392
|
product_id: Optional[str] = None,
|
|
382
393
|
) -> str:
|
|
383
394
|
"""Create new product.
|
|
@@ -392,6 +403,7 @@ class ProductsAPI(BaseServerAPI):
|
|
|
392
403
|
tags (Optional[Iterable[str]]): Product tags.
|
|
393
404
|
status (Optional[str]): Product status.
|
|
394
405
|
active (Optional[bool]): Product active state.
|
|
406
|
+
product_base_type (Optional[str]): Product base type.
|
|
395
407
|
product_id (Optional[str]): Product id. If not passed new id is
|
|
396
408
|
generated.
|
|
397
409
|
|
|
@@ -399,6 +411,14 @@ class ProductsAPI(BaseServerAPI):
|
|
|
399
411
|
str: Product id.
|
|
400
412
|
|
|
401
413
|
"""
|
|
414
|
+
if (
|
|
415
|
+
product_base_type is not None
|
|
416
|
+
and not self.is_product_base_type_supported()
|
|
417
|
+
):
|
|
418
|
+
raise UnsupportedServerVersion(
|
|
419
|
+
"Product base type is not supported for your server version."
|
|
420
|
+
)
|
|
421
|
+
|
|
402
422
|
if not product_id:
|
|
403
423
|
product_id = create_entity_id()
|
|
404
424
|
create_data = {
|
|
@@ -408,6 +428,7 @@ class ProductsAPI(BaseServerAPI):
|
|
|
408
428
|
"folderId": folder_id,
|
|
409
429
|
}
|
|
410
430
|
for key, value in (
|
|
431
|
+
("productBaseType", product_base_type),
|
|
411
432
|
("attrib", attrib),
|
|
412
433
|
("data", data),
|
|
413
434
|
("tags", tags),
|
|
@@ -431,6 +452,7 @@ class ProductsAPI(BaseServerAPI):
|
|
|
431
452
|
name: Optional[str] = None,
|
|
432
453
|
folder_id: Optional[str] = None,
|
|
433
454
|
product_type: Optional[str] = None,
|
|
455
|
+
product_base_type: Optional[str] = None,
|
|
434
456
|
attrib: Optional[dict[str, Any]] = None,
|
|
435
457
|
data: Optional[dict[str, Any]] = None,
|
|
436
458
|
tags: Optional[Iterable[str]] = None,
|
|
@@ -450,6 +472,7 @@ class ProductsAPI(BaseServerAPI):
|
|
|
450
472
|
name (Optional[str]): New product name.
|
|
451
473
|
folder_id (Optional[str]): New product id.
|
|
452
474
|
product_type (Optional[str]): New product type.
|
|
475
|
+
product_base_type (Optional[str]): New product base type.
|
|
453
476
|
attrib (Optional[dict[str, Any]]): New product attributes.
|
|
454
477
|
data (Optional[dict[str, Any]]): New product data.
|
|
455
478
|
tags (Optional[Iterable[str]]): New product tags.
|
|
@@ -457,10 +480,19 @@ class ProductsAPI(BaseServerAPI):
|
|
|
457
480
|
active (Optional[bool]): New product active state.
|
|
458
481
|
|
|
459
482
|
"""
|
|
483
|
+
if (
|
|
484
|
+
product_base_type is not None
|
|
485
|
+
and not self.is_product_base_type_supported()
|
|
486
|
+
):
|
|
487
|
+
raise UnsupportedServerVersion(
|
|
488
|
+
"Product base type is not supported for your server version."
|
|
489
|
+
)
|
|
490
|
+
|
|
460
491
|
update_data = {}
|
|
461
492
|
for key, value in (
|
|
462
493
|
("name", name),
|
|
463
494
|
("productType", product_type),
|
|
495
|
+
("productBaseType", product_base_type),
|
|
464
496
|
("folderId", folder_id),
|
|
465
497
|
("attrib", attrib),
|
|
466
498
|
("data", data),
|
|
@@ -3,17 +3,50 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
import platform
|
|
5
5
|
import warnings
|
|
6
|
+
from enum import Enum
|
|
6
7
|
import typing
|
|
7
8
|
from typing import Optional, Generator, Iterable, Any
|
|
8
9
|
|
|
9
|
-
from ayon_api.constants import
|
|
10
|
+
from ayon_api.constants import (
|
|
11
|
+
PROJECT_NAME_REGEX,
|
|
12
|
+
DEFAULT_PRODUCT_BASE_TYPE_FIELDS,
|
|
13
|
+
DEFAULT_PRODUCT_TYPE_FIELDS,
|
|
14
|
+
)
|
|
10
15
|
from ayon_api.utils import prepare_query_string, fill_own_attribs
|
|
11
16
|
from ayon_api.graphql_queries import projects_graphql_query
|
|
12
17
|
|
|
13
18
|
from .base import BaseServerAPI
|
|
14
19
|
|
|
15
20
|
if typing.TYPE_CHECKING:
|
|
16
|
-
from ayon_api.typing import
|
|
21
|
+
from ayon_api.typing import (
|
|
22
|
+
ProjectDict,
|
|
23
|
+
AnatomyPresetDict,
|
|
24
|
+
ProjectListDict,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ProjectFetchType(Enum):
|
|
29
|
+
"""How a project has to be fetched to get all requested data.
|
|
30
|
+
|
|
31
|
+
Some project data can be received only from GraphQl, and some can be
|
|
32
|
+
received only with REST. That is based on requested fields.
|
|
33
|
+
|
|
34
|
+
There is also a dedicated endpoint to get information about all projects
|
|
35
|
+
but returns very limited information about the project.
|
|
36
|
+
|
|
37
|
+
Enums:
|
|
38
|
+
GraphQl: Requested project data can be received with GraphQl.
|
|
39
|
+
REST: Requested project data can be received with /projects/{project}.
|
|
40
|
+
RESTList: Requested project data can be received with /projects.
|
|
41
|
+
Can be considered as a subset of 'REST'.
|
|
42
|
+
GraphQlAndREST: It is necessary to use GraphQl and REST to get all
|
|
43
|
+
requested data.
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
GraphQl = "GraphQl"
|
|
47
|
+
REST = "REST"
|
|
48
|
+
RESTList = "RESTList"
|
|
49
|
+
GraphQlAndREST = "GraphQlAndREST"
|
|
17
50
|
|
|
18
51
|
|
|
19
52
|
class ProjectsAPI(BaseServerAPI):
|
|
@@ -156,12 +189,12 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
156
189
|
if project:
|
|
157
190
|
yield project
|
|
158
191
|
|
|
159
|
-
def
|
|
192
|
+
def get_rest_projects_list(
|
|
160
193
|
self,
|
|
161
194
|
active: Optional[bool] = True,
|
|
162
195
|
library: Optional[bool] = None,
|
|
163
|
-
) -> list[
|
|
164
|
-
"""Receive available
|
|
196
|
+
) -> list[ProjectListDict]:
|
|
197
|
+
"""Receive available projects.
|
|
165
198
|
|
|
166
199
|
User must be logged in.
|
|
167
200
|
|
|
@@ -172,7 +205,7 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
172
205
|
are returned if 'None' is passed.
|
|
173
206
|
|
|
174
207
|
Returns:
|
|
175
|
-
list[
|
|
208
|
+
list[ProjectListDict]: List of available projects.
|
|
176
209
|
|
|
177
210
|
"""
|
|
178
211
|
if active is not None:
|
|
@@ -181,16 +214,38 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
181
214
|
if library is not None:
|
|
182
215
|
library = "true" if library else "false"
|
|
183
216
|
|
|
184
|
-
query = prepare_query_string({
|
|
185
|
-
|
|
217
|
+
query = prepare_query_string({
|
|
218
|
+
"active": active,
|
|
219
|
+
"library": library,
|
|
220
|
+
})
|
|
186
221
|
response = self.get(f"projects{query}")
|
|
187
222
|
response.raise_for_status()
|
|
188
223
|
data = response.data
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
224
|
+
return data["projects"]
|
|
225
|
+
|
|
226
|
+
def get_project_names(
|
|
227
|
+
self,
|
|
228
|
+
active: Optional[bool] = True,
|
|
229
|
+
library: Optional[bool] = None,
|
|
230
|
+
) -> list[str]:
|
|
231
|
+
"""Receive available project names.
|
|
232
|
+
|
|
233
|
+
User must be logged in.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
active (Optional[bool]): Filter active/inactive projects. Both
|
|
237
|
+
are returned if 'None' is passed.
|
|
238
|
+
library (Optional[bool]): Filter standard/library projects. Both
|
|
239
|
+
are returned if 'None' is passed.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
list[str]: List of available project names.
|
|
243
|
+
|
|
244
|
+
"""
|
|
245
|
+
return [
|
|
246
|
+
project["name"]
|
|
247
|
+
for project in self.get_rest_projects_list(active, library)
|
|
248
|
+
]
|
|
194
249
|
|
|
195
250
|
def get_projects(
|
|
196
251
|
self,
|
|
@@ -218,7 +273,11 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
218
273
|
if fields is not None:
|
|
219
274
|
fields = set(fields)
|
|
220
275
|
|
|
221
|
-
graphql_fields,
|
|
276
|
+
graphql_fields, fetch_type = self._get_project_graphql_fields(fields)
|
|
277
|
+
if fetch_type == ProjectFetchType.RESTList:
|
|
278
|
+
yield from self.get_rest_projects_list(active, library)
|
|
279
|
+
return
|
|
280
|
+
|
|
222
281
|
projects_by_name = {}
|
|
223
282
|
if graphql_fields:
|
|
224
283
|
projects = list(self._get_graphql_projects(
|
|
@@ -227,7 +286,7 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
227
286
|
fields=graphql_fields,
|
|
228
287
|
own_attributes=own_attributes,
|
|
229
288
|
))
|
|
230
|
-
if
|
|
289
|
+
if fetch_type == ProjectFetchType.GraphQl:
|
|
231
290
|
yield from projects
|
|
232
291
|
return
|
|
233
292
|
projects_by_name = {p["name"]: p for p in projects}
|
|
@@ -236,7 +295,12 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
236
295
|
name = project["name"]
|
|
237
296
|
graphql_p = projects_by_name.get(name)
|
|
238
297
|
if graphql_p:
|
|
239
|
-
|
|
298
|
+
for key in (
|
|
299
|
+
"productTypes",
|
|
300
|
+
"usedTags",
|
|
301
|
+
):
|
|
302
|
+
if key in graphql_p:
|
|
303
|
+
project[key] = graphql_p[key]
|
|
240
304
|
yield project
|
|
241
305
|
|
|
242
306
|
def get_project(
|
|
@@ -262,7 +326,7 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
262
326
|
if fields is not None:
|
|
263
327
|
fields = set(fields)
|
|
264
328
|
|
|
265
|
-
graphql_fields,
|
|
329
|
+
graphql_fields, fetch_type = self._get_project_graphql_fields(fields)
|
|
266
330
|
graphql_project = None
|
|
267
331
|
if graphql_fields:
|
|
268
332
|
graphql_project = next(self._get_graphql_projects(
|
|
@@ -271,14 +335,19 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
271
335
|
fields=graphql_fields,
|
|
272
336
|
own_attributes=own_attributes,
|
|
273
337
|
), None)
|
|
274
|
-
if not graphql_project or
|
|
338
|
+
if not graphql_project or fetch_type == fetch_type.GraphQl:
|
|
275
339
|
return graphql_project
|
|
276
340
|
|
|
277
341
|
project = self.get_rest_project(project_name)
|
|
278
342
|
if own_attributes:
|
|
279
343
|
fill_own_attribs(project)
|
|
280
344
|
if graphql_project:
|
|
281
|
-
|
|
345
|
+
for key in (
|
|
346
|
+
"productTypes",
|
|
347
|
+
"usedTags",
|
|
348
|
+
):
|
|
349
|
+
if key in graphql_project:
|
|
350
|
+
project[key] = graphql_project[key]
|
|
282
351
|
return project
|
|
283
352
|
|
|
284
353
|
def create_project(
|
|
@@ -585,34 +654,86 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
585
654
|
|
|
586
655
|
def _get_project_graphql_fields(
|
|
587
656
|
self, fields: Optional[set[str]]
|
|
588
|
-
) -> tuple[set[str],
|
|
589
|
-
"""
|
|
657
|
+
) -> tuple[set[str], ProjectFetchType]:
|
|
658
|
+
"""Find out if project can be fetched with GraphQl, REST or both.
|
|
590
659
|
|
|
591
660
|
Returns:
|
|
592
661
|
set[str]: GraphQl fields.
|
|
593
662
|
|
|
594
663
|
"""
|
|
595
664
|
if fields is None:
|
|
596
|
-
return set(),
|
|
597
|
-
|
|
598
|
-
|
|
665
|
+
return set(), ProjectFetchType.REST
|
|
666
|
+
|
|
667
|
+
rest_list_fields = {
|
|
668
|
+
"name",
|
|
669
|
+
"code",
|
|
670
|
+
"active",
|
|
671
|
+
"createdAt",
|
|
672
|
+
"updatedAt",
|
|
673
|
+
}
|
|
599
674
|
graphql_fields = set()
|
|
600
|
-
|
|
675
|
+
if len(fields - rest_list_fields) == 0:
|
|
676
|
+
return graphql_fields, ProjectFetchType.RESTList
|
|
677
|
+
|
|
678
|
+
must_use_graphql = False
|
|
679
|
+
for field in tuple(fields):
|
|
601
680
|
# Product types are available only in GraphQl
|
|
602
|
-
if field
|
|
603
|
-
|
|
681
|
+
if field == "usedTags":
|
|
682
|
+
graphql_fields.add("usedTags")
|
|
683
|
+
elif field == "productTypes":
|
|
684
|
+
must_use_graphql = True
|
|
685
|
+
fields.discard(field)
|
|
686
|
+
for f_name in DEFAULT_PRODUCT_TYPE_FIELDS:
|
|
687
|
+
fields.add(f"{field}.{f_name}")
|
|
688
|
+
|
|
689
|
+
elif field.startswith("productTypes"):
|
|
690
|
+
must_use_graphql = True
|
|
691
|
+
graphql_fields.add(field)
|
|
692
|
+
|
|
693
|
+
elif field == "productBaseTypes":
|
|
694
|
+
must_use_graphql = True
|
|
695
|
+
fields.discard(field)
|
|
696
|
+
for f_name in DEFAULT_PRODUCT_BASE_TYPE_FIELDS:
|
|
697
|
+
fields.add(f"{field}.{f_name}")
|
|
698
|
+
|
|
699
|
+
elif field.startswith("productBaseTypes"):
|
|
700
|
+
must_use_graphql = True
|
|
604
701
|
graphql_fields.add(field)
|
|
605
702
|
|
|
606
|
-
|
|
607
|
-
|
|
703
|
+
elif field == "bundle" or field == "bundles":
|
|
704
|
+
fields.discard(field)
|
|
705
|
+
graphql_fields.add("bundle.production")
|
|
706
|
+
graphql_fields.add("bundle.staging")
|
|
707
|
+
|
|
708
|
+
elif field.startswith("bundle"):
|
|
709
|
+
graphql_fields.add(field)
|
|
608
710
|
|
|
609
|
-
|
|
711
|
+
elif field == "attrib":
|
|
712
|
+
fields.discard("attrib")
|
|
713
|
+
graphql_fields |= self.get_attributes_fields_for_type(
|
|
714
|
+
"project"
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
# NOTE 'config' in GraphQl is NOT the same as from REST api.
|
|
718
|
+
# - At the moment of this comment there is missing 'productBaseTypes'.
|
|
719
|
+
inters = fields & {
|
|
720
|
+
"name",
|
|
721
|
+
"code",
|
|
722
|
+
"active",
|
|
723
|
+
"library",
|
|
724
|
+
"usedTags",
|
|
725
|
+
"data",
|
|
726
|
+
}
|
|
610
727
|
remainders = fields - (inters | graphql_fields)
|
|
611
|
-
if remainders:
|
|
728
|
+
if not remainders:
|
|
729
|
+
graphql_fields |= inters
|
|
730
|
+
return graphql_fields, ProjectFetchType.GraphQl
|
|
731
|
+
|
|
732
|
+
if must_use_graphql:
|
|
612
733
|
graphql_fields.add("name")
|
|
613
|
-
return graphql_fields,
|
|
614
|
-
|
|
615
|
-
return
|
|
734
|
+
return graphql_fields, ProjectFetchType.GraphQlAndREST
|
|
735
|
+
|
|
736
|
+
return set(), ProjectFetchType.REST
|
|
616
737
|
|
|
617
738
|
def _fill_project_entity_data(self, project: dict[str, Any]) -> None:
|
|
618
739
|
# Add fake scope to statuses if not available
|
|
@@ -632,13 +753,15 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
632
753
|
# Convert 'data' from string to dict if needed
|
|
633
754
|
if "data" in project:
|
|
634
755
|
project_data = project["data"]
|
|
635
|
-
if
|
|
756
|
+
if project_data is None:
|
|
757
|
+
project["data"] = {}
|
|
758
|
+
elif isinstance(project_data, str):
|
|
636
759
|
project_data = json.loads(project_data)
|
|
637
760
|
project["data"] = project_data
|
|
638
761
|
|
|
639
762
|
# Fill 'bundle' from data if is not filled
|
|
640
763
|
if "bundle" not in project:
|
|
641
|
-
bundle_data = project["data"].get("bundle"
|
|
764
|
+
bundle_data = project["data"].get("bundle") or {}
|
|
642
765
|
prod_bundle = bundle_data.get("production")
|
|
643
766
|
staging_bundle = bundle_data.get("staging")
|
|
644
767
|
project["bundle"] = {
|
|
@@ -647,9 +770,12 @@ class ProjectsAPI(BaseServerAPI):
|
|
|
647
770
|
}
|
|
648
771
|
|
|
649
772
|
# Convert 'config' from string to dict if needed
|
|
650
|
-
config
|
|
651
|
-
|
|
652
|
-
|
|
773
|
+
if "config" in project:
|
|
774
|
+
config = project["config"]
|
|
775
|
+
if config is None:
|
|
776
|
+
project["config"] = {}
|
|
777
|
+
elif isinstance(config, str):
|
|
778
|
+
project["config"] = json.loads(config)
|
|
653
779
|
|
|
654
780
|
# Unifiy 'linkTypes' data structure from REST and GraphQL
|
|
655
781
|
if "linkTypes" in project:
|
|
@@ -82,6 +82,13 @@ DEFAULT_PRODUCT_TYPE_FIELDS = {
|
|
|
82
82
|
"color",
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
# --- Product base type ---
|
|
86
|
+
DEFAULT_PRODUCT_BASE_TYPE_FIELDS = {
|
|
87
|
+
# Ignore 'icon' and 'color'
|
|
88
|
+
# - current server implementation always returns 'null'
|
|
89
|
+
"name",
|
|
90
|
+
}
|
|
91
|
+
|
|
85
92
|
# --- Project ---
|
|
86
93
|
DEFAULT_PROJECT_FIELDS = {
|
|
87
94
|
"active",
|