From 706469205cdf1a54b2a969ae2ff953dca1f70b23 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Jun 2023 11:34:45 +0200 Subject: [PATCH] updated ayon api to '0.2.1' --- .../vendor/python/common/ayon_api/__init__.py | 44 +- .../vendor/python/common/ayon_api/_api.py | 92 +++ .../python/common/ayon_api/entity_hub.py | 88 +++ .../python/common/ayon_api/server_api.py | 632 ++++++++++++++++-- .../vendor/python/common/ayon_api/version.py | 2 +- 5 files changed, 802 insertions(+), 56 deletions(-) diff --git a/openpype/vendor/python/common/ayon_api/__init__.py b/openpype/vendor/python/common/ayon_api/__init__.py index b1790d1fb6..6f791972cd 100644 --- a/openpype/vendor/python/common/ayon_api/__init__.py +++ b/openpype/vendor/python/common/ayon_api/__init__.py @@ -59,13 +59,31 @@ from ._api import ( get_addon_url, download_addon_private_file, + get_installers, + create_installer, + update_installer, + delete_installer, + download_installer, + upload_installer, + get_dependencies_info, update_dependency_info, + get_dependency_packages, + create_dependency_package, + update_dependency_package, + delete_dependency_package, download_dependency_package, upload_dependency_package, - delete_dependency_package, + get_bundles, + create_bundle, + update_bundle, + delete_bundle, + + get_info, + get_server_version, + get_server_version_tuple, get_user, get_users, @@ -87,9 +105,11 @@ from ._api import ( get_addons_project_settings, get_addons_settings, + get_project_names, get_projects, get_project, create_project, + update_project, delete_project, get_folder_by_id, @@ -218,13 +238,31 @@ __all__ = ( "get_addon_url", "download_addon_private_file", + "get_installers", + "create_installer", + "update_installer", + "delete_installer", + "download_installer", + "upload_installer", + "get_dependencies_info", "update_dependency_info", + "get_dependency_packages", + "create_dependency_package", + "update_dependency_package", + "delete_dependency_package", "download_dependency_package", "upload_dependency_package", - "delete_dependency_package", + "get_bundles", + "create_bundle", + "update_bundle", + "delete_bundle", + + "get_info", + "get_server_version", + "get_server_version_tuple", "get_user", "get_users", @@ -245,9 +283,11 @@ __all__ = ( "get_addons_project_settings", "get_addons_settings", + "get_project_names", "get_projects", "get_project", "create_project", + "update_project", "delete_project", "get_folder_by_id", diff --git a/openpype/vendor/python/common/ayon_api/_api.py b/openpype/vendor/python/common/ayon_api/_api.py index f351cd8102..f5a3a4787a 100644 --- a/openpype/vendor/python/common/ayon_api/_api.py +++ b/openpype/vendor/python/common/ayon_api/_api.py @@ -531,6 +531,53 @@ def download_addon_private_file(*args, **kwargs): return con.download_addon_private_file(*args, **kwargs) +def get_info(*args, **kwargs): + con = get_server_api_connection() + return con.get_info(*args, **kwargs) + + +def get_server_version(*args, **kwargs): + con = get_server_api_connection() + return con.get_server_version(*args, **kwargs) + + +def get_server_version_tuple(*args, **kwargs): + con = get_server_api_connection() + return con.get_server_version_tuple(*args, **kwargs) + + +# Installers +def get_installers(*args, **kwargs): + con = get_server_api_connection() + return con.get_installers(*args, **kwargs) + + +def create_installer(*args, **kwargs): + con = get_server_api_connection() + return con.create_installer(*args, **kwargs) + + +def update_installer(*args, **kwargs): + con = get_server_api_connection() + return con.update_installer(*args, **kwargs) + + +def delete_installer(*args, **kwargs): + con = get_server_api_connection() + return con.delete_installer(*args, **kwargs) + + +def download_installer(*args, **kwargs): + con = get_server_api_connection() + con.download_installer(*args, **kwargs) + + +def upload_installer(*args, **kwargs): + con = get_server_api_connection() + con.upload_installer(*args, **kwargs) + + +# Dependency packages def get_dependencies_info(*args, **kwargs): con = get_server_api_connection() return con.get_dependencies_info(*args, **kwargs) @@ -551,6 +598,21 @@ def upload_dependency_package(*args, **kwargs): return con.upload_dependency_package(*args, **kwargs) +def get_dependency_packages(*args, **kwargs): + con = get_server_api_connection() + return con.get_dependency_packages(*args, **kwargs) + + +def create_dependency_package(*args, **kwargs): + con = get_server_api_connection() + return con.create_dependency_package(*args, **kwargs) + + +def update_dependency_package(*args, **kwargs): + con = get_server_api_connection() + return con.update_dependency_package(*args, **kwargs) + + def delete_dependency_package(*args, **kwargs): con = get_server_api_connection() return con.delete_dependency_package(*args, **kwargs) @@ -561,6 +623,26 @@ def get_project_anatomy_presets(*args, **kwargs): return con.get_project_anatomy_presets(*args, **kwargs) +def get_bundles(*args, **kwargs): + con = get_server_api_connection() + return con.get_bundles(*args, **kwargs) + + +def create_bundle(*args, **kwargs): + con = get_server_api_connection() + return con.create_bundle(*args, **kwargs) + + +def update_bundle(*args, **kwargs): + con = get_server_api_connection() + return con.update_bundle(*args, **kwargs) + + +def delete_bundle(*args, **kwargs): + con = get_server_api_connection() + return con.delete_bundle(*args, **kwargs) + + def get_project_anatomy_preset(*args, **kwargs): con = get_server_api_connection() return con.get_project_anatomy_preset(*args, **kwargs) @@ -621,6 +703,11 @@ def get_addons_settings(*args, **kwargs): return con.get_addons_settings(*args, **kwargs) +def get_project_names(*args, **kwargs): + con = get_server_api_connection() + return con.get_project_names(*args, **kwargs) + + def get_project(*args, **kwargs): con = get_server_api_connection() return con.get_project(*args, **kwargs) @@ -801,6 +888,11 @@ def create_project( ) +def update_project(project_name, *args, **kwargs): + con = get_server_api_connection() + return con.update_project(project_name, *args, **kwargs) + + def delete_project(project_name): con = get_server_api_connection() return con.delete_project(project_name) diff --git a/openpype/vendor/python/common/ayon_api/entity_hub.py b/openpype/vendor/python/common/ayon_api/entity_hub.py index d71ce18839..ab1e2584d7 100644 --- a/openpype/vendor/python/common/ayon_api/entity_hub.py +++ b/openpype/vendor/python/common/ayon_api/entity_hub.py @@ -683,6 +683,82 @@ class EntityHub(object): "entityId": entity.id } + def _pre_commit_types_changes( + self, project_changes, orig_types, changes_key, post_changes + ): + """Compare changes of types on a project. + + Compare old and new types. Change project changes content if some old + types were removed. In that case the final change of types will + happen when all other entities have changed. + + Args: + project_changes (dict[str, Any]): Project changes. + orig_types (list[dict[str, Any]]): Original types. + changes_key (Literal[folderTypes, taskTypes]): Key of type changes + in project changes. + post_changes (dict[str, Any]): An object where post changes will + be stored. + """ + + if changes_key not in project_changes: + return + + new_types = project_changes[changes_key] + + orig_types_by_name = { + type_info["name"]: type_info + for type_info in orig_types + } + new_names = { + type_info["name"] + for type_info in new_types + } + diff_names = set(orig_types_by_name) - new_names + if not diff_names: + return + + # Create copy of folder type changes to post changes + # - post changes will be commited at the end + post_changes[changes_key] = copy.deepcopy(new_types) + + for type_name in diff_names: + new_types.append(orig_types_by_name[type_name]) + + def _pre_commit_project(self): + """Some project changes cannot be committed before hierarchy changes. + + It is not possible to change folder types or task types if there are + existing hierarchy items using the removed types. For that purposes + is first committed union of all old and new types and post changes + are prepared when all existing entities are changed. + + Returns: + dict[str, Any]: Changes that will be committed after hierarchy + changes. + """ + + project_changes = self.project_entity.changes + + post_changes = {} + if not project_changes: + return post_changes + + self._pre_commit_types_changes( + project_changes, + self.project_entity.get_orig_folder_types(), + "folderType", + post_changes + ) + self._pre_commit_types_changes( + project_changes, + self.project_entity.get_orig_task_types(), + "taskType", + post_changes + ) + self._connection.update_project(self.project_name, **project_changes) + return post_changes + def commit_changes(self): """Commit any changes that happened on entities. @@ -690,6 +766,9 @@ class EntityHub(object): Use Operations Session instead of known operations body. """ + post_project_changes = self._pre_commit_project() + self.project_entity.lock() + project_changes = self.project_entity.changes if project_changes: response = self._connection.patch( @@ -760,6 +839,9 @@ class EntityHub(object): self._connection.send_batch_operations( self.project_name, operations_body ) + if post_project_changes: + self._connection.update_project( + self.project_name, **post_project_changes) self.lock() @@ -1463,6 +1545,9 @@ class ProjectEntity(BaseEntity): parent = property(get_parent, set_parent) + def get_orig_folder_types(self): + return copy.deepcopy(self._orig_folder_types) + def get_folder_types(self): return copy.deepcopy(self._folder_types) @@ -1474,6 +1559,9 @@ class ProjectEntity(BaseEntity): new_folder_types.append(folder_type) self._folder_types = new_folder_types + def get_orig_task_types(self): + return copy.deepcopy(self._orig_task_types) + def get_task_types(self): return copy.deepcopy(self._task_types) diff --git a/openpype/vendor/python/common/ayon_api/server_api.py b/openpype/vendor/python/common/ayon_api/server_api.py index 253d590658..8cbb23ce0d 100644 --- a/openpype/vendor/python/common/ayon_api/server_api.py +++ b/openpype/vendor/python/common/ayon_api/server_api.py @@ -4,6 +4,7 @@ import io import json import logging import collections +import datetime import platform import copy import uuid @@ -70,6 +71,14 @@ PROJECT_NAME_REGEX = re.compile( "^[{}]+$".format(PROJECT_NAME_ALLOWED_SYMBOLS) ) +VERSION_REGEX = re.compile( + r"(?P0|[1-9]\d*)" + r"\.(?P0|[1-9]\d*)" + r"\.(?P0|[1-9]\d*)" + r"(?:-(?P[a-zA-Z\d\-.]*))?" + r"(?:\+(?P[a-zA-Z\d\-.]*))?" +) + def _get_description(response): if HTTPStatus is None: @@ -329,6 +338,8 @@ class ServerAPI(object): self._access_token_is_service = None self._token_is_valid = None self._server_available = None + self._server_version = None + self._server_version_tuple = None self._session = None @@ -631,12 +642,52 @@ class ServerAPI(object): Use this method for validation of token instead of 'get_user'. Returns: - Dict[str, Any]: Information from server. + dict[str, Any]: Information from server. """ response = self.get("info") return response.data + def get_server_version(self): + """Get server version. + + Version should match semantic version (https://semver.org/). + + Returns: + str: Server version. + """ + + if self._server_version is None: + self._server_version = self.get_info()["version"] + return self._server_version + + def get_server_version_tuple(self): + """Get server version as tuple. + + Version should match semantic version (https://semver.org/). + + This function only returns first three numbers of version. + + Returns: + Tuple[int, int, int, Union[str, None], Union[str, None]]: Server + version. + """ + + if self._server_version_tuple is None: + re_match = VERSION_REGEX.fullmatch( + self.get_server_version()) + self._server_version_tuple = ( + int(re_match.group("major")), + int(re_match.group("minor")), + int(re_match.group("patch")), + re_match.group("prerelease"), + re_match.group("buildmetadata") + ) + return self._server_version_tuple + + server_version = property(get_server_version) + server_version_tuple = property(get_server_version_tuple) + def _get_user_info(self): if self._access_token is None: return None @@ -869,7 +920,7 @@ class ServerAPI(object): event_id (str): Id of event. Returns: - Dict[str, Any]: Full event data. + dict[str, Any]: Full event data. """ response = self.get("events/{}".format(event_id)) @@ -902,7 +953,7 @@ class ServerAPI(object): for each event. Returns: - Generator[Dict[str, Any]]: Available events matching filters. + Generator[dict[str, Any]]: Available events matching filters. """ filters = {} @@ -1269,7 +1320,7 @@ class ServerAPI(object): Cache schema - How to find out it is outdated? Returns: - Dict[str, Any]: Full server schema. + dict[str, Any]: Full server schema. """ url = "{}/openapi.json".format(self._base_url) @@ -1287,7 +1338,7 @@ class ServerAPI(object): e.g. 'config' has just object definition without reference schema. Returns: - Dict[str, Any]: Component schemas. + dict[str, Any]: Component schemas. """ server_schema = self.get_server_schema() @@ -1395,7 +1446,7 @@ class ServerAPI(object): received. Returns: - Dict[str, Dict[str, Any]]: Attribute schemas that are available + dict[str, dict[str, Any]]: Attribute schemas that are available for entered entity type. """ attributes = self._entity_type_attributes_cache.get(entity_type) @@ -1563,6 +1614,148 @@ class ServerAPI(object): ) return dst_filepath + def get_installers(self, version=None, platform_name=None): + """Information about desktop application installers on server. + + Desktop application installers are helpers to download/update AYON + desktop application for artists. + + Args: + version (Optional[str]): Filter installers by version. + platform_name (Optional[str]): Filter installers by platform name. + + Returns: + list[dict[str, Any]]: + """ + + query_fields = [ + "{}={}".format(key, value) + for key, value in ( + ("version", version), + ("platform", platform_name), + ) + if value + ] + query = "" + if query_fields: + query = "?{}".format(",".join(query_fields)) + + response = self.get("desktop/installers{}".format(query)) + response.raise_for_status() + return response.data + + def create_installer( + self, + filename, + version, + python_version, + platform_name, + python_modules, + checksum, + checksum_type, + file_size, + sources=None, + ): + """Create new installer information on server. + + This step will create only metadata. Make sure to upload installer + to the server using 'upload_installer' method. + + Args: + filename (str): Installer filename. + version (str): Version of installer. + python_version (str): Version of Python. + platform_name (str): Name of platform. + python_modules (dict[str, str]): Python modules that are available + in installer. + checksum (str): Installer file checksum. + checksum_type (str): Type of checksum used to create checksum. + file_size (int): File size. + sources (Optional[list[dict[str, Any]]]): List of sources that + can be used to download file. + """ + + body = { + "filename": filename, + "version": version, + "pythonVersion": python_version, + "platform": platform_name, + "pythonModules": python_modules, + "checksum": checksum, + "checksumType": checksum_type, + "size": file_size, + } + if sources: + body["sources"] = sources + + response = self.post("desktop/installers", **body) + response.raise_for_status() + + def update_installer(self, filename, sources): + """Update installer information on server. + + Args: + filename (str): Installer filename. + sources (list[dict[str, Any]]): List of sources that + can be used to download file. Fully replaces existing sources. + """ + + response = self.post( + "desktop/installers/{}".format(filename), + sources=sources + ) + response.raise_for_status() + + def delete_installer(self, filename): + """Delete installer from server. + + Args: + filename (str): Installer filename. + """ + + response = self.delete("dekstop/installers/{}".format(filename)) + response.raise_for_status() + + def download_installer( + self, + filename, + dst_filepath, + chunk_size=None, + progress=None + ): + """Download installer file from server. + + Args: + filename (str): Installer filename. + dst_filepath (str): Destination filepath. + chunk_size (Optional[int]): Download chunk size. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + """ + + self.download_file( + "desktop/installers/{}".format(filename), + dst_filepath, + chunk_size=chunk_size, + progress=progress + ) + + def upload_installer(self, src_filepath, dst_filename, progress=None): + """Upload installer file to server. + + Args: + src_filepath (str): Source filepath. + dst_filename (str): Destination filename. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + """ + + self.upload_file( + "desktop/installers/{}".format(dst_filename), + src_filepath, + progress=progress + ) + def get_dependencies_info(self): """Information about dependency packages on server. @@ -1581,13 +1774,22 @@ class ServerAPI(object): "productionPackage": str } + Deprecated: + Deprecated since server version 0.2.1. Use + 'get_dependency_packages' instead. + Returns: dict[str, Any]: Information about dependency packages known for server. """ - result = self.get("dependencies") - return result.data + major, minor, patch, _, _ = self.server_version_tuple + if major == 0 and (minor < 2 or (minor == 2 and patch < 1)): + result = self.get("dependencies") + return result.data + packages = self.get_dependency_packages() + packages["productionPackage"] = None + return packages def update_dependency_info( self, @@ -1604,21 +1806,26 @@ class ServerAPI(object): The endpoint can be used to create or update dependency package. + + Deprecated: + Deprecated for server version 0.2.1. Use + 'create_dependency_pacakge' instead. + Args: name (str): Name of dependency package. - platform_name (Literal["windows", "linux", "darwin"]): Platform for - which is dependency package targeted. + platform_name (Literal["windows", "linux", "darwin"]): Platform + for which is dependency package targeted. size (int): Size of dependency package in bytes. checksum (str): Checksum of archive file where dependencies are. checksum_algorithm (Optional[str]): Algorithm used to calculate checksum. By default, is used 'md5' (defined by server). - supported_addons (Optional[Dict[str, str]]): Name of addons for + supported_addons (Optional[dict[str, str]]): Name of addons for which was the package created. '{"": "", ...}' - python_modules (Optional[Dict[str, str]]): Python modules in + python_modules (Optional[dict[str, str]]): Python modules in dependencies package. '{"": "", ...}' - sources (Optional[List[Dict[str, Any]]]): Information about + sources (Optional[list[dict[str, Any]]]): Information about sources where dependency package is available. """ @@ -1645,27 +1852,153 @@ class ServerAPI(object): raise ServerError("Failed to create/update dependency") return response.data + def get_dependency_packages(self): + """Information about dependency packages on server. + + To download dependency package, use 'download_dependency_package' + method and pass in 'filename'. + + Example data structure: + { + "packages": [ + { + "filename": str, + "platform": str, + "checksum": str, + "checksumAlgorithm": str, + "size": int, + "sources": list[dict[str, Any]], + "supportedAddons": dict[str, str], + "pythonModules": dict[str, str] + } + ] + } + + Returns: + dict[str, Any]: Information about dependency packages known for + server. + """ + + result = self.get("desktop/dependency_packages") + result.raise_for_status() + return result.data + + def create_dependency_package( + self, + filename, + python_modules, + source_addons, + installer_version, + checksum, + checksum_algorithm, + file_size, + sources=None, + platform_name=None, + ): + """Create dependency package on server. + + The package will be created on a server, it is also required to upload + the package archive file (using 'upload_dependency_package'). + + Args: + filename (str): Filename of dependency package. + python_modules (dict[str, str]): Python modules in dependency + package. + '{"": "", ...}' + source_addons (dict[str, str]): Name of addons for which is + dependency package created. + '{"": "", ...}' + installer_version (str): Version of installer for which was + package created. + checksum (str): Checksum of archive file where dependencies are. + checksum_algorithm (str): Algorithm used to calculate checksum. + file_size (Optional[int]): Size of file. + sources (Optional[list[dict[str, Any]]]): Information about + sources from where it is possible to get file. + platform_name (Optional[str]): Name of platform for which is + dependency package targeted. Default value is + current platform. + """ + + post_body = { + "filename": filename, + "pythonModules": python_modules, + "sourceAddons": source_addons, + "installerVersion": installer_version, + "checksum": checksum, + "checksumAlgorithm": checksum_algorithm, + "size": file_size, + "platform": platform_name or platform.system().lower(), + } + if sources: + post_body["sources"] = sources + + response = self.post("desktop/dependency_packages", **post_body) + response.raise_for_status() + + def update_dependency_package(self, filename, sources): + """Update dependency package metadata on server. + + Args: + filename (str): Filename of dependency package. + sources (list[dict[str, Any]]): Information about + sources from where it is possible to get file. Fully replaces + existing sources. + """ + + response = self.patch( + "desktop/dependency_packages/{}".format(filename), + sources=sources + ) + response.raise_for_status() + + def delete_dependency_package(self, filename, platform_name=None): + """Remove dependency package for specific platform. + + Args: + filename (str): Filename of dependency package. Or name of package + for server version 0.2.0 or lower. + platform_name (Optional[str]): Which platform of the package + should be removed. Current platform is used if not passed. + Deprecated since version 0.2.1 + """ + + major, minor, patch, _, _ = self.server_version_tuple + if major == 0 and (minor < 2 or (minor == 2 and patch < 1)): + if platform_name is None: + platform_name = platform.system().lower() + url = "dependencies/{}/{}".format(filename, platform_name) + else: + url = "desktop/dependency_packages/{}".format(filename) + + response = self.delete(url) + if response.status != 200: + raise ServerError("Failed to delete dependency file") + return response.data + def download_dependency_package( self, - package_name, + src_filename, dst_directory, - filename, + dst_filename, platform_name=None, chunk_size=None, progress=None, ): """Download dependency package from server. - This method requires to have authorized token available. The package is - only downloaded. + This method requires to have authorized token available. The package + is only downloaded. Args: - package_name (str): Name of package to download. + src_filename (str): Filename of dependency pacakge. + For server version 0.2.0 and lower it is name of package + to download. dst_directory (str): Where the file should be downloaded. - filename (str): Name of destination filename. + dst_filename (str): Name of destination filename. platform_name (Optional[str]): Name of platform for which the dependency package is targeted. Default value is - current platform. + current platform. Deprecated since server version 0.2.1. chunk_size (Optional[int]): Download chunk size. progress (Optional[TransferProgress]): Object that gives ability to track download progress. @@ -1673,12 +2006,18 @@ class ServerAPI(object): Returns: str: Filepath to downloaded file. """ - if platform_name is None: - platform_name = platform.system().lower() - package_filepath = os.path.join(dst_directory, filename) + major, minor, patch, _, _ = self.server_version_tuple + if major == 0 and (minor < 2 or (minor == 2 and patch < 1)): + if platform_name is None: + platform_name = platform.system().lower() + url = "dependencies/{}/{}".format(src_filename, platform_name) + else: + url = "desktop/dependency_packages/{}".format(src_filename) + + package_filepath = os.path.join(dst_directory, dst_filename) self.download_file( - "dependencies/{}/{}".format(package_name, platform_name), + url, package_filepath, chunk_size=chunk_size, progress=progress @@ -1686,47 +2025,170 @@ class ServerAPI(object): return package_filepath def upload_dependency_package( - self, filepath, package_name, platform_name=None, progress=None + self, src_filepath, dst_filename, platform_name=None, progress=None ): """Upload dependency package to server. Args: - filepath (str): Path to a package file. - package_name (str): Name of package. Must be unique. + src_filepath (str): Path to a package file. + dst_filename (str): Dependency package filename or name of package + for server version 0.2.0 or lower. Must be unique. platform_name (Optional[str]): For which platform is the - package targeted. + package targeted. Deprecated since server version 0.2.1. progress (Optional[TransferProgress]): Object to keep track about upload state. """ - if platform_name is None: - platform_name = platform.system().lower() + major, minor, patch, _, _ = self.server_version_tuple + if major == 0 and (minor < 2 or (minor == 2 and patch < 1)): + if platform_name is None: + platform_name = platform.system().lower() + url = "dependencies/{}/{}".format(dst_filename, platform_name) + else: + url = "desktop/dependency_packages/{}".format(dst_filename) - self.upload_file( - "dependencies/{}/{}".format(package_name, platform_name), - filepath, - progress=progress - ) + self.upload_file(url, src_filepath, progress=progress) - def delete_dependency_package(self, package_name, platform_name=None): - """Remove dependency package for specific platform. + def create_dependency_package_basename(self, platform_name=None): + """Create basename for dependency package file. Args: - package_name (str): Name of package to remove. - platform_name (Optional[str]): Which platform of the package - should be removed. Current platform is used if not passed. + platform_name (Optional[str]): Name of platform for which the + bundle is targeted. Default value is current platform. + + Returns: + str: Dependency package name with timestamp and platform. """ if platform_name is None: platform_name = platform.system().lower() - response = self.delete( - "dependencies/{}/{}".format(package_name, platform_name), - ) - if response.status != 200: - raise ServerError("Failed to delete dependency file") + now_date = datetime.datetime.now() + time_stamp = now_date.strftime("%y%m%d%H%M") + return "ayon_{}_{}".format(time_stamp, platform_name) + + def get_bundles(self): + """Server bundles with basic information. + + Example output: + { + "bundles": [ + { + "name": "my_bundle", + "createdAt": "2023-06-12T15:37:02.420260", + "installerVersion": "1.0.0", + "addons": { + "core": "1.2.3" + }, + "dependencyPackages": { + "windows": "a_windows_package123.zip", + "linux": "a_linux_package123.zip", + "darwin": "a_mac_package123.zip" + }, + "isProduction": False, + "isStaging": False + } + ], + "productionBundle": "my_bundle", + "stagingBundle": "test_bundle" + } + + Returns: + dict[str, Any]: Server bundles with basic information. + """ + + response = self.get("desktop/bundles") + response.raise_for_status() return response.data + def create_bundle( + self, + name, + addon_versions, + installer_version, + dependency_packages=None, + is_production=None, + is_staging=None + ): + """Create bundle on server. + + Bundle cannot be changed once is created. Only isProduction, isStaging + and dependency packages can change after creation. + + Args: + name (str): Name of bundle. + addon_versions (dict[str, str]): Addon versions. + installer_version (Union[str, None]): Installer version. + dependency_packages (Optional[dict[str, str]]): Dependency + package names. Keys are platform names and values are name of + packages. + is_production (Optional[bool]): Bundle will be marked as + production. + is_staging (Optional[bool]): Bundle will be marked as staging. + """ + + body = { + "name": name, + "installerVersion": installer_version, + "addons": addon_versions, + } + for key, value in ( + ("dependencyPackages", dependency_packages), + ("isProduction", is_production), + ("isStaging", is_staging), + ): + if value is not None: + body[key] = value + + response = self.post("desktop/bundles", **body) + response.raise_for_status() + + def update_bundle( + self, + bundle_name, + dependency_packages=None, + is_production=None, + is_staging=None + ): + """Update bundle on server. + + Dependency packages can be update only for single platform. Others + will be left untouched. Use 'None' value to unset dependency package + from bundle. + + Args: + bundle_name (str): Name of bundle. + dependency_packages (Optional[dict[str, str]]): Dependency pacakge + names that should be used with the bundle. + is_production (Optional[bool]): Bundle will be marked as + production. + is_staging (Optional[bool]): Bundle will be marked as staging. + """ + + body = { + key: value + for key, value in ( + ("dependencyPackages", dependency_packages), + ("isProduction", is_production), + ("isStaging", is_staging), + ) + if value is not None + } + response = self.patch( + "desktop/bundles/{}".format(bundle_name), **body + ) + response.raise_for_status() + + def delete_bundle(self, bundle_name): + """Delete bundle from server. + + Args: + bundle_name (str): Name of bundle to delete. + """ + + response = self.delete("desktop/bundles/{}".format(bundle_name)) + response.raise_for_status() + # Anatomy presets def get_project_anatomy_presets(self): """Anatomy presets available on server. @@ -2150,7 +2612,7 @@ class ServerAPI(object): project_name (str): Name of project. Returns: - Union[Dict[str, Any], None]: Project entity data or 'None' if + Union[dict[str, Any], None]: Project entity data or 'None' if project was not found. """ @@ -2174,7 +2636,7 @@ class ServerAPI(object): are returned if 'None' is passed. Returns: - Generator[Dict[str, Any]]: Available projects. + Generator[dict[str, Any]]: Available projects. """ for project_name in self.get_project_names(active, library): @@ -2235,7 +2697,7 @@ class ServerAPI(object): are returned if 'None' is passed. Returns: - List[str]: List of available project names. + list[str]: List of available project names. """ query_keys = {} @@ -2276,7 +2738,7 @@ class ServerAPI(object): not explicitly set on entity will have 'None' value. Returns: - Generator[Dict[str, Any]]: Queried projects. + Generator[dict[str, Any]]: Queried projects. """ if fields is None: @@ -2316,7 +2778,7 @@ class ServerAPI(object): not explicitly set on entity will have 'None' value. Returns: - Union[Dict[str, Any], None]: Project entity data or None + Union[dict[str, Any], None]: Project entity data or None if project was not found. """ @@ -3112,7 +3574,7 @@ class ServerAPI(object): not explicitly set on entity will have 'None' value. Returns: - Generator[Dict[str, Any]]: Queried version entities. + Generator[dict[str, Any]]: Queried version entities. """ if not fields: @@ -3570,7 +4032,7 @@ class ServerAPI(object): not explicitly set on entity will have 'None' value. Returns: - Generator[Dict[str, Any]]: Queried representation entities. + Generator[dict[str, Any]]: Queried representation entities. """ if not fields: @@ -4253,7 +4715,7 @@ class ServerAPI(object): ValueError: When project name already exists. Returns: - Dict[str, Any]: Created project entity. + dict[str, Any]: Created project entity. """ if self.get_project(project_name): @@ -4286,6 +4748,70 @@ class ServerAPI(object): return self.get_project(project_name) + def update_project( + self, + project_name, + library=None, + folder_types=None, + task_types=None, + link_types=None, + statuses=None, + tags=None, + config=None, + attrib=None, + data=None, + active=None, + project_code=None, + **changes + ): + """Update project entity on server. + + Args: + project_name (str): Name of project. + library (Optional[bool]): Change library state. + folder_types (Optional[list[dict[str, Any]]]): Folder type + definitions. + task_types (Optional[list[dict[str, Any]]]): Task type + definitions. + link_types (Optional[list[dict[str, Any]]]): Link type + definitions. + statuses (Optional[list[dict[str, Any]]]): Status definitions. + tags (Optional[list[dict[str, Any]]]): List of tags available to + set on entities. + config (Optional[dict[dict[str, Any]]]): Project anatomy config + with templates and roots. + attrib (Optional[dict[str, Any]]): Project attributes to change. + data (Optional[dict[str, Any]]): Custom data of a project. This + value will 100% override project data. + active (Optional[bool]): Change active state of a project. + project_code (Optional[str]): Change project code. Not recommended + during production. + **changes: Other changed keys based on Rest API documentation. + """ + + changes.update({ + key: value + for key, value in ( + ("library", library), + ("folderTypes", folder_types), + ("taskTypes", task_types), + ("linkTypes", link_types), + ("statuses", statuses), + ("tags", tags), + ("config", config), + ("attrib", attrib), + ("data", data), + ("active", active), + ("code", project_code), + ) + if value is not None + }) + response = self.patch( + "projects/{}".format(project_name), + **changes + ) + response.raise_for_status() + def delete_project(self, project_name): """Delete project from server. diff --git a/openpype/vendor/python/common/ayon_api/version.py b/openpype/vendor/python/common/ayon_api/version.py index d464469cf7..1a1769eeb8 100644 --- a/openpype/vendor/python/common/ayon_api/version.py +++ b/openpype/vendor/python/common/ayon_api/version.py @@ -1,2 +1,2 @@ """Package declaring Python API for Ayon server.""" -__version__ = "0.2.0" +__version__ = "0.2.1"