Ayon-Python-Api 0.1.8__tar.gz → 0.1.10__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-0.1.8/ayon_python_api.egg-info → ayon-python-api-0.1.10}/PKG-INFO +6 -8
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/README.md +6 -8
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/__init__.py +10 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/constants.py +15 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/graphql_queries.py +33 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/server.py +152 -64
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/server_api.py +68 -3
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/version.py +1 -1
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10/ayon_python_api.egg-info}/PKG-INFO +6 -8
- ayon-python-api-0.1.10/ayon_python_api.egg-info/dependency_links.txt +1 -0
- ayon-python-api-0.1.10/ayon_python_api.egg-info/requires.txt +4 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/pyproject.toml +3 -3
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/setup.py +2 -3
- ayon-python-api-0.1.8/ayon_python_api.egg-info/dependency_links.txt +0 -1
- ayon-python-api-0.1.8/ayon_python_api.egg-info/requires.txt +0 -2
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/LICENSE +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/entity_hub.py +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/events.py +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/exceptions.py +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/graphql.py +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/operations.py +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/thumbnails.py +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/utils.py +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_python_api.egg-info/SOURCES.txt +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_python_api.egg-info/top_level.txt +0 -0
- {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ayon-python-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: AYON Python API
|
|
5
5
|
Home-page: https://github.com/ynput/ayon-python-api
|
|
6
6
|
Author: ynput.io
|
|
@@ -9,20 +9,18 @@ License: Apache License (2.0)
|
|
|
9
9
|
Description: # AYON server API
|
|
10
10
|
Python client for connection server. Client must be (at least for some time) Python 2 compatible because will be used in DCC that are "older".
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
AYON Python api should support connection to server with raw REST functions and prepared functionality for work with entities. Must not contain only functionality that can be used with core server functionality.
|
|
13
13
|
|
|
14
|
+
Module support singleton connection which is using `AYON_SERVER_URL` and `AYON_TOKEN` environment variables as source for connection. The singleton connection is using `ServerAPI` object. There can be created multiple connection to different server at one time, for that purpose use `ServerAPIBase` object.
|
|
14
15
|
|
|
15
16
|
## TODOs
|
|
16
|
-
-
|
|
17
|
-
- only functions related to v4 should stay at that place and v3 compatible functions should be moved
|
|
18
|
-
- Missing settings getter
|
|
19
|
-
- Missing CRUD operations for entities (Only read is possible)
|
|
20
|
-
- More clear what is difference in `ServerAPIBase` and `ServerAPI`
|
|
17
|
+
- More clear what is difference in `ServerAPIBase` and `ServerAPI` (`server.py` and `server_api.py`) and better names
|
|
21
18
|
- `ServerAPI` was added primarily for desktop app which handle login and logout in a different way so the class should be maybe removed and `ServerAPIBase` should be renamed to `ServerAPI`
|
|
22
19
|
- find more suitable names of classes
|
|
23
|
-
- find more suitable name of objects (right now is used `connection` or `
|
|
20
|
+
- find more suitable name of objects (right now is used `connection` or `con`)
|
|
24
21
|
- Add folder and task changes to operations
|
|
25
22
|
- Entity hub should use operations to update folders and tasks
|
|
23
|
+
|
|
26
24
|
Keywords: AYON,ynput,OpenPype,vfx
|
|
27
25
|
Platform: UNKNOWN
|
|
28
26
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# AYON server API
|
|
2
2
|
Python client for connection server. Client must be (at least for some time) Python 2 compatible because will be used in DCC that are "older".
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
AYON Python api should support connection to server with raw REST functions and prepared functionality for work with entities. Must not contain only functionality that can be used with core server functionality.
|
|
5
5
|
|
|
6
|
+
Module support singleton connection which is using `AYON_SERVER_URL` and `AYON_TOKEN` environment variables as source for connection. The singleton connection is using `ServerAPI` object. There can be created multiple connection to different server at one time, for that purpose use `ServerAPIBase` object.
|
|
6
7
|
|
|
7
8
|
## TODOs
|
|
8
|
-
-
|
|
9
|
-
- only functions related to v4 should stay at that place and v3 compatible functions should be moved
|
|
10
|
-
- Missing settings getter
|
|
11
|
-
- Missing CRUD operations for entities (Only read is possible)
|
|
12
|
-
- More clear what is difference in `ServerAPIBase` and `ServerAPI`
|
|
9
|
+
- More clear what is difference in `ServerAPIBase` and `ServerAPI` (`server.py` and `server_api.py`) and better names
|
|
13
10
|
- `ServerAPI` was added primarily for desktop app which handle login and logout in a different way so the class should be maybe removed and `ServerAPIBase` should be renamed to `ServerAPI`
|
|
14
11
|
- find more suitable names of classes
|
|
15
|
-
- find more suitable name of objects (right now is used `connection` or `
|
|
12
|
+
- find more suitable name of objects (right now is used `connection` or `con`)
|
|
16
13
|
- Add folder and task changes to operations
|
|
17
|
-
- Entity hub should use operations to update folders and tasks
|
|
14
|
+
- Entity hub should use operations to update folders and tasks
|
|
15
|
+
|
|
@@ -12,6 +12,10 @@ from .server_api import (
|
|
|
12
12
|
|
|
13
13
|
init_service,
|
|
14
14
|
|
|
15
|
+
is_connection_created,
|
|
16
|
+
close_connection,
|
|
17
|
+
change_token,
|
|
18
|
+
set_environments,
|
|
15
19
|
get_server_api_connection,
|
|
16
20
|
|
|
17
21
|
get_base_url,
|
|
@@ -30,6 +34,7 @@ from .server_api import (
|
|
|
30
34
|
delete,
|
|
31
35
|
|
|
32
36
|
get_event,
|
|
37
|
+
get_events,
|
|
33
38
|
dispatch_event,
|
|
34
39
|
update_event,
|
|
35
40
|
enroll_event_job,
|
|
@@ -116,6 +121,10 @@ __all__ = (
|
|
|
116
121
|
"ServiceContext",
|
|
117
122
|
"init_service",
|
|
118
123
|
|
|
124
|
+
"is_connection_created",
|
|
125
|
+
"close_connection",
|
|
126
|
+
"change_token",
|
|
127
|
+
"set_environments",
|
|
119
128
|
"get_server_api_connection",
|
|
120
129
|
|
|
121
130
|
"get_base_url",
|
|
@@ -134,6 +143,7 @@ __all__ = (
|
|
|
134
143
|
"delete",
|
|
135
144
|
|
|
136
145
|
"get_event",
|
|
146
|
+
"get_events",
|
|
137
147
|
"dispatch_event",
|
|
138
148
|
"update_event",
|
|
139
149
|
"enroll_event_job",
|
|
@@ -88,3 +88,18 @@ DEFAULT_WORKFILE_INFO_FIELDS = {
|
|
|
88
88
|
"updatedAt",
|
|
89
89
|
"updatedBy",
|
|
90
90
|
}
|
|
91
|
+
|
|
92
|
+
DEFAULT_EVENT_FIELDS = {
|
|
93
|
+
"id",
|
|
94
|
+
"hash",
|
|
95
|
+
"createdAt",
|
|
96
|
+
"dependsOn",
|
|
97
|
+
"description",
|
|
98
|
+
"project",
|
|
99
|
+
"retries",
|
|
100
|
+
"sender",
|
|
101
|
+
"status",
|
|
102
|
+
"topic",
|
|
103
|
+
"updatedAt",
|
|
104
|
+
"user",
|
|
105
|
+
}
|
|
@@ -327,3 +327,36 @@ def workfiles_info_graphql_query(fields):
|
|
|
327
327
|
for k, v in value.items():
|
|
328
328
|
query_queue.append((k, v, field))
|
|
329
329
|
return query
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def events_graphql_query(fields):
|
|
333
|
+
query = GraphQlQuery("WorkfilesInfo")
|
|
334
|
+
topics_var = query.add_variable("eventTopics", "[String!]")
|
|
335
|
+
projects_var = query.add_variable("projectNames", "[String!]")
|
|
336
|
+
states_var = query.add_variable("eventStates", "[String!]")
|
|
337
|
+
users_var = query.add_variable("eventUsers", "[String!]")
|
|
338
|
+
include_logs_var = query.add_variable("includeLogsFilter", "Boolean!")
|
|
339
|
+
|
|
340
|
+
events_field = query.add_field("events", has_edges=True)
|
|
341
|
+
events_field.set_filter("topics", topics_var)
|
|
342
|
+
events_field.set_filter("projects", projects_var)
|
|
343
|
+
events_field.set_filter("states", states_var)
|
|
344
|
+
events_field.set_filter("users", users_var)
|
|
345
|
+
events_field.set_filter("includeLogs", include_logs_var)
|
|
346
|
+
|
|
347
|
+
nested_fields = fields_to_dict(set(fields))
|
|
348
|
+
|
|
349
|
+
query_queue = collections.deque()
|
|
350
|
+
for key, value in nested_fields.items():
|
|
351
|
+
query_queue.append((key, value, events_field))
|
|
352
|
+
|
|
353
|
+
while query_queue:
|
|
354
|
+
item = query_queue.popleft()
|
|
355
|
+
key, value, parent = item
|
|
356
|
+
field = parent.add_field(key)
|
|
357
|
+
if value is FIELD_VALUE:
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
for k, v in value.items():
|
|
361
|
+
query_queue.append((k, v, field))
|
|
362
|
+
return query
|
|
@@ -6,7 +6,10 @@ import logging
|
|
|
6
6
|
import collections
|
|
7
7
|
import platform
|
|
8
8
|
import copy
|
|
9
|
-
|
|
9
|
+
try:
|
|
10
|
+
from http import HTTPStatus
|
|
11
|
+
except ImportError:
|
|
12
|
+
HTTPStatus = None
|
|
10
13
|
|
|
11
14
|
import requests
|
|
12
15
|
|
|
@@ -19,6 +22,7 @@ from .constants import (
|
|
|
19
22
|
DEFAULT_REPRESENTATION_FIELDS,
|
|
20
23
|
REPRESENTATION_FILES_FIELDS,
|
|
21
24
|
DEFAULT_WORKFILE_INFO_FIELDS,
|
|
25
|
+
DEFAULT_EVENT_FIELDS,
|
|
22
26
|
)
|
|
23
27
|
from .thumbnails import ThumbnailCache
|
|
24
28
|
from .graphql import GraphQlQuery, INTROSPECTION_QUERY
|
|
@@ -32,6 +36,7 @@ from .graphql_queries import (
|
|
|
32
36
|
representations_graphql_query,
|
|
33
37
|
representations_parents_qraphql_query,
|
|
34
38
|
workfiles_info_graphql_query,
|
|
39
|
+
events_graphql_query,
|
|
35
40
|
)
|
|
36
41
|
from .exceptions import (
|
|
37
42
|
FailedOperations,
|
|
@@ -56,6 +61,12 @@ PROJECT_NAME_REGEX = re.compile(
|
|
|
56
61
|
)
|
|
57
62
|
|
|
58
63
|
|
|
64
|
+
def _get_description(response):
|
|
65
|
+
if HTTPStatus is None:
|
|
66
|
+
return str(response.orig_response)
|
|
67
|
+
return HTTPStatus(response.status).description
|
|
68
|
+
|
|
69
|
+
|
|
59
70
|
class RequestType:
|
|
60
71
|
def __init__(self, name):
|
|
61
72
|
self.name = name
|
|
@@ -95,7 +106,10 @@ class RestApiResponse(object):
|
|
|
95
106
|
@property
|
|
96
107
|
def data(self):
|
|
97
108
|
if self._data is None:
|
|
98
|
-
self.
|
|
109
|
+
if self.status != 204:
|
|
110
|
+
self._data = self.orig_response.json()
|
|
111
|
+
else:
|
|
112
|
+
self._data = {}
|
|
99
113
|
return self._data
|
|
100
114
|
|
|
101
115
|
@property
|
|
@@ -108,7 +122,7 @@ class RestApiResponse(object):
|
|
|
108
122
|
|
|
109
123
|
@property
|
|
110
124
|
def detail(self):
|
|
111
|
-
return self.get("detail",
|
|
125
|
+
return self.get("detail", _get_description(self))
|
|
112
126
|
|
|
113
127
|
@property
|
|
114
128
|
def status_code(self):
|
|
@@ -586,6 +600,74 @@ class ServerAPIBase(object):
|
|
|
586
600
|
response.raise_for_status()
|
|
587
601
|
return response.data
|
|
588
602
|
|
|
603
|
+
def get_events(
|
|
604
|
+
self,
|
|
605
|
+
topics=None,
|
|
606
|
+
project_names=None,
|
|
607
|
+
states=None,
|
|
608
|
+
users=None,
|
|
609
|
+
include_logs=None,
|
|
610
|
+
fields=None
|
|
611
|
+
):
|
|
612
|
+
"""Get events from server with filtering options.
|
|
613
|
+
|
|
614
|
+
Notes:
|
|
615
|
+
Not all event happen on a project.
|
|
616
|
+
|
|
617
|
+
Args:
|
|
618
|
+
topics (Iterable[str]): Name of topics.
|
|
619
|
+
project_names (Iterable[str]): Project on which event happened.
|
|
620
|
+
states (Iterable[str]): Filtering by states.
|
|
621
|
+
users (Iterable[str]): Filtering by users who created/triggered
|
|
622
|
+
an event.
|
|
623
|
+
include_logs (bool): Query also log events.
|
|
624
|
+
fields (Iterable[str]): Fields that should be received for each
|
|
625
|
+
event.
|
|
626
|
+
|
|
627
|
+
Returns:
|
|
628
|
+
Generator[Dict[str, Any]]: Available events matching filters.
|
|
629
|
+
"""
|
|
630
|
+
|
|
631
|
+
filters = {}
|
|
632
|
+
if topics is not None:
|
|
633
|
+
topics = set(topics)
|
|
634
|
+
if not topics:
|
|
635
|
+
return
|
|
636
|
+
filters["eventTopics"] = list(topics)
|
|
637
|
+
|
|
638
|
+
if project_names is not None:
|
|
639
|
+
project_names = set(project_names)
|
|
640
|
+
if not project_names:
|
|
641
|
+
return
|
|
642
|
+
filters["projectName"] = list(project_names)
|
|
643
|
+
|
|
644
|
+
if states is not None:
|
|
645
|
+
states = set(states)
|
|
646
|
+
if not states:
|
|
647
|
+
return
|
|
648
|
+
filters["eventStates"] = list(states)
|
|
649
|
+
|
|
650
|
+
if users is not None:
|
|
651
|
+
users = set(users)
|
|
652
|
+
if not users:
|
|
653
|
+
return
|
|
654
|
+
filters["eventUsers"] = list(users)
|
|
655
|
+
|
|
656
|
+
if include_logs is None:
|
|
657
|
+
include_logs = False
|
|
658
|
+
filters["includeLogsFilter"] = include_logs
|
|
659
|
+
|
|
660
|
+
if not fields:
|
|
661
|
+
fields = DEFAULT_EVENT_FIELDS
|
|
662
|
+
|
|
663
|
+
query = events_graphql_query(set(fields))
|
|
664
|
+
for attr, filter_value in filters.items():
|
|
665
|
+
query.set_variable_value(attr, filter_value)
|
|
666
|
+
|
|
667
|
+
for parsed_data in query.continuous_query(self):
|
|
668
|
+
for event in parsed_data["events"]:
|
|
669
|
+
yield event
|
|
670
|
+
|
|
589
671
|
def update_event(
|
|
590
672
|
self,
|
|
591
673
|
event_id,
|
|
@@ -607,7 +689,7 @@ class ServerAPIBase(object):
|
|
|
607
689
|
):
|
|
608
690
|
if value is not None:
|
|
609
691
|
kwargs[key] = value
|
|
610
|
-
response = self.
|
|
692
|
+
response = self.patch(
|
|
611
693
|
"events/{}".format(event_id),
|
|
612
694
|
**kwargs
|
|
613
695
|
)
|
|
@@ -1016,8 +1098,7 @@ class ServerAPIBase(object):
|
|
|
1016
1098
|
if details:
|
|
1017
1099
|
endpoint += "?details=1"
|
|
1018
1100
|
response = self.get(endpoint)
|
|
1019
|
-
|
|
1020
|
-
raise ServerError(response.text)
|
|
1101
|
+
response.raise_for_status()
|
|
1021
1102
|
return response.data
|
|
1022
1103
|
|
|
1023
1104
|
def download_addon_private_file(
|
|
@@ -1233,8 +1314,7 @@ class ServerAPIBase(object):
|
|
|
1233
1314
|
result = self.get(
|
|
1234
1315
|
"addons/{}/{}/settings".format(addon_name, addon_version)
|
|
1235
1316
|
)
|
|
1236
|
-
|
|
1237
|
-
raise ServerError(result.text)
|
|
1317
|
+
result.raise_for_status()
|
|
1238
1318
|
return result.data
|
|
1239
1319
|
|
|
1240
1320
|
def get_addon_project_settings(
|
|
@@ -1245,8 +1325,7 @@ class ServerAPIBase(object):
|
|
|
1245
1325
|
addon_name, addon_version, project_name
|
|
1246
1326
|
)
|
|
1247
1327
|
)
|
|
1248
|
-
|
|
1249
|
-
raise ServerError(result.text)
|
|
1328
|
+
result.raise_for_status()
|
|
1250
1329
|
return result.data
|
|
1251
1330
|
|
|
1252
1331
|
def get_addon_settings(self, addon_name, addon_version, project_name=None):
|
|
@@ -1287,7 +1366,7 @@ class ServerAPIBase(object):
|
|
|
1287
1366
|
returned if 'None' is passed.
|
|
1288
1367
|
|
|
1289
1368
|
Returns:
|
|
1290
|
-
|
|
1369
|
+
Generator[Dict[str, Any]]: Available projects.
|
|
1291
1370
|
"""
|
|
1292
1371
|
|
|
1293
1372
|
for project_name in self.get_project_names(active, library):
|
|
@@ -1350,8 +1429,7 @@ class ServerAPIBase(object):
|
|
|
1350
1429
|
]))
|
|
1351
1430
|
|
|
1352
1431
|
response = self.get("projects{}".format(query), **query_keys)
|
|
1353
|
-
|
|
1354
|
-
response.status
|
|
1432
|
+
response.raise_for_status()
|
|
1355
1433
|
data = response.data
|
|
1356
1434
|
project_names = []
|
|
1357
1435
|
if data:
|
|
@@ -1375,7 +1453,7 @@ class ServerAPIBase(object):
|
|
|
1375
1453
|
on entity will have 'None' value.
|
|
1376
1454
|
|
|
1377
1455
|
Returns:
|
|
1378
|
-
|
|
1456
|
+
Generator[Dict[str, Any]]: Queried projects.
|
|
1379
1457
|
"""
|
|
1380
1458
|
|
|
1381
1459
|
if fields is None:
|
|
@@ -1484,11 +1562,11 @@ class ServerAPIBase(object):
|
|
|
1484
1562
|
if 'None' is passed.
|
|
1485
1563
|
|
|
1486
1564
|
Returns:
|
|
1487
|
-
|
|
1565
|
+
Generator[dict[str, Any]]: Queried folder entities.
|
|
1488
1566
|
"""
|
|
1489
1567
|
|
|
1490
1568
|
if not project_name:
|
|
1491
|
-
return
|
|
1569
|
+
return
|
|
1492
1570
|
|
|
1493
1571
|
filters = {
|
|
1494
1572
|
"projectName": project_name
|
|
@@ -1496,25 +1574,25 @@ class ServerAPIBase(object):
|
|
|
1496
1574
|
if folder_ids is not None:
|
|
1497
1575
|
folder_ids = set(folder_ids)
|
|
1498
1576
|
if not folder_ids:
|
|
1499
|
-
return
|
|
1577
|
+
return
|
|
1500
1578
|
filters["folderIds"] = list(folder_ids)
|
|
1501
1579
|
|
|
1502
1580
|
if folder_paths is not None:
|
|
1503
1581
|
folder_paths = set(folder_paths)
|
|
1504
1582
|
if not folder_paths:
|
|
1505
|
-
return
|
|
1583
|
+
return
|
|
1506
1584
|
filters["folderPaths"] = list(folder_paths)
|
|
1507
1585
|
|
|
1508
1586
|
if folder_names is not None:
|
|
1509
1587
|
folder_names = set(folder_names)
|
|
1510
1588
|
if not folder_names:
|
|
1511
|
-
return
|
|
1589
|
+
return
|
|
1512
1590
|
filters["folderNames"] = list(folder_names)
|
|
1513
1591
|
|
|
1514
1592
|
if parent_ids is not None:
|
|
1515
1593
|
parent_ids = set(parent_ids)
|
|
1516
1594
|
if not parent_ids:
|
|
1517
|
-
return
|
|
1595
|
+
return
|
|
1518
1596
|
if None in parent_ids:
|
|
1519
1597
|
# Replace 'None' with '"root"' which is used during GraphQl
|
|
1520
1598
|
# query for parent ids filter for folders without folder
|
|
@@ -1575,7 +1653,7 @@ class ServerAPIBase(object):
|
|
|
1575
1653
|
own_attributes=False
|
|
1576
1654
|
):
|
|
1577
1655
|
if not project_name:
|
|
1578
|
-
return
|
|
1656
|
+
return
|
|
1579
1657
|
|
|
1580
1658
|
filters = {
|
|
1581
1659
|
"projectName": project_name
|
|
@@ -1584,25 +1662,25 @@ class ServerAPIBase(object):
|
|
|
1584
1662
|
if task_ids is not None:
|
|
1585
1663
|
task_ids = set(task_ids)
|
|
1586
1664
|
if not task_ids:
|
|
1587
|
-
return
|
|
1665
|
+
return
|
|
1588
1666
|
filters["taskIds"] = list(task_ids)
|
|
1589
1667
|
|
|
1590
1668
|
if task_names is not None:
|
|
1591
1669
|
task_names = set(task_names)
|
|
1592
1670
|
if not task_names:
|
|
1593
|
-
return
|
|
1671
|
+
return
|
|
1594
1672
|
filters["taskNames"] = list(task_names)
|
|
1595
1673
|
|
|
1596
1674
|
if task_types is not None:
|
|
1597
1675
|
task_types = set(task_types)
|
|
1598
1676
|
if not task_types:
|
|
1599
|
-
return
|
|
1677
|
+
return
|
|
1600
1678
|
filters["taskTypes"] = list(task_types)
|
|
1601
1679
|
|
|
1602
1680
|
if folder_ids is not None:
|
|
1603
1681
|
folder_ids = set(folder_ids)
|
|
1604
1682
|
if not folder_ids:
|
|
1605
|
-
return
|
|
1683
|
+
return
|
|
1606
1684
|
filters["folderIds"] = list(folder_ids)
|
|
1607
1685
|
|
|
1608
1686
|
if not fields:
|
|
@@ -1760,6 +1838,20 @@ class ServerAPIBase(object):
|
|
|
1760
1838
|
for folder in folders
|
|
1761
1839
|
}
|
|
1762
1840
|
|
|
1841
|
+
def _filter_subset(
|
|
1842
|
+
self, project_name, subset, active, own_attributes, use_rest
|
|
1843
|
+
):
|
|
1844
|
+
if active is not None and subset["active"] is not active:
|
|
1845
|
+
return None
|
|
1846
|
+
|
|
1847
|
+
if use_rest:
|
|
1848
|
+
subset = self.get_rest_subset(project_name, subset["id"])
|
|
1849
|
+
|
|
1850
|
+
if own_attributes:
|
|
1851
|
+
fill_own_attribs(subset)
|
|
1852
|
+
|
|
1853
|
+
return subset
|
|
1854
|
+
|
|
1763
1855
|
def get_subsets(
|
|
1764
1856
|
self,
|
|
1765
1857
|
project_name,
|
|
@@ -1772,24 +1864,24 @@ class ServerAPIBase(object):
|
|
|
1772
1864
|
own_attributes=False
|
|
1773
1865
|
):
|
|
1774
1866
|
if not project_name:
|
|
1775
|
-
return
|
|
1867
|
+
return
|
|
1776
1868
|
|
|
1777
1869
|
if subset_ids is not None:
|
|
1778
1870
|
subset_ids = set(subset_ids)
|
|
1779
1871
|
if not subset_ids:
|
|
1780
|
-
return
|
|
1872
|
+
return
|
|
1781
1873
|
|
|
1782
1874
|
filter_subset_names = None
|
|
1783
1875
|
if subset_names is not None:
|
|
1784
1876
|
filter_subset_names = set(subset_names)
|
|
1785
1877
|
if not filter_subset_names:
|
|
1786
|
-
return
|
|
1878
|
+
return
|
|
1787
1879
|
|
|
1788
1880
|
filter_folder_ids = None
|
|
1789
1881
|
if folder_ids is not None:
|
|
1790
1882
|
filter_folder_ids = set(folder_ids)
|
|
1791
1883
|
if not filter_folder_ids:
|
|
1792
|
-
return
|
|
1884
|
+
return
|
|
1793
1885
|
|
|
1794
1886
|
# This will disable 'folder_ids' and 'subset_names' filters
|
|
1795
1887
|
# - maybe could be enhanced in future?
|
|
@@ -1803,7 +1895,7 @@ class ServerAPIBase(object):
|
|
|
1803
1895
|
filter_subset_names |= set(names)
|
|
1804
1896
|
|
|
1805
1897
|
if not filter_subset_names or not filter_folder_ids:
|
|
1806
|
-
return
|
|
1898
|
+
return
|
|
1807
1899
|
|
|
1808
1900
|
# Convert fields and add minimum required fields
|
|
1809
1901
|
if fields:
|
|
@@ -1847,35 +1939,30 @@ class ServerAPIBase(object):
|
|
|
1847
1939
|
parsed_data = query.query(self)
|
|
1848
1940
|
|
|
1849
1941
|
subsets = parsed_data.get("project", {}).get("subsets", [])
|
|
1850
|
-
if active is not None or own_attributes:
|
|
1851
|
-
_subsets = []
|
|
1852
|
-
for subset in subsets:
|
|
1853
|
-
if active is not None and subset["active"] is not active:
|
|
1854
|
-
continue
|
|
1855
|
-
|
|
1856
|
-
if use_rest:
|
|
1857
|
-
subset = self.get_rest_subset(project_name, subset["id"])
|
|
1858
|
-
|
|
1859
|
-
if own_attributes:
|
|
1860
|
-
fill_own_attribs(subset)
|
|
1861
|
-
_subsets.append(subset)
|
|
1862
|
-
subsets = _subsets
|
|
1863
|
-
|
|
1864
1942
|
# Filter subsets by 'names_by_folder_ids'
|
|
1865
1943
|
if names_by_folder_ids:
|
|
1866
1944
|
subsets_by_folder_id = collections.defaultdict(list)
|
|
1867
1945
|
for subset in subsets:
|
|
1868
|
-
|
|
1869
|
-
|
|
1946
|
+
filtered_subset = self._filter_subset(
|
|
1947
|
+
project_name, subset, active, own_attributes, use_rest
|
|
1948
|
+
)
|
|
1949
|
+
if filtered_subset is not None:
|
|
1950
|
+
folder_id = filtered_subset["folderId"]
|
|
1951
|
+
subsets_by_folder_id[folder_id].append(filtered_subset)
|
|
1870
1952
|
|
|
1871
|
-
filtered_subsets = []
|
|
1872
1953
|
for folder_id, names in names_by_folder_ids.items():
|
|
1873
1954
|
for folder_subset in subsets_by_folder_id[folder_id]:
|
|
1874
1955
|
if folder_subset["name"] in names:
|
|
1875
|
-
|
|
1876
|
-
|
|
1956
|
+
yield folder_subset
|
|
1957
|
+
|
|
1958
|
+
else:
|
|
1959
|
+
for subset in subsets:
|
|
1960
|
+
filtered_subset = self._filter_subset(
|
|
1961
|
+
project_name, subset, active, own_attributes, use_rest
|
|
1962
|
+
)
|
|
1963
|
+
if filtered_subset is not None:
|
|
1964
|
+
yield filtered_subset
|
|
1877
1965
|
|
|
1878
|
-
return list(subsets)
|
|
1879
1966
|
|
|
1880
1967
|
def get_subset_by_id(
|
|
1881
1968
|
self,
|
|
@@ -1972,7 +2059,7 @@ class ServerAPIBase(object):
|
|
|
1972
2059
|
if 'None' is passed.
|
|
1973
2060
|
|
|
1974
2061
|
Returns:
|
|
1975
|
-
|
|
2062
|
+
Generator[Dict[str, Any]]: Queried version entities.
|
|
1976
2063
|
"""
|
|
1977
2064
|
|
|
1978
2065
|
if not fields:
|
|
@@ -1999,24 +2086,24 @@ class ServerAPIBase(object):
|
|
|
1999
2086
|
if version_ids is not None:
|
|
2000
2087
|
version_ids = set(version_ids)
|
|
2001
2088
|
if not version_ids:
|
|
2002
|
-
return
|
|
2089
|
+
return
|
|
2003
2090
|
filters["versionIds"] = list(version_ids)
|
|
2004
2091
|
|
|
2005
2092
|
if subset_ids is not None:
|
|
2006
2093
|
subset_ids = set(subset_ids)
|
|
2007
2094
|
if not subset_ids:
|
|
2008
|
-
return
|
|
2095
|
+
return
|
|
2009
2096
|
filters["subsetIds"] = list(subset_ids)
|
|
2010
2097
|
|
|
2011
2098
|
# TODO versions can't be used as fitler at this moment!
|
|
2012
2099
|
if versions is not None:
|
|
2013
2100
|
versions = set(versions)
|
|
2014
2101
|
if not versions:
|
|
2015
|
-
return
|
|
2102
|
+
return
|
|
2016
2103
|
filters["versions"] = list(versions)
|
|
2017
2104
|
|
|
2018
2105
|
if not hero and not standard:
|
|
2019
|
-
return
|
|
2106
|
+
return
|
|
2020
2107
|
|
|
2021
2108
|
queries = []
|
|
2022
2109
|
# Add filters based on 'hero' and 'standard'
|
|
@@ -2285,7 +2372,7 @@ class ServerAPIBase(object):
|
|
|
2285
2372
|
passed.
|
|
2286
2373
|
|
|
2287
2374
|
Returns:
|
|
2288
|
-
|
|
2375
|
+
Generator[Dict[str, Any]]: Queried representation entities.
|
|
2289
2376
|
"""
|
|
2290
2377
|
|
|
2291
2378
|
if not fields:
|
|
@@ -2310,7 +2397,7 @@ class ServerAPIBase(object):
|
|
|
2310
2397
|
if representation_ids is not None:
|
|
2311
2398
|
representation_ids = set(representation_ids)
|
|
2312
2399
|
if not representation_ids:
|
|
2313
|
-
return
|
|
2400
|
+
return
|
|
2314
2401
|
filters["representationIds"] = list(representation_ids)
|
|
2315
2402
|
|
|
2316
2403
|
version_ids_filter = None
|
|
@@ -2323,18 +2410,18 @@ class ServerAPIBase(object):
|
|
|
2323
2410
|
representaion_names_filter |= set(names)
|
|
2324
2411
|
|
|
2325
2412
|
if not version_ids_filter or not representaion_names_filter:
|
|
2326
|
-
return
|
|
2413
|
+
return
|
|
2327
2414
|
|
|
2328
2415
|
else:
|
|
2329
2416
|
if representation_names is not None:
|
|
2330
2417
|
representaion_names_filter = set(representation_names)
|
|
2331
2418
|
if not representaion_names_filter:
|
|
2332
|
-
return
|
|
2419
|
+
return
|
|
2333
2420
|
|
|
2334
2421
|
if version_ids is not None:
|
|
2335
2422
|
version_ids_filter = set(version_ids)
|
|
2336
2423
|
if not version_ids_filter:
|
|
2337
|
-
return
|
|
2424
|
+
return
|
|
2338
2425
|
|
|
2339
2426
|
if version_ids_filter:
|
|
2340
2427
|
filters["versionIds"] = list(version_ids_filter)
|
|
@@ -2459,19 +2546,19 @@ class ServerAPIBase(object):
|
|
|
2459
2546
|
if task_ids is not None:
|
|
2460
2547
|
task_ids = set(task_ids)
|
|
2461
2548
|
if not task_ids:
|
|
2462
|
-
return
|
|
2549
|
+
return
|
|
2463
2550
|
filters["taskIds"] = list(task_ids)
|
|
2464
2551
|
|
|
2465
2552
|
if paths is not None:
|
|
2466
2553
|
paths = set(paths)
|
|
2467
2554
|
if not paths:
|
|
2468
|
-
return
|
|
2555
|
+
return
|
|
2469
2556
|
filters["paths"] = list(paths)
|
|
2470
2557
|
|
|
2471
2558
|
if workfile_ids is not None:
|
|
2472
2559
|
workfile_ids = set(workfile_ids)
|
|
2473
2560
|
if not workfile_ids:
|
|
2474
|
-
return
|
|
2561
|
+
return
|
|
2475
2562
|
filters["workfileIds"] = list(workfile_ids)
|
|
2476
2563
|
|
|
2477
2564
|
if not fields:
|
|
@@ -2849,4 +2936,5 @@ class ServerAPIBase(object):
|
|
|
2849
2936
|
operation_id,
|
|
2850
2937
|
json.dumps(body_by_id[operation_id], indent=4),
|
|
2851
2938
|
op_result["error"],
|
|
2852
|
-
))
|
|
2939
|
+
))
|
|
2940
|
+
|
|
@@ -68,6 +68,27 @@ class GlobalContext:
|
|
|
68
68
|
|
|
69
69
|
_connection = None
|
|
70
70
|
|
|
71
|
+
@classmethod
|
|
72
|
+
def is_connection_created(cls):
|
|
73
|
+
return cls._connection is not None
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def change_token(cls, url, token):
|
|
77
|
+
ServerAPI.set_environments(url, token)
|
|
78
|
+
if cls._connection is None:
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
if cls._connection.get_base_url() == url:
|
|
82
|
+
cls._connection.set_token(token)
|
|
83
|
+
else:
|
|
84
|
+
cls.close_connection()
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def close_connection(cls):
|
|
88
|
+
if cls._connection is not None:
|
|
89
|
+
cls._connection.close_session()
|
|
90
|
+
cls._connection = None
|
|
91
|
+
|
|
71
92
|
@classmethod
|
|
72
93
|
def get_server_api_connection(cls):
|
|
73
94
|
if cls._connection is None:
|
|
@@ -150,8 +171,7 @@ class ServiceContext:
|
|
|
150
171
|
cls.service_name = service_name or socket.gethostname()
|
|
151
172
|
|
|
152
173
|
# Make sure required environments for ServerAPI are set
|
|
153
|
-
|
|
154
|
-
os.environ["AYON_TOKEN"] = cls.token
|
|
174
|
+
ServerAPI.set_environments(cls.server_url, cls.token)
|
|
155
175
|
|
|
156
176
|
if connect:
|
|
157
177
|
print("Connecting to server \"{}\"".format(server_url))
|
|
@@ -164,6 +184,46 @@ def init_service(*args, **kwargs):
|
|
|
164
184
|
ServiceContext.init_service(*args, **kwargs)
|
|
165
185
|
|
|
166
186
|
|
|
187
|
+
def is_connection_created():
|
|
188
|
+
"""Is global connection created.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
bool: True if connection was connected.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
return GlobalContext.is_connection_created()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def close_connection():
|
|
198
|
+
"""Close global connection if is connected."""
|
|
199
|
+
|
|
200
|
+
GlobalContext.close_connection()
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def change_token(url, token):
|
|
204
|
+
"""Change connection token for url.
|
|
205
|
+
|
|
206
|
+
This function can be also used to change url.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
url (str): Server url.
|
|
210
|
+
token (str): API key token.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
GlobalContext.change_token(url, token)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def set_environments(url, token):
|
|
217
|
+
"""Set global environments for global connection.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
url (Union[str, None]): Url to server or None to unset environments.
|
|
221
|
+
token (Union[str, None]): API key token to be used for connection.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
ServerAPI.set_environments(url, token)
|
|
225
|
+
|
|
226
|
+
|
|
167
227
|
def get_server_api_connection():
|
|
168
228
|
"""Access to global scope object of ServerAPI.
|
|
169
229
|
|
|
@@ -242,9 +302,14 @@ def get_event(*args, **kwargs):
|
|
|
242
302
|
return con.get_event(*args, **kwargs)
|
|
243
303
|
|
|
244
304
|
|
|
305
|
+
def get_events(*args, **kwargs):
|
|
306
|
+
con = get_server_api_connection()
|
|
307
|
+
return con.get_events(*args, **kwargs)
|
|
308
|
+
|
|
309
|
+
|
|
245
310
|
def dispatch_event(*args, **kwargs):
|
|
246
311
|
con = get_server_api_connection()
|
|
247
|
-
return con.
|
|
312
|
+
return con.dispatch_event(*args, **kwargs)
|
|
248
313
|
|
|
249
314
|
|
|
250
315
|
def update_event(*args, **kwargs):
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Package declaring Python API for Ayon server."""
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.10"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: ayon-python-api
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: AYON Python API
|
|
5
5
|
Home-page: https://github.com/ynput/ayon-python-api
|
|
6
6
|
Author: ynput.io
|
|
@@ -9,20 +9,18 @@ License: Apache License (2.0)
|
|
|
9
9
|
Description: # AYON server API
|
|
10
10
|
Python client for connection server. Client must be (at least for some time) Python 2 compatible because will be used in DCC that are "older".
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
AYON Python api should support connection to server with raw REST functions and prepared functionality for work with entities. Must not contain only functionality that can be used with core server functionality.
|
|
13
13
|
|
|
14
|
+
Module support singleton connection which is using `AYON_SERVER_URL` and `AYON_TOKEN` environment variables as source for connection. The singleton connection is using `ServerAPI` object. There can be created multiple connection to different server at one time, for that purpose use `ServerAPIBase` object.
|
|
14
15
|
|
|
15
16
|
## TODOs
|
|
16
|
-
-
|
|
17
|
-
- only functions related to v4 should stay at that place and v3 compatible functions should be moved
|
|
18
|
-
- Missing settings getter
|
|
19
|
-
- Missing CRUD operations for entities (Only read is possible)
|
|
20
|
-
- More clear what is difference in `ServerAPIBase` and `ServerAPI`
|
|
17
|
+
- More clear what is difference in `ServerAPIBase` and `ServerAPI` (`server.py` and `server_api.py`) and better names
|
|
21
18
|
- `ServerAPI` was added primarily for desktop app which handle login and logout in a different way so the class should be maybe removed and `ServerAPIBase` should be renamed to `ServerAPI`
|
|
22
19
|
- find more suitable names of classes
|
|
23
|
-
- find more suitable name of objects (right now is used `connection` or `
|
|
20
|
+
- find more suitable name of objects (right now is used `connection` or `con`)
|
|
24
21
|
- Add folder and task changes to operations
|
|
25
22
|
- Entity hub should use operations to update folders and tasks
|
|
23
|
+
|
|
26
24
|
Keywords: AYON,ynput,OpenPype,vfx
|
|
27
25
|
Platform: UNKNOWN
|
|
28
26
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "ayon_api"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.10"
|
|
4
4
|
description = "Python api for AYON server"
|
|
5
5
|
authors = ["ynput.io <info@ynput.io>"]
|
|
6
6
|
|
|
7
7
|
[tool.poetry.dependencies]
|
|
8
8
|
requests = "^2.28.1"
|
|
9
9
|
six = "^1.15"
|
|
10
|
-
|
|
11
|
-
appdirs =
|
|
10
|
+
Unidecode = "^1.2"
|
|
11
|
+
appdirs = "^1.4"
|
|
12
12
|
|
|
13
13
|
[tool.poetry.dev-dependencies]
|
|
14
14
|
pytest = "^5.2"
|
|
@@ -30,9 +30,8 @@ setup(
|
|
|
30
30
|
install_requires=[
|
|
31
31
|
"requests >= 2.27.1",
|
|
32
32
|
"six >= 1.15",
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"appdirs @ git+https://github.com/ActiveState/appdirs.git@master"
|
|
33
|
+
"Unidecode >= 1.2.0",
|
|
34
|
+
"appdirs >=1, <2",
|
|
36
35
|
],
|
|
37
36
|
keywords=["AYON", "ynput", "OpenPype", "vfx"],
|
|
38
37
|
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
appdirs @ git+https://github.com/ActiveState/appdirs.git@master
|
|
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
|