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.
Files changed (26) hide show
  1. {ayon-python-api-0.1.8/ayon_python_api.egg-info → ayon-python-api-0.1.10}/PKG-INFO +6 -8
  2. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/README.md +6 -8
  3. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/__init__.py +10 -0
  4. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/constants.py +15 -0
  5. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/graphql_queries.py +33 -0
  6. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/server.py +152 -64
  7. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/server_api.py +68 -3
  8. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/version.py +1 -1
  9. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10/ayon_python_api.egg-info}/PKG-INFO +6 -8
  10. ayon-python-api-0.1.10/ayon_python_api.egg-info/dependency_links.txt +1 -0
  11. ayon-python-api-0.1.10/ayon_python_api.egg-info/requires.txt +4 -0
  12. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/pyproject.toml +3 -3
  13. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/setup.py +2 -3
  14. ayon-python-api-0.1.8/ayon_python_api.egg-info/dependency_links.txt +0 -1
  15. ayon-python-api-0.1.8/ayon_python_api.egg-info/requires.txt +0 -2
  16. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/LICENSE +0 -0
  17. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/entity_hub.py +0 -0
  18. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/events.py +0 -0
  19. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/exceptions.py +0 -0
  20. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/graphql.py +0 -0
  21. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/operations.py +0 -0
  22. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/thumbnails.py +0 -0
  23. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_api/utils.py +0 -0
  24. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  25. {ayon-python-api-0.1.8 → ayon-python-api-0.1.10}/ayon_python_api.egg-info/top_level.txt +0 -0
  26. {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.8
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
- Client 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.
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
- - Current implementation contains prepared functions for entity queries which is not using full potential of OpenPype server but is MongoDB compatible.
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 `conn`)
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
- Client 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.
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
- - Current implementation contains prepared functions for entity queries which is not using full potential of OpenPype server but is MongoDB compatible.
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 `conn`)
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
- from http import HTTPStatus
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._data = self._response.json()
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", HTTPStatus(self.status).description)
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.put(
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
- if response.status != 200:
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
- if result.status != 200:
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
- if result.status != 200:
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
- List[Dict[str, Any]]: List of available projects.
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
- # TODO check status
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
- List[Dict[str, Any]]: List of queried projects.
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
- Iterable[dict[str, Any]]: Queried folder entities.
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
- folder_id = subset["folderId"]
1869
- subsets_by_folder_id[folder_id].append(subset)
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
- filtered_subsets.append(subset)
1876
- subsets = filtered_subsets
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
- List[Dict[str, Any]]: Queried version entities.
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
- List[Dict[str, Any]]: Queried representation entities.
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
- os.environ["AYON_SERVER_URL"] = cls.server_url
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.get_event(*args, **kwargs)
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.8"
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.8
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
- Client 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.
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
- - Current implementation contains prepared functions for entity queries which is not using full potential of OpenPype server but is MongoDB compatible.
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 `conn`)
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,4 @@
1
+ requests>=2.27.1
2
+ six>=1.15
3
+ Unidecode>=1.2.0
4
+ appdirs<2,>=1
@@ -1,14 +1,14 @@
1
1
  [tool.poetry]
2
2
  name = "ayon_api"
3
- version = "0.1.8"
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
- unidecode = "^1.3"
11
- appdirs = { git = "https://github.com/ActiveState/appdirs.git", branch = "master" }
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
- dependency_links=[
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
@@ -1,2 +0,0 @@
1
- requests>=2.27.1
2
- six>=1.15