Ayon-Python-Api 0.2.0__tar.gz → 0.2.1__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 (24) hide show
  1. {ayon-python-api-0.2.0/ayon_python_api.egg-info → ayon-python-api-0.2.1}/PKG-INFO +1 -1
  2. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/__init__.py +42 -2
  3. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/_api.py +92 -0
  4. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/entity_hub.py +88 -0
  5. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/server_api.py +579 -53
  6. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/version.py +1 -1
  7. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1/ayon_python_api.egg-info}/PKG-INFO +1 -1
  8. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/pyproject.toml +2 -2
  9. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/LICENSE +0 -0
  10. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/README.md +0 -0
  11. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/constants.py +0 -0
  12. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/events.py +0 -0
  13. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/exceptions.py +0 -0
  14. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/graphql.py +0 -0
  15. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/graphql_queries.py +0 -0
  16. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/operations.py +0 -0
  17. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/thumbnails.py +0 -0
  18. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_api/utils.py +0 -0
  19. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_python_api.egg-info/SOURCES.txt +0 -0
  20. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_python_api.egg-info/dependency_links.txt +0 -0
  21. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_python_api.egg-info/requires.txt +0 -0
  22. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/ayon_python_api.egg-info/top_level.txt +0 -0
  23. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/setup.cfg +0 -0
  24. {ayon-python-api-0.2.0 → ayon-python-api-0.2.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -59,13 +59,31 @@ from ._api import (
59
59
  get_addon_url,
60
60
  download_addon_private_file,
61
61
 
62
+ get_installers,
63
+ create_installer,
64
+ update_installer,
65
+ delete_installer,
66
+ download_installer,
67
+ upload_installer,
68
+
62
69
  get_dependencies_info,
63
70
  update_dependency_info,
71
+ get_dependency_packages,
72
+ create_dependency_package,
73
+ update_dependency_package,
74
+ delete_dependency_package,
64
75
 
65
76
  download_dependency_package,
66
77
  upload_dependency_package,
67
- delete_dependency_package,
68
78
 
79
+ get_bundles,
80
+ create_bundle,
81
+ update_bundle,
82
+ delete_bundle,
83
+
84
+ get_info,
85
+ get_server_version,
86
+ get_server_version_tuple,
69
87
  get_user,
70
88
  get_users,
71
89
 
@@ -87,9 +105,11 @@ from ._api import (
87
105
  get_addons_project_settings,
88
106
  get_addons_settings,
89
107
 
108
+ get_project_names,
90
109
  get_projects,
91
110
  get_project,
92
111
  create_project,
112
+ update_project,
93
113
  delete_project,
94
114
 
95
115
  get_folder_by_id,
@@ -218,13 +238,31 @@ __all__ = (
218
238
  "get_addon_url",
219
239
  "download_addon_private_file",
220
240
 
241
+ "get_installers",
242
+ "create_installer",
243
+ "update_installer",
244
+ "delete_installer",
245
+ "download_installer",
246
+ "upload_installer",
247
+
221
248
  "get_dependencies_info",
222
249
  "update_dependency_info",
250
+ "get_dependency_packages",
251
+ "create_dependency_package",
252
+ "update_dependency_package",
253
+ "delete_dependency_package",
223
254
 
224
255
  "download_dependency_package",
225
256
  "upload_dependency_package",
226
- "delete_dependency_package",
227
257
 
258
+ "get_bundles",
259
+ "create_bundle",
260
+ "update_bundle",
261
+ "delete_bundle",
262
+
263
+ "get_info",
264
+ "get_server_version",
265
+ "get_server_version_tuple",
228
266
  "get_user",
229
267
  "get_users",
230
268
 
@@ -245,9 +283,11 @@ __all__ = (
245
283
  "get_addons_project_settings",
246
284
  "get_addons_settings",
247
285
 
286
+ "get_project_names",
248
287
  "get_projects",
249
288
  "get_project",
250
289
  "create_project",
290
+ "update_project",
251
291
  "delete_project",
252
292
 
253
293
  "get_folder_by_id",
@@ -531,6 +531,53 @@ def download_addon_private_file(*args, **kwargs):
531
531
  return con.download_addon_private_file(*args, **kwargs)
532
532
 
533
533
 
534
+ def get_info(*args, **kwargs):
535
+ con = get_server_api_connection()
536
+ return con.get_info(*args, **kwargs)
537
+
538
+
539
+ def get_server_version(*args, **kwargs):
540
+ con = get_server_api_connection()
541
+ return con.get_server_version(*args, **kwargs)
542
+
543
+
544
+ def get_server_version_tuple(*args, **kwargs):
545
+ con = get_server_api_connection()
546
+ return con.get_server_version_tuple(*args, **kwargs)
547
+
548
+
549
+ # Installers
550
+ def get_installers(*args, **kwargs):
551
+ con = get_server_api_connection()
552
+ return con.get_installers(*args, **kwargs)
553
+
554
+
555
+ def create_installer(*args, **kwargs):
556
+ con = get_server_api_connection()
557
+ return con.create_installer(*args, **kwargs)
558
+
559
+
560
+ def update_installer(*args, **kwargs):
561
+ con = get_server_api_connection()
562
+ return con.update_installer(*args, **kwargs)
563
+
564
+
565
+ def delete_installer(*args, **kwargs):
566
+ con = get_server_api_connection()
567
+ return con.delete_installer(*args, **kwargs)
568
+
569
+
570
+ def download_installer(*args, **kwargs):
571
+ con = get_server_api_connection()
572
+ con.download_installer(*args, **kwargs)
573
+
574
+
575
+ def upload_installer(*args, **kwargs):
576
+ con = get_server_api_connection()
577
+ con.upload_installer(*args, **kwargs)
578
+
579
+
580
+ # Dependency packages
534
581
  def get_dependencies_info(*args, **kwargs):
535
582
  con = get_server_api_connection()
536
583
  return con.get_dependencies_info(*args, **kwargs)
@@ -551,6 +598,21 @@ def upload_dependency_package(*args, **kwargs):
551
598
  return con.upload_dependency_package(*args, **kwargs)
552
599
 
553
600
 
601
+ def get_dependency_packages(*args, **kwargs):
602
+ con = get_server_api_connection()
603
+ return con.get_dependency_packages(*args, **kwargs)
604
+
605
+
606
+ def create_dependency_package(*args, **kwargs):
607
+ con = get_server_api_connection()
608
+ return con.create_dependency_package(*args, **kwargs)
609
+
610
+
611
+ def update_dependency_package(*args, **kwargs):
612
+ con = get_server_api_connection()
613
+ return con.update_dependency_package(*args, **kwargs)
614
+
615
+
554
616
  def delete_dependency_package(*args, **kwargs):
555
617
  con = get_server_api_connection()
556
618
  return con.delete_dependency_package(*args, **kwargs)
@@ -561,6 +623,26 @@ def get_project_anatomy_presets(*args, **kwargs):
561
623
  return con.get_project_anatomy_presets(*args, **kwargs)
562
624
 
563
625
 
626
+ def get_bundles(*args, **kwargs):
627
+ con = get_server_api_connection()
628
+ return con.get_bundles(*args, **kwargs)
629
+
630
+
631
+ def create_bundle(*args, **kwargs):
632
+ con = get_server_api_connection()
633
+ return con.create_bundle(*args, **kwargs)
634
+
635
+
636
+ def update_bundle(*args, **kwargs):
637
+ con = get_server_api_connection()
638
+ return con.update_bundle(*args, **kwargs)
639
+
640
+
641
+ def delete_bundle(*args, **kwargs):
642
+ con = get_server_api_connection()
643
+ return con.delete_bundle(*args, **kwargs)
644
+
645
+
564
646
  def get_project_anatomy_preset(*args, **kwargs):
565
647
  con = get_server_api_connection()
566
648
  return con.get_project_anatomy_preset(*args, **kwargs)
@@ -621,6 +703,11 @@ def get_addons_settings(*args, **kwargs):
621
703
  return con.get_addons_settings(*args, **kwargs)
622
704
 
623
705
 
706
+ def get_project_names(*args, **kwargs):
707
+ con = get_server_api_connection()
708
+ return con.get_project_names(*args, **kwargs)
709
+
710
+
624
711
  def get_project(*args, **kwargs):
625
712
  con = get_server_api_connection()
626
713
  return con.get_project(*args, **kwargs)
@@ -801,6 +888,11 @@ def create_project(
801
888
  )
802
889
 
803
890
 
891
+ def update_project(project_name, *args, **kwargs):
892
+ con = get_server_api_connection()
893
+ return con.update_project(project_name, *args, **kwargs)
894
+
895
+
804
896
  def delete_project(project_name):
805
897
  con = get_server_api_connection()
806
898
  return con.delete_project(project_name)
@@ -683,6 +683,82 @@ class EntityHub(object):
683
683
  "entityId": entity.id
684
684
  }
685
685
 
686
+ def _pre_commit_types_changes(
687
+ self, project_changes, orig_types, changes_key, post_changes
688
+ ):
689
+ """Compare changes of types on a project.
690
+
691
+ Compare old and new types. Change project changes content if some old
692
+ types were removed. In that case the final change of types will
693
+ happen when all other entities have changed.
694
+
695
+ Args:
696
+ project_changes (dict[str, Any]): Project changes.
697
+ orig_types (list[dict[str, Any]]): Original types.
698
+ changes_key (Literal[folderTypes, taskTypes]): Key of type changes
699
+ in project changes.
700
+ post_changes (dict[str, Any]): An object where post changes will
701
+ be stored.
702
+ """
703
+
704
+ if changes_key not in project_changes:
705
+ return
706
+
707
+ new_types = project_changes[changes_key]
708
+
709
+ orig_types_by_name = {
710
+ type_info["name"]: type_info
711
+ for type_info in orig_types
712
+ }
713
+ new_names = {
714
+ type_info["name"]
715
+ for type_info in new_types
716
+ }
717
+ diff_names = set(orig_types_by_name) - new_names
718
+ if not diff_names:
719
+ return
720
+
721
+ # Create copy of folder type changes to post changes
722
+ # - post changes will be commited at the end
723
+ post_changes[changes_key] = copy.deepcopy(new_types)
724
+
725
+ for type_name in diff_names:
726
+ new_types.append(orig_types_by_name[type_name])
727
+
728
+ def _pre_commit_project(self):
729
+ """Some project changes cannot be committed before hierarchy changes.
730
+
731
+ It is not possible to change folder types or task types if there are
732
+ existing hierarchy items using the removed types. For that purposes
733
+ is first committed union of all old and new types and post changes
734
+ are prepared when all existing entities are changed.
735
+
736
+ Returns:
737
+ dict[str, Any]: Changes that will be committed after hierarchy
738
+ changes.
739
+ """
740
+
741
+ project_changes = self.project_entity.changes
742
+
743
+ post_changes = {}
744
+ if not project_changes:
745
+ return post_changes
746
+
747
+ self._pre_commit_types_changes(
748
+ project_changes,
749
+ self.project_entity.get_orig_folder_types(),
750
+ "folderType",
751
+ post_changes
752
+ )
753
+ self._pre_commit_types_changes(
754
+ project_changes,
755
+ self.project_entity.get_orig_task_types(),
756
+ "taskType",
757
+ post_changes
758
+ )
759
+ self._connection.update_project(self.project_name, **project_changes)
760
+ return post_changes
761
+
686
762
  def commit_changes(self):
687
763
  """Commit any changes that happened on entities.
688
764
 
@@ -690,6 +766,9 @@ class EntityHub(object):
690
766
  Use Operations Session instead of known operations body.
691
767
  """
692
768
 
769
+ post_project_changes = self._pre_commit_project()
770
+ self.project_entity.lock()
771
+
693
772
  project_changes = self.project_entity.changes
694
773
  if project_changes:
695
774
  response = self._connection.patch(
@@ -760,6 +839,9 @@ class EntityHub(object):
760
839
  self._connection.send_batch_operations(
761
840
  self.project_name, operations_body
762
841
  )
842
+ if post_project_changes:
843
+ self._connection.update_project(
844
+ self.project_name, **post_project_changes)
763
845
 
764
846
  self.lock()
765
847
 
@@ -1463,6 +1545,9 @@ class ProjectEntity(BaseEntity):
1463
1545
 
1464
1546
  parent = property(get_parent, set_parent)
1465
1547
 
1548
+ def get_orig_folder_types(self):
1549
+ return copy.deepcopy(self._orig_folder_types)
1550
+
1466
1551
  def get_folder_types(self):
1467
1552
  return copy.deepcopy(self._folder_types)
1468
1553
 
@@ -1474,6 +1559,9 @@ class ProjectEntity(BaseEntity):
1474
1559
  new_folder_types.append(folder_type)
1475
1560
  self._folder_types = new_folder_types
1476
1561
 
1562
+ def get_orig_task_types(self):
1563
+ return copy.deepcopy(self._orig_task_types)
1564
+
1477
1565
  def get_task_types(self):
1478
1566
  return copy.deepcopy(self._task_types)
1479
1567
 
@@ -4,6 +4,7 @@ import io
4
4
  import json
5
5
  import logging
6
6
  import collections
7
+ import datetime
7
8
  import platform
8
9
  import copy
9
10
  import uuid
@@ -70,6 +71,14 @@ PROJECT_NAME_REGEX = re.compile(
70
71
  "^[{}]+$".format(PROJECT_NAME_ALLOWED_SYMBOLS)
71
72
  )
72
73
 
74
+ VERSION_REGEX = re.compile(
75
+ r"(?P<major>0|[1-9]\d*)"
76
+ r"\.(?P<minor>0|[1-9]\d*)"
77
+ r"\.(?P<patch>0|[1-9]\d*)"
78
+ r"(?:-(?P<prerelease>[a-zA-Z\d\-.]*))?"
79
+ r"(?:\+(?P<buildmetadata>[a-zA-Z\d\-.]*))?"
80
+ )
81
+
73
82
 
74
83
  def _get_description(response):
75
84
  if HTTPStatus is None:
@@ -329,6 +338,8 @@ class ServerAPI(object):
329
338
  self._access_token_is_service = None
330
339
  self._token_is_valid = None
331
340
  self._server_available = None
341
+ self._server_version = None
342
+ self._server_version_tuple = None
332
343
 
333
344
  self._session = None
334
345
 
@@ -631,12 +642,52 @@ class ServerAPI(object):
631
642
  Use this method for validation of token instead of 'get_user'.
632
643
 
633
644
  Returns:
634
- Dict[str, Any]: Information from server.
645
+ dict[str, Any]: Information from server.
635
646
  """
636
647
 
637
648
  response = self.get("info")
638
649
  return response.data
639
650
 
651
+ def get_server_version(self):
652
+ """Get server version.
653
+
654
+ Version should match semantic version (https://semver.org/).
655
+
656
+ Returns:
657
+ str: Server version.
658
+ """
659
+
660
+ if self._server_version is None:
661
+ self._server_version = self.get_info()["version"]
662
+ return self._server_version
663
+
664
+ def get_server_version_tuple(self):
665
+ """Get server version as tuple.
666
+
667
+ Version should match semantic version (https://semver.org/).
668
+
669
+ This function only returns first three numbers of version.
670
+
671
+ Returns:
672
+ Tuple[int, int, int, Union[str, None], Union[str, None]]: Server
673
+ version.
674
+ """
675
+
676
+ if self._server_version_tuple is None:
677
+ re_match = VERSION_REGEX.fullmatch(
678
+ self.get_server_version())
679
+ self._server_version_tuple = (
680
+ int(re_match.group("major")),
681
+ int(re_match.group("minor")),
682
+ int(re_match.group("patch")),
683
+ re_match.group("prerelease"),
684
+ re_match.group("buildmetadata")
685
+ )
686
+ return self._server_version_tuple
687
+
688
+ server_version = property(get_server_version)
689
+ server_version_tuple = property(get_server_version_tuple)
690
+
640
691
  def _get_user_info(self):
641
692
  if self._access_token is None:
642
693
  return None
@@ -869,7 +920,7 @@ class ServerAPI(object):
869
920
  event_id (str): Id of event.
870
921
 
871
922
  Returns:
872
- Dict[str, Any]: Full event data.
923
+ dict[str, Any]: Full event data.
873
924
  """
874
925
 
875
926
  response = self.get("events/{}".format(event_id))
@@ -902,7 +953,7 @@ class ServerAPI(object):
902
953
  for each event.
903
954
 
904
955
  Returns:
905
- Generator[Dict[str, Any]]: Available events matching filters.
956
+ Generator[dict[str, Any]]: Available events matching filters.
906
957
  """
907
958
 
908
959
  filters = {}
@@ -1269,7 +1320,7 @@ class ServerAPI(object):
1269
1320
  Cache schema - How to find out it is outdated?
1270
1321
 
1271
1322
  Returns:
1272
- Dict[str, Any]: Full server schema.
1323
+ dict[str, Any]: Full server schema.
1273
1324
  """
1274
1325
 
1275
1326
  url = "{}/openapi.json".format(self._base_url)
@@ -1287,7 +1338,7 @@ class ServerAPI(object):
1287
1338
  e.g. 'config' has just object definition without reference schema.
1288
1339
 
1289
1340
  Returns:
1290
- Dict[str, Any]: Component schemas.
1341
+ dict[str, Any]: Component schemas.
1291
1342
  """
1292
1343
 
1293
1344
  server_schema = self.get_server_schema()
@@ -1395,7 +1446,7 @@ class ServerAPI(object):
1395
1446
  received.
1396
1447
 
1397
1448
  Returns:
1398
- Dict[str, Dict[str, Any]]: Attribute schemas that are available
1449
+ dict[str, dict[str, Any]]: Attribute schemas that are available
1399
1450
  for entered entity type.
1400
1451
  """
1401
1452
  attributes = self._entity_type_attributes_cache.get(entity_type)
@@ -1563,6 +1614,148 @@ class ServerAPI(object):
1563
1614
  )
1564
1615
  return dst_filepath
1565
1616
 
1617
+ def get_installers(self, version=None, platform_name=None):
1618
+ """Information about desktop application installers on server.
1619
+
1620
+ Desktop application installers are helpers to download/update AYON
1621
+ desktop application for artists.
1622
+
1623
+ Args:
1624
+ version (Optional[str]): Filter installers by version.
1625
+ platform_name (Optional[str]): Filter installers by platform name.
1626
+
1627
+ Returns:
1628
+ list[dict[str, Any]]:
1629
+ """
1630
+
1631
+ query_fields = [
1632
+ "{}={}".format(key, value)
1633
+ for key, value in (
1634
+ ("version", version),
1635
+ ("platform", platform_name),
1636
+ )
1637
+ if value
1638
+ ]
1639
+ query = ""
1640
+ if query_fields:
1641
+ query = "?{}".format(",".join(query_fields))
1642
+
1643
+ response = self.get("desktop/installers{}".format(query))
1644
+ response.raise_for_status()
1645
+ return response.data
1646
+
1647
+ def create_installer(
1648
+ self,
1649
+ filename,
1650
+ version,
1651
+ python_version,
1652
+ platform_name,
1653
+ python_modules,
1654
+ checksum,
1655
+ checksum_type,
1656
+ file_size,
1657
+ sources=None,
1658
+ ):
1659
+ """Create new installer information on server.
1660
+
1661
+ This step will create only metadata. Make sure to upload installer
1662
+ to the server using 'upload_installer' method.
1663
+
1664
+ Args:
1665
+ filename (str): Installer filename.
1666
+ version (str): Version of installer.
1667
+ python_version (str): Version of Python.
1668
+ platform_name (str): Name of platform.
1669
+ python_modules (dict[str, str]): Python modules that are available
1670
+ in installer.
1671
+ checksum (str): Installer file checksum.
1672
+ checksum_type (str): Type of checksum used to create checksum.
1673
+ file_size (int): File size.
1674
+ sources (Optional[list[dict[str, Any]]]): List of sources that
1675
+ can be used to download file.
1676
+ """
1677
+
1678
+ body = {
1679
+ "filename": filename,
1680
+ "version": version,
1681
+ "pythonVersion": python_version,
1682
+ "platform": platform_name,
1683
+ "pythonModules": python_modules,
1684
+ "checksum": checksum,
1685
+ "checksumType": checksum_type,
1686
+ "size": file_size,
1687
+ }
1688
+ if sources:
1689
+ body["sources"] = sources
1690
+
1691
+ response = self.post("desktop/installers", **body)
1692
+ response.raise_for_status()
1693
+
1694
+ def update_installer(self, filename, sources):
1695
+ """Update installer information on server.
1696
+
1697
+ Args:
1698
+ filename (str): Installer filename.
1699
+ sources (list[dict[str, Any]]): List of sources that
1700
+ can be used to download file. Fully replaces existing sources.
1701
+ """
1702
+
1703
+ response = self.post(
1704
+ "desktop/installers/{}".format(filename),
1705
+ sources=sources
1706
+ )
1707
+ response.raise_for_status()
1708
+
1709
+ def delete_installer(self, filename):
1710
+ """Delete installer from server.
1711
+
1712
+ Args:
1713
+ filename (str): Installer filename.
1714
+ """
1715
+
1716
+ response = self.delete("dekstop/installers/{}".format(filename))
1717
+ response.raise_for_status()
1718
+
1719
+ def download_installer(
1720
+ self,
1721
+ filename,
1722
+ dst_filepath,
1723
+ chunk_size=None,
1724
+ progress=None
1725
+ ):
1726
+ """Download installer file from server.
1727
+
1728
+ Args:
1729
+ filename (str): Installer filename.
1730
+ dst_filepath (str): Destination filepath.
1731
+ chunk_size (Optional[int]): Download chunk size.
1732
+ progress (Optional[TransferProgress]): Object that gives ability
1733
+ to track download progress.
1734
+ """
1735
+
1736
+ self.download_file(
1737
+ "desktop/installers/{}".format(filename),
1738
+ dst_filepath,
1739
+ chunk_size=chunk_size,
1740
+ progress=progress
1741
+ )
1742
+
1743
+ def upload_installer(self, src_filepath, dst_filename, progress=None):
1744
+ """Upload installer file to server.
1745
+
1746
+ Args:
1747
+ src_filepath (str): Source filepath.
1748
+ dst_filename (str): Destination filename.
1749
+ progress (Optional[TransferProgress]): Object that gives ability
1750
+ to track download progress.
1751
+ """
1752
+
1753
+ self.upload_file(
1754
+ "desktop/installers/{}".format(dst_filename),
1755
+ src_filepath,
1756
+ progress=progress
1757
+ )
1758
+
1566
1759
  def get_dependencies_info(self):
1567
1760
  """Information about dependency packages on server.
1568
1761
 
@@ -1581,13 +1774,22 @@ class ServerAPI(object):
1581
1774
  "productionPackage": str
1582
1775
  }
1583
1776
 
1777
+ Deprecated:
1778
+ Deprecated since server version 0.2.1. Use
1779
+ 'get_dependency_packages' instead.
1780
+
1584
1781
  Returns:
1585
1782
  dict[str, Any]: Information about dependency packages known for
1586
1783
  server.
1587
1784
  """
1588
1785
 
1589
- result = self.get("dependencies")
1590
- return result.data
1786
+ major, minor, patch, _, _ = self.server_version_tuple
1787
+ if major == 0 and (minor < 2 or (minor == 2 and patch < 1)):
1788
+ result = self.get("dependencies")
1789
+ return result.data
1790
+ packages = self.get_dependency_packages()
1791
+ packages["productionPackage"] = None
1792
+ return packages
1591
1793
 
1592
1794
  def update_dependency_info(
1593
1795
  self,
@@ -1604,21 +1806,26 @@ class ServerAPI(object):
1604
1806
 
1605
1807
  The endpoint can be used to create or update dependency package.
1606
1808
 
1809
+
1810
+ Deprecated:
1811
+ Deprecated for server version 0.2.1. Use
1812
+ 'create_dependency_pacakge' instead.
1813
+
1607
1814
  Args:
1608
1815
  name (str): Name of dependency package.
1609
- platform_name (Literal["windows", "linux", "darwin"]): Platform for
1610
- which is dependency package targeted.
1816
+ platform_name (Literal["windows", "linux", "darwin"]): Platform
1817
+ for which is dependency package targeted.
1611
1818
  size (int): Size of dependency package in bytes.
1612
1819
  checksum (str): Checksum of archive file where dependencies are.
1613
1820
  checksum_algorithm (Optional[str]): Algorithm used to calculate
1614
1821
  checksum. By default, is used 'md5' (defined by server).
1615
- supported_addons (Optional[Dict[str, str]]): Name of addons for
1822
+ supported_addons (Optional[dict[str, str]]): Name of addons for
1616
1823
  which was the package created.
1617
1824
  '{"<addon name>": "<addon version>", ...}'
1618
- python_modules (Optional[Dict[str, str]]): Python modules in
1825
+ python_modules (Optional[dict[str, str]]): Python modules in
1619
1826
  dependencies package.
1620
1827
  '{"<module name>": "<module version>", ...}'
1621
- sources (Optional[List[Dict[str, Any]]]): Information about
1828
+ sources (Optional[list[dict[str, Any]]]): Information about
1622
1829
  sources where dependency package is available.
1623
1830
  """
1624
1831
 
@@ -1645,27 +1852,153 @@ class ServerAPI(object):
1645
1852
  raise ServerError("Failed to create/update dependency")
1646
1853
  return response.data
1647
1854
 
1855
+ def get_dependency_packages(self):
1856
+ """Information about dependency packages on server.
1857
+
1858
+ To download dependency package, use 'download_dependency_package'
1859
+ method and pass in 'filename'.
1860
+
1861
+ Example data structure:
1862
+ {
1863
+ "packages": [
1864
+ {
1865
+ "filename": str,
1866
+ "platform": str,
1867
+ "checksum": str,
1868
+ "checksumAlgorithm": str,
1869
+ "size": int,
1870
+ "sources": list[dict[str, Any]],
1871
+ "supportedAddons": dict[str, str],
1872
+ "pythonModules": dict[str, str]
1873
+ }
1874
+ ]
1875
+ }
1876
+
1877
+ Returns:
1878
+ dict[str, Any]: Information about dependency packages known for
1879
+ server.
1880
+ """
1881
+
1882
+ result = self.get("desktop/dependency_packages")
1883
+ result.raise_for_status()
1884
+ return result.data
1885
+
1886
+ def create_dependency_package(
1887
+ self,
1888
+ filename,
1889
+ python_modules,
1890
+ source_addons,
1891
+ installer_version,
1892
+ checksum,
1893
+ checksum_algorithm,
1894
+ file_size,
1895
+ sources=None,
1896
+ platform_name=None,
1897
+ ):
1898
+ """Create dependency package on server.
1899
+
1900
+ The package will be created on a server, it is also required to upload
1901
+ the package archive file (using 'upload_dependency_package').
1902
+
1903
+ Args:
1904
+ filename (str): Filename of dependency package.
1905
+ python_modules (dict[str, str]): Python modules in dependency
1906
+ package.
1907
+ '{"<module name>": "<module version>", ...}'
1908
+ source_addons (dict[str, str]): Name of addons for which is
1909
+ dependency package created.
1910
+ '{"<addon name>": "<addon version>", ...}'
1911
+ installer_version (str): Version of installer for which was
1912
+ package created.
1913
+ checksum (str): Checksum of archive file where dependencies are.
1914
+ checksum_algorithm (str): Algorithm used to calculate checksum.
1915
+ file_size (Optional[int]): Size of file.
1916
+ sources (Optional[list[dict[str, Any]]]): Information about
1917
+ sources from where it is possible to get file.
1918
+ platform_name (Optional[str]): Name of platform for which is
1919
+ dependency package targeted. Default value is
1920
+ current platform.
1921
+ """
1922
+
1923
+ post_body = {
1924
+ "filename": filename,
1925
+ "pythonModules": python_modules,
1926
+ "sourceAddons": source_addons,
1927
+ "installerVersion": installer_version,
1928
+ "checksum": checksum,
1929
+ "checksumAlgorithm": checksum_algorithm,
1930
+ "size": file_size,
1931
+ "platform": platform_name or platform.system().lower(),
1932
+ }
1933
+ if sources:
1934
+ post_body["sources"] = sources
1935
+
1936
+ response = self.post("desktop/dependency_packages", **post_body)
1937
+ response.raise_for_status()
1938
+
1939
+ def update_dependency_package(self, filename, sources):
1940
+ """Update dependency package metadata on server.
1941
+
1942
+ Args:
1943
+ filename (str): Filename of dependency package.
1944
+ sources (list[dict[str, Any]]): Information about
1945
+ sources from where it is possible to get file. Fully replaces
1946
+ existing sources.
1947
+ """
1948
+
1949
+ response = self.patch(
1950
+ "desktop/dependency_packages/{}".format(filename),
1951
+ sources=sources
1952
+ )
1953
+ response.raise_for_status()
1954
+
1955
+ def delete_dependency_package(self, filename, platform_name=None):
1956
+ """Remove dependency package for specific platform.
1957
+
1958
+ Args:
1959
+ filename (str): Filename of dependency package. Or name of package
1960
+ for server version 0.2.0 or lower.
1961
+ platform_name (Optional[str]): Which platform of the package
1962
+ should be removed. Current platform is used if not passed.
1963
+ Deprecated since version 0.2.1
1964
+ """
1965
+
1966
+ major, minor, patch, _, _ = self.server_version_tuple
1967
+ if major == 0 and (minor < 2 or (minor == 2 and patch < 1)):
1968
+ if platform_name is None:
1969
+ platform_name = platform.system().lower()
1970
+ url = "dependencies/{}/{}".format(filename, platform_name)
1971
+ else:
1972
+ url = "desktop/dependency_packages/{}".format(filename)
1973
+
1974
+ response = self.delete(url)
1975
+ if response.status != 200:
1976
+ raise ServerError("Failed to delete dependency file")
1977
+ return response.data
1978
+
1648
1979
  def download_dependency_package(
1649
1980
  self,
1650
- package_name,
1981
+ src_filename,
1651
1982
  dst_directory,
1652
- filename,
1983
+ dst_filename,
1653
1984
  platform_name=None,
1654
1985
  chunk_size=None,
1655
1986
  progress=None,
1656
1987
  ):
1657
1988
  """Download dependency package from server.
1658
1989
 
1659
- This method requires to have authorized token available. The package is
1660
- only downloaded.
1990
+ This method requires to have authorized token available. The package
1991
+ is only downloaded.
1661
1992
 
1662
1993
  Args:
1663
- package_name (str): Name of package to download.
1994
+ src_filename (str): Filename of dependency pacakge.
1995
+ For server version 0.2.0 and lower it is name of package
1996
+ to download.
1664
1997
  dst_directory (str): Where the file should be downloaded.
1665
- filename (str): Name of destination filename.
1998
+ dst_filename (str): Name of destination filename.
1666
1999
  platform_name (Optional[str]): Name of platform for which the
1667
2000
  dependency package is targeted. Default value is
1668
- current platform.
2001
+ current platform. Deprecated since server version 0.2.1.
1669
2002
  chunk_size (Optional[int]): Download chunk size.
1670
2003
  progress (Optional[TransferProgress]): Object that gives ability
1671
2004
  to track download progress.
@@ -1673,12 +2006,18 @@ class ServerAPI(object):
1673
2006
  Returns:
1674
2007
  str: Filepath to downloaded file.
1675
2008
  """
1676
- if platform_name is None:
1677
- platform_name = platform.system().lower()
1678
2009
 
1679
- package_filepath = os.path.join(dst_directory, filename)
2010
+ major, minor, patch, _, _ = self.server_version_tuple
2011
+ if major == 0 and (minor < 2 or (minor == 2 and patch < 1)):
2012
+ if platform_name is None:
2013
+ platform_name = platform.system().lower()
2014
+ url = "dependencies/{}/{}".format(src_filename, platform_name)
2015
+ else:
2016
+ url = "desktop/dependency_packages/{}".format(src_filename)
2017
+
2018
+ package_filepath = os.path.join(dst_directory, dst_filename)
1680
2019
  self.download_file(
1681
- "dependencies/{}/{}".format(package_name, platform_name),
2020
+ url,
1682
2021
  package_filepath,
1683
2022
  chunk_size=chunk_size,
1684
2023
  progress=progress
@@ -1686,47 +2025,170 @@ class ServerAPI(object):
1686
2025
  return package_filepath
1687
2026
 
1688
2027
  def upload_dependency_package(
1689
- self, filepath, package_name, platform_name=None, progress=None
2028
+ self, src_filepath, dst_filename, platform_name=None, progress=None
1690
2029
  ):
1691
2030
  """Upload dependency package to server.
1692
2031
 
1693
2032
  Args:
1694
- filepath (str): Path to a package file.
1695
- package_name (str): Name of package. Must be unique.
2033
+ src_filepath (str): Path to a package file.
2034
+ dst_filename (str): Dependency package filename or name of package
2035
+ for server version 0.2.0 or lower. Must be unique.
1696
2036
  platform_name (Optional[str]): For which platform is the
1697
- package targeted.
2037
+ package targeted. Deprecated since server version 0.2.1.
1698
2038
  progress (Optional[TransferProgress]): Object to keep track about
1699
2039
  upload state.
1700
2040
  """
1701
2041
 
1702
- if platform_name is None:
1703
- platform_name = platform.system().lower()
2042
+ major, minor, patch, _, _ = self.server_version_tuple
2043
+ if major == 0 and (minor < 2 or (minor == 2 and patch < 1)):
2044
+ if platform_name is None:
2045
+ platform_name = platform.system().lower()
2046
+ url = "dependencies/{}/{}".format(dst_filename, platform_name)
2047
+ else:
2048
+ url = "desktop/dependency_packages/{}".format(dst_filename)
1704
2049
 
1705
- self.upload_file(
1706
- "dependencies/{}/{}".format(package_name, platform_name),
1707
- filepath,
1708
- progress=progress
1709
- )
2050
+ self.upload_file(url, src_filepath, progress=progress)
1710
2051
 
1711
- def delete_dependency_package(self, package_name, platform_name=None):
1712
- """Remove dependency package for specific platform.
2052
+ def create_dependency_package_basename(self, platform_name=None):
2053
+ """Create basename for dependency package file.
1713
2054
 
1714
2055
  Args:
1715
- package_name (str): Name of package to remove.
1716
- platform_name (Optional[str]): Which platform of the package
1717
- should be removed. Current platform is used if not passed.
2056
+ platform_name (Optional[str]): Name of platform for which the
2057
+ bundle is targeted. Default value is current platform.
2058
+
2059
+ Returns:
2060
+ str: Dependency package name with timestamp and platform.
1718
2061
  """
1719
2062
 
1720
2063
  if platform_name is None:
1721
2064
  platform_name = platform.system().lower()
1722
2065
 
1723
- response = self.delete(
1724
- "dependencies/{}/{}".format(package_name, platform_name),
1725
- )
1726
- if response.status != 200:
1727
- raise ServerError("Failed to delete dependency file")
2066
+ now_date = datetime.datetime.now()
2067
+ time_stamp = now_date.strftime("%y%m%d%H%M")
2068
+ return "ayon_{}_{}".format(time_stamp, platform_name)
2069
+
2070
+ def get_bundles(self):
2071
+ """Server bundles with basic information.
2072
+
2073
+ Example output:
2074
+ {
2075
+ "bundles": [
2076
+ {
2077
+ "name": "my_bundle",
2078
+ "createdAt": "2023-06-12T15:37:02.420260",
2079
+ "installerVersion": "1.0.0",
2080
+ "addons": {
2081
+ "core": "1.2.3"
2082
+ },
2083
+ "dependencyPackages": {
2084
+ "windows": "a_windows_package123.zip",
2085
+ "linux": "a_linux_package123.zip",
2086
+ "darwin": "a_mac_package123.zip"
2087
+ },
2088
+ "isProduction": False,
2089
+ "isStaging": False
2090
+ }
2091
+ ],
2092
+ "productionBundle": "my_bundle",
2093
+ "stagingBundle": "test_bundle"
2094
+ }
2095
+
2096
+ Returns:
2097
+ dict[str, Any]: Server bundles with basic information.
2098
+ """
2099
+
2100
+ response = self.get("desktop/bundles")
2101
+ response.raise_for_status()
1728
2102
  return response.data
1729
2103
 
2104
+ def create_bundle(
2105
+ self,
2106
+ name,
2107
+ addon_versions,
2108
+ installer_version,
2109
+ dependency_packages=None,
2110
+ is_production=None,
2111
+ is_staging=None
2112
+ ):
2113
+ """Create bundle on server.
2114
+
2115
+ Bundle cannot be changed once is created. Only isProduction, isStaging
2116
+ and dependency packages can change after creation.
2117
+
2118
+ Args:
2119
+ name (str): Name of bundle.
2120
+ addon_versions (dict[str, str]): Addon versions.
2121
+ installer_version (Union[str, None]): Installer version.
2122
+ dependency_packages (Optional[dict[str, str]]): Dependency
2123
+ package names. Keys are platform names and values are name of
2124
+ packages.
2125
+ is_production (Optional[bool]): Bundle will be marked as
2126
+ production.
2127
+ is_staging (Optional[bool]): Bundle will be marked as staging.
2128
+ """
2129
+
2130
+ body = {
2131
+ "name": name,
2132
+ "installerVersion": installer_version,
2133
+ "addons": addon_versions,
2134
+ }
2135
+ for key, value in (
2136
+ ("dependencyPackages", dependency_packages),
2137
+ ("isProduction", is_production),
2138
+ ("isStaging", is_staging),
2139
+ ):
2140
+ if value is not None:
2141
+ body[key] = value
2142
+
2143
+ response = self.post("desktop/bundles", **body)
2144
+ response.raise_for_status()
2145
+
2146
+ def update_bundle(
2147
+ self,
2148
+ bundle_name,
2149
+ dependency_packages=None,
2150
+ is_production=None,
2151
+ is_staging=None
2152
+ ):
2153
+ """Update bundle on server.
2154
+
2155
+ Dependency packages can be update only for single platform. Others
2156
+ will be left untouched. Use 'None' value to unset dependency package
2157
+ from bundle.
2158
+
2159
+ Args:
2160
+ bundle_name (str): Name of bundle.
2161
+ dependency_packages (Optional[dict[str, str]]): Dependency pacakge
2162
+ names that should be used with the bundle.
2163
+ is_production (Optional[bool]): Bundle will be marked as
2164
+ production.
2165
+ is_staging (Optional[bool]): Bundle will be marked as staging.
2166
+ """
2167
+
2168
+ body = {
2169
+ key: value
2170
+ for key, value in (
2171
+ ("dependencyPackages", dependency_packages),
2172
+ ("isProduction", is_production),
2173
+ ("isStaging", is_staging),
2174
+ )
2175
+ if value is not None
2176
+ }
2177
+ response = self.patch(
2178
+ "desktop/bundles/{}".format(bundle_name), **body
2179
+ )
2180
+ response.raise_for_status()
2181
+
2182
+ def delete_bundle(self, bundle_name):
2183
+ """Delete bundle from server.
2184
+
2185
+ Args:
2186
+ bundle_name (str): Name of bundle to delete.
2187
+ """
2188
+
2189
+ response = self.delete("desktop/bundles/{}".format(bundle_name))
2190
+ response.raise_for_status()
2191
+
1730
2192
  # Anatomy presets
1731
2193
  def get_project_anatomy_presets(self):
1732
2194
  """Anatomy presets available on server.
@@ -2150,7 +2612,7 @@ class ServerAPI(object):
2150
2612
  project_name (str): Name of project.
2151
2613
 
2152
2614
  Returns:
2153
- Union[Dict[str, Any], None]: Project entity data or 'None' if
2615
+ Union[dict[str, Any], None]: Project entity data or 'None' if
2154
2616
  project was not found.
2155
2617
  """
2156
2618
 
@@ -2174,7 +2636,7 @@ class ServerAPI(object):
2174
2636
  are returned if 'None' is passed.
2175
2637
 
2176
2638
  Returns:
2177
- Generator[Dict[str, Any]]: Available projects.
2639
+ Generator[dict[str, Any]]: Available projects.
2178
2640
  """
2179
2641
 
2180
2642
  for project_name in self.get_project_names(active, library):
@@ -2235,7 +2697,7 @@ class ServerAPI(object):
2235
2697
  are returned if 'None' is passed.
2236
2698
 
2237
2699
  Returns:
2238
- List[str]: List of available project names.
2700
+ list[str]: List of available project names.
2239
2701
  """
2240
2702
 
2241
2703
  query_keys = {}
@@ -2276,7 +2738,7 @@ class ServerAPI(object):
2276
2738
  not explicitly set on entity will have 'None' value.
2277
2739
 
2278
2740
  Returns:
2279
- Generator[Dict[str, Any]]: Queried projects.
2741
+ Generator[dict[str, Any]]: Queried projects.
2280
2742
  """
2281
2743
 
2282
2744
  if fields is None:
@@ -2316,7 +2778,7 @@ class ServerAPI(object):
2316
2778
  not explicitly set on entity will have 'None' value.
2317
2779
 
2318
2780
  Returns:
2319
- Union[Dict[str, Any], None]: Project entity data or None
2781
+ Union[dict[str, Any], None]: Project entity data or None
2320
2782
  if project was not found.
2321
2783
  """
2322
2784
 
@@ -3112,7 +3574,7 @@ class ServerAPI(object):
3112
3574
  not explicitly set on entity will have 'None' value.
3113
3575
 
3114
3576
  Returns:
3115
- Generator[Dict[str, Any]]: Queried version entities.
3577
+ Generator[dict[str, Any]]: Queried version entities.
3116
3578
  """
3117
3579
 
3118
3580
  if not fields:
@@ -3570,7 +4032,7 @@ class ServerAPI(object):
3570
4032
  not explicitly set on entity will have 'None' value.
3571
4033
 
3572
4034
  Returns:
3573
- Generator[Dict[str, Any]]: Queried representation entities.
4035
+ Generator[dict[str, Any]]: Queried representation entities.
3574
4036
  """
3575
4037
 
3576
4038
  if not fields:
@@ -4253,7 +4715,7 @@ class ServerAPI(object):
4253
4715
  ValueError: When project name already exists.
4254
4716
 
4255
4717
  Returns:
4256
- Dict[str, Any]: Created project entity.
4718
+ dict[str, Any]: Created project entity.
4257
4719
  """
4258
4720
 
4259
4721
  if self.get_project(project_name):
@@ -4286,6 +4748,70 @@ class ServerAPI(object):
4286
4748
 
4287
4749
  return self.get_project(project_name)
4288
4750
 
4751
+ def update_project(
4752
+ self,
4753
+ project_name,
4754
+ library=None,
4755
+ folder_types=None,
4756
+ task_types=None,
4757
+ link_types=None,
4758
+ statuses=None,
4759
+ tags=None,
4760
+ config=None,
4761
+ attrib=None,
4762
+ data=None,
4763
+ active=None,
4764
+ project_code=None,
4765
+ **changes
4766
+ ):
4767
+ """Update project entity on server.
4768
+
4769
+ Args:
4770
+ project_name (str): Name of project.
4771
+ library (Optional[bool]): Change library state.
4772
+ folder_types (Optional[list[dict[str, Any]]]): Folder type
4773
+ definitions.
4774
+ task_types (Optional[list[dict[str, Any]]]): Task type
4775
+ definitions.
4776
+ link_types (Optional[list[dict[str, Any]]]): Link type
4777
+ definitions.
4778
+ statuses (Optional[list[dict[str, Any]]]): Status definitions.
4779
+ tags (Optional[list[dict[str, Any]]]): List of tags available to
4780
+ set on entities.
4781
+ config (Optional[dict[dict[str, Any]]]): Project anatomy config
4782
+ with templates and roots.
4783
+ attrib (Optional[dict[str, Any]]): Project attributes to change.
4784
+ data (Optional[dict[str, Any]]): Custom data of a project. This
4785
+ value will 100% override project data.
4786
+ active (Optional[bool]): Change active state of a project.
4787
+ project_code (Optional[str]): Change project code. Not recommended
4788
+ during production.
4789
+ **changes: Other changed keys based on Rest API documentation.
4790
+ """
4791
+
4792
+ changes.update({
4793
+ key: value
4794
+ for key, value in (
4795
+ ("library", library),
4796
+ ("folderTypes", folder_types),
4797
+ ("taskTypes", task_types),
4798
+ ("linkTypes", link_types),
4799
+ ("statuses", statuses),
4800
+ ("tags", tags),
4801
+ ("config", config),
4802
+ ("attrib", attrib),
4803
+ ("data", data),
4804
+ ("active", active),
4805
+ ("code", project_code),
4806
+ )
4807
+ if value is not None
4808
+ })
4809
+ response = self.patch(
4810
+ "projects/{}".format(project_name),
4811
+ **changes
4812
+ )
4813
+ response.raise_for_status()
4814
+
4289
4815
  def delete_project(self, project_name):
4290
4816
  """Delete project from server.
4291
4817
 
@@ -1,2 +1,2 @@
1
1
  """Package declaring Python API for Ayon server."""
2
- __version__ = "0.2.0"
2
+ __version__ = "0.2.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ayon-python-api
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: AYON Python API
5
5
  Home-page: https://github.com/ynput/ayon-python-api
6
6
  Author: ynput.io
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "ayon_api"
3
- version = "0.2.0"
3
+ version = "0.2.1"
4
4
  description = "Python api for AYON server"
5
5
  authors = ["ynput.io <info@ynput.io>"]
6
6
 
@@ -8,7 +8,7 @@ authors = ["ynput.io <info@ynput.io>"]
8
8
  python = ">=2.7,>=3.6.5"
9
9
  requests = "^2.28.1"
10
10
  six = "^1.15"
11
- Unidecode = "^1.2"
11
+ Unidecode = "1.2.0" # Python 2 support
12
12
  appdirs = "^1.4"
13
13
 
14
14
  [tool.poetry.dev-dependencies]
File without changes