From 5f562ab63164655fc757d470a4e4dcd6bcbf96a6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:21:16 +0200 Subject: [PATCH 01/53] added more methods to base class --- ayon_api/_base.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/ayon_api/_base.py b/ayon_api/_base.py index b3863a2c4..5d7757e0b 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -1,11 +1,21 @@ import typing -from typing import Set +from typing import Set, Optional + +import requests + +from .utils import TransferProgress, RequestType if typing.TYPE_CHECKING: from .typing import AnyEntityDict class _BaseServerAPI: + def get_base_url(self) -> str: + raise NotImplementedError() + + def get_rest_url(self) -> str: + raise NotImplementedError() + def get(self, entrypoint: str, **kwargs): raise NotImplementedError() @@ -42,5 +52,24 @@ def get_default_settings_variant(self) -> str: def get_default_fields_for_type(self, entity_type: str) -> Set[str]: raise NotImplementedError() + def upload_file( + self, + endpoint: str, + filepath: str, + progress: Optional[TransferProgress] = None, + request_type: Optional[RequestType] = None, + **kwargs + ) -> requests.Response: + raise NotImplementedError() + + def download_file( + self, + endpoint: str, + filepath: str, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, + ) -> TransferProgress: + raise NotImplementedError() + def _convert_entity_data(self, entity: "AnyEntityDict"): raise NotImplementedError() From 1d5ef253437bee36f8e6ad53ac5b95446cb302f8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:21:43 +0200 Subject: [PATCH 02/53] moved request type to utils --- ayon_api/__init__.py | 4 ++-- ayon_api/server_api.py | 18 ++---------------- ayon_api/utils.py | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 719c54a1c..0baa87a5a 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -1,5 +1,6 @@ from .version import __version__ from .utils import ( + RequestTypes, TransferProgress, slugify_string, create_dependency_package_basename, @@ -12,7 +13,6 @@ SortOrder, ) from .server_api import ( - RequestTypes, ServerAPI, ) @@ -267,6 +267,7 @@ __all__ = ( "__version__", + "RequestTypes", "TransferProgress", "slugify_string", "create_dependency_package_basename", @@ -278,7 +279,6 @@ "abort_web_action_event", "SortOrder", - "RequestTypes", "ServerAPI", "GlobalServerAPI", diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 4fecb75da..361a00a12 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -83,6 +83,8 @@ UnsupportedServerVersion, ) from .utils import ( + RequestType, + RequestTypes, RepresentationParents, RepresentationHierarchy, prepare_query_string, @@ -184,22 +186,6 @@ def _get_description(response): return HTTPStatus(response.status).description -class RequestType: - def __init__(self, name: str): - self.name: str = name - - def __hash__(self): - return self.name.__hash__() - - -class RequestTypes: - get = RequestType("GET") - post = RequestType("POST") - put = RequestType("PUT") - patch = RequestType("PATCH") - delete = RequestType("DELETE") - - class RestApiResponse(object): """API Response.""" diff --git a/ayon_api/utils.py b/ayon_api/utils.py index 6eb1941c7..c075fdf74 100644 --- a/ayon_api/utils.py +++ b/ayon_api/utils.py @@ -62,6 +62,22 @@ def parse_value(cls, value, default=None): return default +class RequestType: + def __init__(self, name: str): + self.name: str = name + + def __hash__(self): + return self.name.__hash__() + + +class RequestTypes: + get = RequestType("GET") + post = RequestType("POST") + put = RequestType("PUT") + patch = RequestType("PATCH") + delete = RequestType("DELETE") + + def get_default_timeout() -> float: """Default value for requests timeout. From 8d335974ba60bdcebc7c22c06012cb85b997b947 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:22:14 +0200 Subject: [PATCH 03/53] moved addons logic to separate class --- ayon_api/_addons.py | 217 +++++++++++++++++++++++++++++++++++++++++ ayon_api/server_api.py | 206 ++------------------------------------ 2 files changed, 223 insertions(+), 200 deletions(-) create mode 100644 ayon_api/_addons.py diff --git a/ayon_api/_addons.py b/ayon_api/_addons.py new file mode 100644 index 000000000..10337ec50 --- /dev/null +++ b/ayon_api/_addons.py @@ -0,0 +1,217 @@ +import os +import typing +from typing import Optional + +from .utils import ( + RequestTypes, + prepare_query_string, + TransferProgress, +) +from ._base import _BaseServerAPI + +if typing.TYPE_CHECKING: + from .typing import AddonsInfoDict + + +class _AddonsAPI(_BaseServerAPI): + def get_addon_endpoint( + self, + addon_name: str, + addon_version: str, + *subpaths: str, + ) -> str: + """Calculate endpoint to addon route. + + Examples: + >>> from ayon_api import ServerAPI + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'addons/example/1.0.0/private/my.zip' + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. + + Returns: + str: Final url. + + """ + ending = "" + if subpaths: + ending = f"/{'/'.join(subpaths)}" + return f"addons/{addon_name}/{addon_version}{ending}" + + def get_addons_info(self, details: bool = True) -> "AddonsInfoDict": + """Get information about addons available on server. + + Args: + details (Optional[bool]): Detailed data with information how + to get client code. + + """ + endpoint = "addons" + if details: + endpoint += "?details=1" + response = self.get(endpoint) + response.raise_for_status() + return response.data + + def get_addon_url( + self, + addon_name: str, + addon_version: str, + *subpaths: str, + use_rest: bool = True, + ) -> str: + """Calculate url to addon route. + + Examples: + + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. + use_rest (Optional[bool]): Use rest endpoint. + + Returns: + str: Final url. + + """ + endpoint = self.get_addon_endpoint( + addon_name, addon_version, *subpaths + ) + url_base = self.get_base_url() if use_rest else self.get_rest_url() + return f"{url_base}/{endpoint}" + + def delete_addon( + self, + addon_name: str, + purge: Optional[bool] = None, + ) -> None: + """Delete addon from server. + + Delete all versions of addon from server. + + Args: + addon_name (str): Addon name. + purge (Optional[bool]): Purge all data related to the addon. + + """ + if purge is not None: + purge = "true" if purge else "false" + query = prepare_query_string({"purge": purge}) + + response = self.delete(f"addons/{addon_name}{query}") + response.raise_for_status() + + def delete_addon_version( + self, + addon_name: str, + addon_version: str, + purge: Optional[bool] = None, + ) -> None: + """Delete addon version from server. + + Delete all versions of addon from server. + + Args: + addon_name (str): Addon name. + addon_version (str): Addon version. + purge (Optional[bool]): Purge all data related to the addon. + + """ + if purge is not None: + purge = "true" if purge else "false" + query = prepare_query_string({"purge": purge}) + response = self.delete(f"addons/{addon_name}/{addon_version}{query}") + response.raise_for_status() + + def upload_addon_zip( + self, + src_filepath: str, + progress: Optional[TransferProgress] = None, + ): + """Upload addon zip file to server. + + File is validated on server. If it is valid, it is installed. It will + create an event job which can be tracked (tracking part is not + implemented yet). + + Example output:: + + {'eventId': 'a1bfbdee27c611eea7580242ac120003'} + + Args: + src_filepath (str): Path to a zip file. + progress (Optional[TransferProgress]): Object to keep track about + upload state. + + Returns: + dict[str, Any]: Response data from server. + + """ + response = self.upload_file( + "addons/install", + src_filepath, + progress=progress, + request_type=RequestTypes.post, + ) + return response.json() + + def download_addon_private_file( + self, + addon_name: str, + addon_version: str, + filename: str, + destination_dir: str, + destination_filename: Optional[str] = None, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, + ) -> str: + """Download a file from addon private files. + + This method requires to have authorized token available. Private files + are not under '/api' restpoint. + + Args: + addon_name (str): Addon name. + addon_version (str): Addon version. + filename (str): Filename in private folder on server. + destination_dir (str): Where the file should be downloaded. + destination_filename (Optional[str]): Name of destination + filename. Source filename is used if not passed. + chunk_size (Optional[int]): Download chunk size. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + + Returns: + str: Filepath to downloaded file. + + """ + if not destination_filename: + destination_filename = filename + dst_filepath = os.path.join(destination_dir, destination_filename) + # Filename can contain "subfolders" + dst_dirpath = os.path.dirname(dst_filepath) + os.makedirs(dst_dirpath, exist_ok=True) + + endpoint = self.get_addon_endpoint( + addon_name, + addon_version, + "private", + filename + ) + url = f"{self.get_base_url()}/{endpoint}" + self.download_file( + url, dst_filepath, chunk_size=chunk_size, progress=progress + ) + return dst_filepath diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 361a00a12..b8140a2af 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -103,6 +103,7 @@ get_machine_name, ) from ._actions import _ActionsAPI +from ._addons import _AddonsAPI from ._lists import _ListsAPI if typing.TYPE_CHECKING: @@ -116,7 +117,6 @@ AttributeSchemaDataDict, AttributeSchemaDict, AttributesSchemaDict, - AddonsInfoDict, InstallersInfoDict, DependencyPackagesDict, DevBundleAddonInfoDict, @@ -412,7 +412,11 @@ def as_user(self, username): self._last_user = new_last_user -class ServerAPI(_ListsAPI, _ActionsAPI): +class ServerAPI( + _ListsAPI, + _ActionsAPI, + _AddonsAPI, +): """Base handler of connection to server. Requires url to server which is used as base for api and graphql calls. @@ -2782,133 +2786,6 @@ def get_default_fields_for_type(self, entity_type: str) -> Set[str]: | self.get_attributes_fields_for_type(entity_type) ) - def get_addons_info(self, details: bool = True) -> "AddonsInfoDict": - """Get information about addons available on server. - - Args: - details (Optional[bool]): Detailed data with information how - to get client code. - - """ - endpoint = "addons" - if details: - endpoint += "?details=1" - response = self.get(endpoint) - response.raise_for_status() - return response.data - - def get_addon_endpoint( - self, - addon_name: str, - addon_version: str, - *subpaths: str, - ) -> str: - """Calculate endpoint to addon route. - - Examples: - - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'addons/example/1.0.0/private/my.zip' - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. - - Returns: - str: Final url. - - """ - ending = "" - if subpaths: - ending = "/{}".format("/".join(subpaths)) - return f"addons/{addon_name}/{addon_version}{ending}" - - def get_addon_url( - self, - addon_name: str, - addon_version: str, - *subpaths: str, - use_rest: bool = True, - ) -> str: - """Calculate url to addon route. - - Examples: - - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. - use_rest (Optional[bool]): Use rest endpoint. - - Returns: - str: Final url. - - """ - endpoint = self.get_addon_endpoint( - addon_name, addon_version, *subpaths - ) - url_base = self._base_url if use_rest else self._rest_url - return f"{url_base}/{endpoint}" - - def download_addon_private_file( - self, - addon_name: str, - addon_version: str, - filename: str, - destination_dir: str, - destination_filename: Optional[str] = None, - chunk_size: Optional[int] = None, - progress: Optional[TransferProgress] = None, - ) -> str: - """Download a file from addon private files. - - This method requires to have authorized token available. Private files - are not under '/api' restpoint. - - Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - filename (str): Filename in private folder on server. - destination_dir (str): Where the file should be downloaded. - destination_filename (Optional[str]): Name of destination - filename. Source filename is used if not passed. - chunk_size (Optional[int]): Download chunk size. - progress (Optional[TransferProgress]): Object that gives ability - to track download progress. - - Returns: - str: Filepath to downloaded file. - - """ - if not destination_filename: - destination_filename = filename - dst_filepath = os.path.join(destination_dir, destination_filename) - # Filename can contain "subfolders" - dst_dirpath = os.path.dirname(dst_filepath) - os.makedirs(dst_dirpath, exist_ok=True) - - endpoint = self.get_addon_endpoint( - addon_name, - addon_version, - "private", - filename - ) - url = f"{self._base_url}/{endpoint}" - self.download_file( - url, dst_filepath, chunk_size=chunk_size, progress=progress - ) - return dst_filepath - def get_installers( self, version: Optional[str] = None, @@ -3284,77 +3161,6 @@ def upload_dependency_package( route = self._get_dependency_package_route(dst_filename) self.upload_file(route, src_filepath, progress=progress) - def delete_addon(self, addon_name: str, purge: Optional[bool] = None): - """Delete addon from server. - - Delete all versions of addon from server. - - Args: - addon_name (str): Addon name. - purge (Optional[bool]): Purge all data related to the addon. - - """ - if purge is not None: - purge = "true" if purge else "false" - query = prepare_query_string({"purge": purge}) - - response = self.delete(f"addons/{addon_name}{query}") - response.raise_for_status() - - def delete_addon_version( - self, - addon_name: str, - addon_version: str, - purge: Optional[bool] = None, - ): - """Delete addon version from server. - - Delete all versions of addon from server. - - Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - purge (Optional[bool]): Purge all data related to the addon. - - """ - if purge is not None: - purge = "true" if purge else "false" - query = prepare_query_string({"purge": purge}) - response = self.delete(f"addons/{addon_name}/{addon_version}{query}") - response.raise_for_status() - - def upload_addon_zip( - self, - src_filepath: str, - progress: Optional[TransferProgress] = None, - ): - """Upload addon zip file to server. - - File is validated on server. If it is valid, it is installed. It will - create an event job which can be tracked (tracking part is not - implemented yet). - - Example output:: - - {'eventId': 'a1bfbdee27c611eea7580242ac120003'} - - Args: - src_filepath (str): Path to a zip file. - progress (Optional[TransferProgress]): Object to keep track about - upload state. - - Returns: - dict[str, Any]: Response data from server. - - """ - response = self.upload_file( - "addons/install", - src_filepath, - progress=progress, - request_type=RequestTypes.post, - ) - return response.json() - def get_bundles(self) -> "BundlesInfoDict": """Server bundles with basic information. From b9011f8b39cc6be9cc65f314247ce1b6330cd8ad Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:22:50 +0200 Subject: [PATCH 04/53] add addons api to automated api --- automated_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/automated_api.py b/automated_api.py index 16b3ed9cb..5b09a1d4e 100644 --- a/automated_api.py +++ b/automated_api.py @@ -34,6 +34,7 @@ ServerAPI, _PLACEHOLDER, _ActionsAPI, + _AddonsAPI, _ListsAPI, ) from ayon_api.utils import NOT_SET # noqa: E402 @@ -295,6 +296,7 @@ def prepare_api_functions(api_globals): functions = [] _items = list(ServerAPI.__dict__.items()) _items.extend(_ActionsAPI.__dict__.items()) + _items.extend(_AddonsAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) for attr_name, attr in _items: if ( From ee6ec100f9e6eef208c84fca17a85f649e169c2f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:41:22 +0200 Subject: [PATCH 05/53] updated order of functions in public api --- ayon_api/__init__.py | 19 ++- ayon_api/_api.py | 394 +++++++++++++++++++++---------------------- 2 files changed, 209 insertions(+), 204 deletions(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 0baa87a5a..90fb4374c 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -95,10 +95,6 @@ get_attributes_for_type, get_attributes_fields_for_type, get_default_fields_for_type, - get_addons_info, - get_addon_endpoint, - get_addon_url, - download_addon_private_file, get_installers, create_installer, update_installer, @@ -111,9 +107,6 @@ delete_dependency_package, download_dependency_package, upload_dependency_package, - delete_addon, - delete_addon_version, - upload_addon_zip, get_bundles, create_bundle, update_bundle, @@ -249,6 +242,13 @@ set_action_config, take_action, abort_action, + get_addon_endpoint, + get_addons_info, + get_addon_url, + delete_addon, + delete_addon_version, + upload_addon_zip, + download_addon_private_file, get_entity_lists, get_entity_list_rest, get_entity_list_by_id, @@ -261,6 +261,11 @@ update_entity_list_items, update_entity_list_item, delete_entity_list_item, + get_rest_project, + get_rest_projects, + get_project_names, + get_projects, + get_project, ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 585c6d44d..b6ea440cb 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -1758,130 +1758,6 @@ def get_default_fields_for_type( ) -def get_addons_info( - details: bool = True, -) -> "AddonsInfoDict": - """Get information about addons available on server. - - Args: - details (Optional[bool]): Detailed data with information how - to get client code. - - """ - con = get_server_api_connection() - return con.get_addons_info( - details=details, - ) - - -def get_addon_endpoint( - addon_name: str, - addon_version: str, - *subpaths, -) -> str: - """Calculate endpoint to addon route. - - Examples: - - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'addons/example/1.0.0/private/my.zip' - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. - - Returns: - str: Final url. - - """ - con = get_server_api_connection() - return con.get_addon_endpoint( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, - ) - - -def get_addon_url( - addon_name: str, - addon_version: str, - *subpaths, - use_rest: bool = True, -) -> str: - """Calculate url to addon route. - - Examples: - - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. - use_rest (Optional[bool]): Use rest endpoint. - - Returns: - str: Final url. - - """ - con = get_server_api_connection() - return con.get_addon_url( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, - use_rest=use_rest, - ) - - -def download_addon_private_file( - addon_name: str, - addon_version: str, - filename: str, - destination_dir: str, - destination_filename: Optional[str] = None, - chunk_size: Optional[int] = None, - progress: Optional[TransferProgress] = None, -) -> str: - """Download a file from addon private files. - - This method requires to have authorized token available. Private files - are not under '/api' restpoint. - - Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - filename (str): Filename in private folder on server. - destination_dir (str): Where the file should be downloaded. - destination_filename (Optional[str]): Name of destination - filename. Source filename is used if not passed. - chunk_size (Optional[int]): Download chunk size. - progress (Optional[TransferProgress]): Object that gives ability - to track download progress. - - Returns: - str: Filepath to downloaded file. - - """ - con = get_server_api_connection() - return con.download_addon_private_file( - addon_name=addon_name, - addon_version=addon_version, - filename=filename, - destination_dir=destination_dir, - destination_filename=destination_filename, - chunk_size=chunk_size, - progress=progress, - ) - - def get_installers( version: Optional[str] = None, platform_name: Optional[str] = None, @@ -2231,79 +2107,6 @@ def upload_dependency_package( ) -def delete_addon( - addon_name: str, - purge: Optional[bool] = None, -): - """Delete addon from server. - - Delete all versions of addon from server. - - Args: - addon_name (str): Addon name. - purge (Optional[bool]): Purge all data related to the addon. - - """ - con = get_server_api_connection() - return con.delete_addon( - addon_name=addon_name, - purge=purge, - ) - - -def delete_addon_version( - addon_name: str, - addon_version: str, - purge: Optional[bool] = None, -): - """Delete addon version from server. - - Delete all versions of addon from server. - - Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - purge (Optional[bool]): Purge all data related to the addon. - - """ - con = get_server_api_connection() - return con.delete_addon_version( - addon_name=addon_name, - addon_version=addon_version, - purge=purge, - ) - - -def upload_addon_zip( - src_filepath: str, - progress: Optional[TransferProgress] = None, -): - """Upload addon zip file to server. - - File is validated on server. If it is valid, it is installed. It will - create an event job which can be tracked (tracking part is not - implemented yet). - - Example output:: - - {'eventId': 'a1bfbdee27c611eea7580242ac120003'} - - Args: - src_filepath (str): Path to a zip file. - progress (Optional[TransferProgress]): Object to keep track about - upload state. - - Returns: - dict[str, Any]: Response data from server. - - """ - con = get_server_api_connection() - return con.upload_addon_zip( - src_filepath=src_filepath, - progress=progress, - ) - - def get_bundles() -> "BundlesInfoDict": """Server bundles with basic information. @@ -6957,6 +6760,203 @@ def abort_action( ) +def get_addon_endpoint( + addon_name: str, + addon_version: str, + *subpaths, +) -> str: + """Calculate endpoint to addon route. + + Examples: + >>> from ayon_api import ServerAPI + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'addons/example/1.0.0/private/my.zip' + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. + + Returns: + str: Final url. + + """ + con = get_server_api_connection() + return con.get_addon_endpoint( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, + ) + + +def get_addons_info( + details: bool = True, +) -> "AddonsInfoDict": + """Get information about addons available on server. + + Args: + details (Optional[bool]): Detailed data with information how + to get client code. + + """ + con = get_server_api_connection() + return con.get_addons_info( + details=details, + ) + + +def get_addon_url( + addon_name: str, + addon_version: str, + *subpaths, + use_rest: bool = True, +) -> str: + """Calculate url to addon route. + + Examples: + + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. + use_rest (Optional[bool]): Use rest endpoint. + + Returns: + str: Final url. + + """ + con = get_server_api_connection() + return con.get_addon_url( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, + use_rest=use_rest, + ) + + +def delete_addon( + addon_name: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon from server. + + Delete all versions of addon from server. + + Args: + addon_name (str): Addon name. + purge (Optional[bool]): Purge all data related to the addon. + + """ + con = get_server_api_connection() + return con.delete_addon( + addon_name=addon_name, + purge=purge, + ) + + +def delete_addon_version( + addon_name: str, + addon_version: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon version from server. + + Delete all versions of addon from server. + + Args: + addon_name (str): Addon name. + addon_version (str): Addon version. + purge (Optional[bool]): Purge all data related to the addon. + + """ + con = get_server_api_connection() + return con.delete_addon_version( + addon_name=addon_name, + addon_version=addon_version, + purge=purge, + ) + + +def upload_addon_zip( + src_filepath: str, + progress: Optional[TransferProgress] = None, +): + """Upload addon zip file to server. + + File is validated on server. If it is valid, it is installed. It will + create an event job which can be tracked (tracking part is not + implemented yet). + + Example output:: + + {'eventId': 'a1bfbdee27c611eea7580242ac120003'} + + Args: + src_filepath (str): Path to a zip file. + progress (Optional[TransferProgress]): Object to keep track about + upload state. + + Returns: + dict[str, Any]: Response data from server. + + """ + con = get_server_api_connection() + return con.upload_addon_zip( + src_filepath=src_filepath, + progress=progress, + ) + + +def download_addon_private_file( + addon_name: str, + addon_version: str, + filename: str, + destination_dir: str, + destination_filename: Optional[str] = None, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, +) -> str: + """Download a file from addon private files. + + This method requires to have authorized token available. Private files + are not under '/api' restpoint. + + Args: + addon_name (str): Addon name. + addon_version (str): Addon version. + filename (str): Filename in private folder on server. + destination_dir (str): Where the file should be downloaded. + destination_filename (Optional[str]): Name of destination + filename. Source filename is used if not passed. + chunk_size (Optional[int]): Download chunk size. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + + Returns: + str: Filepath to downloaded file. + + """ + con = get_server_api_connection() + return con.download_addon_private_file( + addon_name=addon_name, + addon_version=addon_version, + filename=filename, + destination_dir=destination_dir, + destination_filename=destination_filename, + chunk_size=chunk_size, + progress=progress, + ) + + def get_entity_lists( project_name: str, *, From 4daab3e399b6de520bdb8776b16b0c58bd479e85 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 15:30:54 +0200 Subject: [PATCH 06/53] move 'fill_own_attribs' to utils --- ayon_api/server_api.py | 20 +------------------- ayon_api/utils.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index b8140a2af..294ed9a52 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -101,6 +101,7 @@ get_media_mime_type, SortOrder, get_machine_name, + fill_own_attribs, ) from ._actions import _ActionsAPI from ._addons import _AddonsAPI @@ -309,25 +310,6 @@ def __repr__(self): return f"<{self.__class__.__name__}>" -def fill_own_attribs(entity): - if not entity or not entity.get("attrib"): - return - - attributes = entity.get("ownAttrib") - if attributes is None: - return - attributes = set(attributes) - - own_attrib = {} - entity["ownAttrib"] = own_attrib - - for key, value in entity["attrib"].items(): - if key not in attributes: - own_attrib[key] = None - else: - own_attrib[key] = copy.deepcopy(value) - - class _AsUserStack: """Handle stack of users used over server api connection in service mode. diff --git a/ayon_api/utils.py b/ayon_api/utils.py index c075fdf74..1f863233c 100644 --- a/ayon_api/utils.py +++ b/ayon_api/utils.py @@ -1,6 +1,7 @@ import os import re import datetime +import copy import uuid import string import platform @@ -78,6 +79,39 @@ class RequestTypes: delete = RequestType("DELETE") +def fill_own_attribs(entity: "AnyEntityDict") -> None: + """Fill own attributes. + + Prepare data with own attributes. Prepare data based on a list of + attribute names in 'ownAttrib' and 'attrib'. If is not attribute in + 'ownAttrib' then it's value is set to 'None'. + + This can be used with a project, folder or task entity. All other entities + don't use hierarchical attributes and 'attrib' values are + "real values". + + Args: + entity (dict): Entity dictionary. + + """ + if not entity or not entity.get("attrib"): + return + + attributes = entity.get("ownAttrib") + if attributes is None: + return + attributes = set(attributes) + + own_attrib = {} + entity["ownAttrib"] = own_attrib + + for key, value in entity["attrib"].items(): + if key not in attributes: + own_attrib[key] = None + else: + own_attrib[key] = copy.deepcopy(value) + + def get_default_timeout() -> float: """Default value for requests timeout. From f04edad7c2afda78e6b9b72f3bc756590da9bee6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 16:43:38 +0200 Subject: [PATCH 07/53] added more black magic --- automated_api.py | 142 +++++++++++++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 48 deletions(-) diff --git a/automated_api.py b/automated_api.py index 5b09a1d4e..c3738bdbc 100644 --- a/automated_api.py +++ b/automated_api.py @@ -20,7 +20,6 @@ import typing # Fake modules to avoid import errors - requests = type(sys)("requests") requests.__dict__["Response"] = type( "Response", (), {"__module__": "requests"} @@ -29,15 +28,7 @@ sys.modules["requests"] = requests sys.modules["unidecode"] = type(sys)("unidecode") -import ayon_api # noqa: E402 -from ayon_api.server_api import ( # noqa: E402 - ServerAPI, - _PLACEHOLDER, - _ActionsAPI, - _AddonsAPI, - _ListsAPI, -) -from ayon_api.utils import NOT_SET # noqa: E402 +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) EXCLUDED_METHODS = { "get_default_service_username", @@ -126,34 +117,38 @@ def prepare_docstring(func): return f'"""{docstring}{line_char}\n"""' +def _find_obj(obj_full, api_globals): + parts = list(reversed(obj_full.split("."))) + _name = None + for part in parts: + if _name is None: + _name = part + else: + _name = f"{part}.{_name}" + try: + # Test if typehint is valid for known '_api' content + exec(f"_: {_name} = None", api_globals) + return _name + except NameError: + pass + return None + + def _get_typehint(annotation, api_globals): + if isinstance(annotation, str): + annotation = annotation.replace("'", '"') + if inspect.isclass(annotation): - module_name_parts = list(str(annotation.__module__).split(".")) - module_name_parts.append(annotation.__name__) - module_name_parts.reverse() - options = [] - _name = None - for name in module_name_parts: - if _name is None: - _name = name - options.append(name) - else: - _name = f"{name}.{_name}" - options.append(_name) - - options.reverse() - for option in options: - try: - # Test if typehint is valid for known '_api' content - exec(f"_: {option} = None", api_globals) - return option - except NameError: - pass - - typehint = options[0] - print("Unknown typehint:", typehint) - typehint = f'"{typehint}"' - return typehint + module_name = str(annotation.__module__) + full_name = annotation.__name__ + if module_name: + full_name = f"{module_name}.{full_name}" + obj_name = _find_obj(full_name, api_globals) + if obj_name is not None: + return obj_name + + print("Unknown typehint:", full_name) + return f'"{full_name}"' typehint = ( str(annotation) @@ -162,9 +157,15 @@ def _get_typehint(annotation, api_globals): full_path_regex = re.compile( r"(?P(?P[a-zA-Z0-9_\.]+))" ) + for item in full_path_regex.finditer(str(typehint)): groups = item.groupdict() - name = groups["name"].split(".")[-1] + name = groups["name"] + obj_name = _find_obj(name, api_globals) + if obj_name: + name = obj_name + else: + name = name.split(".")[-1] typehint = typehint.replace(groups["full"], name) forwardref_regex = re.compile( @@ -172,16 +173,52 @@ def _get_typehint(annotation, api_globals): ) for item in forwardref_regex.finditer(str(typehint)): groups = item.groupdict() - name = groups["name"].split(".")[-1] - typehint = typehint.replace(groups["full"], f'"{name}"') + name = groups["name"] + obj_name = _find_obj(name, api_globals) + if obj_name: + name = obj_name + else: + name = name.split(".")[-1] + typehint = typehint.replace(groups["full"], name) try: # Test if typehint is valid for known '_api' content exec(f"_: {typehint} = None", api_globals) + return typehint except NameError: print("Unknown typehint:", typehint) - typehint = f'"{typehint}"' - return typehint + + _typehint = typehint + _typehing_parents = [] + while True: + # Too hard to manage typehints with commas + if "[" not in _typehint: + break + + parts = _typehint.split("[") + parent = parts.pop(0) + + try: + # Test if typehint is valid for known '_api' content + exec(f"_: {parent} = None", api_globals) + except NameError: + _typehint = parent + break + + _typehint = "[".join(parts)[:-1] + if "," in _typehint: + _typing = parent + break + + _typehing_parents.append(parent) + + if _typehing_parents: + typehint = f'"{_typehint}"' + for parent in reversed(_typehing_parents): + typehint = f"{parent}[{typehint}]" + return typehint + + return f'"{typehint}"' def _get_param_typehint(param, api_globals): @@ -198,6 +235,9 @@ def _add_typehint(param_name, param, api_globals): def _kw_default_to_str(param_name, param, api_globals): + from ayon_api.server_api import _PLACEHOLDER + from ayon_api.utils import NOT_SET + if param.default is inspect.Parameter.empty: return _add_typehint(param_name, param, api_globals) @@ -293,12 +333,23 @@ def sig_params_to_str(sig, param_names, api_globals, indent=0): def prepare_api_functions(api_globals): + from ayon_api.server_api import ( # noqa: E402 + ServerAPI, + _ActionsAPI, + _AddonsAPI, + _ListsAPI, + ) + functions = [] _items = list(ServerAPI.__dict__.items()) _items.extend(_ActionsAPI.__dict__.items()) _items.extend(_AddonsAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) + processed = set() for attr_name, attr in _items: + if attr_name in processed: + continue + processed.add(attr_name) if ( attr_name.startswith("_") or attr_name in EXCLUDED_METHODS @@ -336,10 +387,7 @@ def prepare_api_functions(api_globals): def main(): print("Creating public API functions based on ServerAPI methods") # TODO order methods in some order - dirpath = os.path.dirname(os.path.dirname( - os.path.abspath(ayon_api.__file__) - )) - ayon_api_root = os.path.join(dirpath, "ayon_api") + ayon_api_root = os.path.join(CURRENT_DIR, "ayon_api") init_filepath = os.path.join(ayon_api_root, "__init__.py") api_filepath = os.path.join(ayon_api_root, "_api.py") @@ -363,15 +411,13 @@ def main(): # Read content of first part of `_api.py` to get global variables # - disable type checking so imports done only during typechecking are # not executed - old_value = typing.TYPE_CHECKING typing.TYPE_CHECKING = False api_globals = {"__name__": "ayon_api._api"} exec(parts[0], api_globals) + for attr_name in dir(__builtins__): api_globals[attr_name] = getattr(__builtins__, attr_name) - typing.TYPE_CHECKING = old_value - # print(api_globals) print("(3/5) Preparing functions body based on 'ServerAPI' class") result = prepare_api_functions(api_globals) From 1e9498290258074d3b04fecc9e3708fd49409bbd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:21:56 +0200 Subject: [PATCH 08/53] change imports in init --- ayon_api/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 90fb4374c..2c5baa8ba 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -364,10 +364,6 @@ "get_attributes_for_type", "get_attributes_fields_for_type", "get_default_fields_for_type", - "get_addons_info", - "get_addon_endpoint", - "get_addon_url", - "download_addon_private_file", "get_installers", "create_installer", "update_installer", @@ -380,9 +376,6 @@ "delete_dependency_package", "download_dependency_package", "upload_dependency_package", - "delete_addon", - "delete_addon_version", - "upload_addon_zip", "get_bundles", "create_bundle", "update_bundle", @@ -518,6 +511,13 @@ "set_action_config", "take_action", "abort_action", + "get_addon_endpoint", + "get_addons_info", + "get_addon_url", + "delete_addon", + "delete_addon_version", + "upload_addon_zip", + "download_addon_private_file", "get_entity_lists", "get_entity_list_rest", "get_entity_list_by_id", From ac96f87834c71d75bbaeb54bd9dce6eb3682e239 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:29:44 +0200 Subject: [PATCH 09/53] separated project methods --- automated_api.py | 3 + ayon_api/__init__.py | 15 +-- ayon_api/_api.py | 258 +++++++++++++++++------------------ ayon_api/_base.py | 8 ++ ayon_api/_projects.py | 297 +++++++++++++++++++++++++++++++++++++++++ ayon_api/server_api.py | 286 +-------------------------------------- 6 files changed, 445 insertions(+), 422 deletions(-) create mode 100644 ayon_api/_projects.py diff --git a/automated_api.py b/automated_api.py index c3738bdbc..d13ee7995 100644 --- a/automated_api.py +++ b/automated_api.py @@ -338,6 +338,7 @@ def prepare_api_functions(api_globals): _ActionsAPI, _AddonsAPI, _ListsAPI, + _ProjectsAPI, ) functions = [] @@ -345,6 +346,8 @@ def prepare_api_functions(api_globals): _items.extend(_ActionsAPI.__dict__.items()) _items.extend(_AddonsAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) + _items.extend(_ProjectsAPI.__dict__.items()) + processed = set() for attr_name, attr in _items: if attr_name in processed: diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 2c5baa8ba..c9fc2af89 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -137,8 +137,6 @@ get_secret, save_secret, delete_secret, - get_rest_project, - get_rest_projects, get_rest_entity_by_id, get_rest_folder, get_rest_folders, @@ -146,9 +144,6 @@ get_rest_product, get_rest_version, get_rest_representation, - get_project_names, - get_projects, - get_project, get_folders_hierarchy, get_folders_rest, get_folders, @@ -406,8 +401,6 @@ "get_secret", "save_secret", "delete_secret", - "get_rest_project", - "get_rest_projects", "get_rest_entity_by_id", "get_rest_folder", "get_rest_folders", @@ -415,9 +408,6 @@ "get_rest_product", "get_rest_version", "get_rest_representation", - "get_project_names", - "get_projects", - "get_project", "get_folders_hierarchy", "get_folders_rest", "get_folders", @@ -530,4 +520,9 @@ "update_entity_list_items", "update_entity_list_item", "delete_entity_list_item", + "get_rest_project", + "get_rest_projects", + "get_project_names", + "get_projects", + "get_project", ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index b6ea440cb..79d776a22 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -3023,52 +3023,6 @@ def delete_secret( ) -def get_rest_project( - project_name: str, -) -> Optional["ProjectDict"]: - """Query project by name. - - This call returns project with anatomy data. - - Args: - project_name (str): Name of project. - - Returns: - Optional[ProjectDict]: Project entity data or 'None' if - project was not found. - - """ - con = get_server_api_connection() - return con.get_rest_project( - project_name=project_name, - ) - - -def get_rest_projects( - active: Optional[bool] = True, - library: Optional[bool] = None, -) -> Generator["ProjectDict", None, None]: - """Query available project entities. - - User must be logged in. - - Args: - active (Optional[bool]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Optional[bool]): Filter standard/library projects. Both - are returned if 'None' is passed. - - Returns: - Generator[ProjectDict, None, None]: Available projects. - - """ - con = get_server_api_connection() - return con.get_rest_projects( - active=active, - library=library, - ) - - def get_rest_entity_by_id( project_name: str, entity_type: str, @@ -3200,89 +3154,6 @@ def get_rest_representation( ) -def get_project_names( - active: "Union[bool, None]" = True, - library: "Union[bool, None]" = None, -) -> List[str]: - """Receive available project names. - - User must be logged in. - - Args: - active (Union[bool, None]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Union[bool, None]): Filter standard/library projects. Both - are returned if 'None' is passed. - - Returns: - list[str]: List of available project names. - - """ - con = get_server_api_connection() - return con.get_project_names( - active=active, - library=library, - ) - - -def get_projects( - active: "Union[bool, None]" = True, - library: "Union[bool, None]" = None, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Generator["ProjectDict", None, None]: - """Get projects. - - Args: - active (Optional[bool]): Filter active or inactive projects. - Filter is disabled when 'None' is passed. - library (Optional[bool]): Filter library projects. Filter is - disabled when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Generator[ProjectDict, None, None]: Queried projects. - - """ - con = get_server_api_connection() - return con.get_projects( - active=active, - library=library, - fields=fields, - own_attributes=own_attributes, - ) - - -def get_project( - project_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["ProjectDict"]: - """Get project. - - Args: - project_name (str): Name of project. - fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Optional[ProjectDict]: Project entity data or None - if project was not found. - - """ - con = get_server_api_connection() - return con.get_project( - project_name=project_name, - fields=fields, - own_attributes=own_attributes, - ) - - def get_folders_hierarchy( project_name: str, search_string: Optional[str] = None, @@ -7316,3 +7187,132 @@ def delete_entity_list_item( list_id=list_id, item_id=item_id, ) + + +def get_rest_project( + project_name: str, +) -> Optional["ProjectDict"]: + """Query project by name. + + This call returns project with anatomy data. + + Args: + project_name (str): Name of project. + + Returns: + Optional[ProjectDict]: Project entity data or 'None' if + project was not found. + + """ + con = get_server_api_connection() + return con.get_rest_project( + project_name=project_name, + ) + + +def get_rest_projects( + active: Optional[bool] = True, + library: Optional[bool] = None, +) -> Generator["ProjectDict", None, None]: + """Query available project entities. + + User must be logged in. + + Args: + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. + + Returns: + Generator[ProjectDict, None, None]: Available projects. + + """ + con = get_server_api_connection() + return con.get_rest_projects( + active=active, + library=library, + ) + + +def get_project_names( + active: Optional[bool] = True, + library: Optional[bool] = None, +) -> list[str]: + """Receive available project names. + + User must be logged in. + + Args: + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. + + Returns: + list[str]: List of available project names. + + """ + con = get_server_api_connection() + return con.get_project_names( + active=active, + library=library, + ) + + +def get_projects( + active: Optional[bool] = True, + library: Optional[bool] = None, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Generator["ProjectDict", None, None]: + """Get projects. + + Args: + active (Optional[bool]): Filter active or inactive projects. + Filter is disabled when 'None' is passed. + library (Optional[bool]): Filter library projects. Filter is + disabled when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Generator[ProjectDict, None, None]: Queried projects. + + """ + con = get_server_api_connection() + return con.get_projects( + active=active, + library=library, + fields=fields, + own_attributes=own_attributes, + ) + + +def get_project( + project_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["ProjectDict"]: + """Get project. + + Args: + project_name (str): Name of project. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[ProjectDict]: Project entity data or None + if project was not found. + + """ + con = get_server_api_connection() + return con.get_project( + project_name=project_name, + fields=fields, + own_attributes=own_attributes, + ) diff --git a/ayon_api/_base.py b/ayon_api/_base.py index 5d7757e0b..4fb92a960 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -71,5 +71,13 @@ def download_file( ) -> TransferProgress: raise NotImplementedError() + def _prepare_fields( + self, + entity_type: str, + fields: set[str], + own_attributes: bool = False, + ): + raise NotImplementedError() + def _convert_entity_data(self, entity: "AnyEntityDict"): raise NotImplementedError() diff --git a/ayon_api/_projects.py b/ayon_api/_projects.py new file mode 100644 index 000000000..62c0523d2 --- /dev/null +++ b/ayon_api/_projects.py @@ -0,0 +1,297 @@ +from __future__ import annotations + +import json +import typing +from typing import Optional, Generator, Iterable, Any + +from ._base import _BaseServerAPI +from .utils import prepare_query_string, fill_own_attribs +from .graphql_queries import projects_graphql_query + +if typing.TYPE_CHECKING: + from .typing import ProjectDict + + +class _ProjectsAPI(_BaseServerAPI): + def get_rest_project( + self, project_name: str + ) -> Optional["ProjectDict"]: + """Query project by name. + + This call returns project with anatomy data. + + Args: + project_name (str): Name of project. + + Returns: + Optional[ProjectDict]: Project entity data or 'None' if + project was not found. + + """ + if not project_name: + return None + + response = self.get(f"projects/{project_name}") + # TODO ignore only error about not existing project + if response.status != 200: + return None + project = response.data + self._fill_project_entity_data(project) + return project + + def get_rest_projects( + self, + active: Optional[bool] = True, + library: Optional[bool] = None, + ) -> Generator["ProjectDict", None, None]: + """Query available project entities. + + User must be logged in. + + Args: + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. + + Returns: + Generator[ProjectDict, None, None]: Available projects. + + """ + for project_name in self.get_project_names(active, library): + project = self.get_rest_project(project_name) + if project: + yield project + + def get_project_names( + self, + active: Optional[bool] = True, + library: Optional[bool] = None, + ) -> list[str]: + """Receive available project names. + + User must be logged in. + + Args: + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. + + Returns: + list[str]: List of available project names. + + """ + if active is not None: + active = "true" if active else "false" + + if library is not None: + library = "true" if library else "false" + + query = prepare_query_string({"active": active, "library": library}) + + response = self.get(f"projects{query}") + response.raise_for_status() + data = response.data + project_names = [] + if data: + for project in data["projects"]: + project_names.append(project["name"]) + return project_names + + def get_projects( + self, + active: Optional[bool] = True, + library: Optional[bool] = None, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, + ) -> Generator["ProjectDict", None, None]: + """Get projects. + + Args: + active (Optional[bool]): Filter active or inactive projects. + Filter is disabled when 'None' is passed. + library (Optional[bool]): Filter library projects. Filter is + disabled when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Generator[ProjectDict, None, None]: Queried projects. + + """ + if fields is not None: + fields = set(fields) + + graphql_fields, use_rest = self._get_project_graphql_fields(fields) + projects_by_name = {} + if graphql_fields: + projects = list(self._get_graphql_projects( + active, + library, + fields=graphql_fields, + own_attributes=own_attributes, + )) + if not use_rest: + yield from projects + return + projects_by_name = {p["name"]: p for p in projects} + + for project in self.get_rest_projects(active, library): + name = project["name"] + graphql_p = projects_by_name.get(name) + if graphql_p: + project["productTypes"] = graphql_p["productTypes"] + yield project + + def get_project( + self, + project_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, + ) -> Optional["ProjectDict"]: + """Get project. + + Args: + project_name (str): Name of project. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[ProjectDict]: Project entity data or None + if project was not found. + + """ + if fields is not None: + fields = set(fields) + + graphql_fields, use_rest = self._get_project_graphql_fields(fields) + graphql_project = None + if graphql_fields: + graphql_project = next(self._get_graphql_projects( + None, + None, + fields=graphql_fields, + own_attributes=own_attributes, + ), None) + if not graphql_project or not use_rest: + return graphql_project + + project = self.get_rest_project(project_name) + if own_attributes: + fill_own_attribs(project) + if graphql_project: + project["productTypes"] = graphql_project["productTypes"] + return project + + def _get_project_graphql_fields( + self, fields: Optional[set[str]] + ) -> tuple[set[str], bool]: + """Fetch of project must be done using REST endpoint. + + Returns: + set[str]: GraphQl fields. + + """ + if fields is None: + return set(), True + + has_product_types = False + graphql_fields = set() + for field in fields: + # Product types are available only in GraphQl + if field.startswith("productTypes"): + has_product_types = True + graphql_fields.add(field) + + if not has_product_types: + return set(), True + + inters = fields & {"name", "code", "active", "library"} + remainders = fields - (inters | graphql_fields) + if remainders: + graphql_fields.add("name") + return graphql_fields, True + graphql_fields |= inters + return graphql_fields, False + + def _fill_project_entity_data(self, project: dict[str, Any]) -> None: + # Add fake scope to statuses if not available + if "statuses" in project: + for status in project["statuses"]: + scope = status.get("scope") + if scope is None: + status["scope"] = [ + "folder", + "task", + "product", + "version", + "representation", + "workfile" + ] + + # Convert 'data' from string to dict if needed + if "data" in project: + project_data = project["data"] + if isinstance(project_data, str): + project_data = json.loads(project_data) + project["data"] = project_data + + # Fill 'bundle' from data if is not filled + if "bundle" not in project: + bundle_data = project["data"].get("bundle", {}) + prod_bundle = bundle_data.get("production") + staging_bundle = bundle_data.get("staging") + project["bundle"] = { + "production": prod_bundle, + "staging": staging_bundle, + } + + # Convert 'config' from string to dict if needed + config = project.get("config") + if isinstance(config, str): + project["config"] = json.loads(config) + + # Unifiy 'linkTypes' data structure from REST and GraphQL + if "linkTypes" in project: + for link_type in project["linkTypes"]: + if "data" in link_type: + link_data = link_type.pop("data") + link_type.update(link_data) + if "style" not in link_type: + link_type["style"] = None + if "color" not in link_type: + link_type["color"] = None + + def _get_graphql_projects( + self, + active: Optional[bool], + library: Optional[bool], + fields: set[str], + own_attributes: bool, + project_name: Optional[str] = None + ): + if active is not None: + fields.add("active") + + if library is not None: + fields.add("library") + + self._prepare_fields("project", fields, own_attributes) + + query = projects_graphql_query(fields) + if project_name is not None: + query.set_variable_value("projectName", project_name) + + for parsed_data in query.continuous_query(self): + for project in parsed_data["projects"]: + if active is not None and active is not project["active"]: + continue + if own_attributes: + fill_own_attribs(project) + self._fill_project_entity_data(project) + yield project diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 294ed9a52..a92fbb2d1 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -106,6 +106,7 @@ from ._actions import _ActionsAPI from ._addons import _AddonsAPI from ._lists import _ListsAPI +from ._projects import _ProjectsAPI if typing.TYPE_CHECKING: from typing import Union @@ -395,9 +396,10 @@ def as_user(self, username): class ServerAPI( - _ListsAPI, _ActionsAPI, _AddonsAPI, + _ListsAPI, + _ProjectsAPI, ): """Base handler of connection to server. @@ -4168,55 +4170,6 @@ def delete_secret(self, secret_name: str): return response.data # Entity getters - def get_rest_project( - self, project_name: str - ) -> Optional["ProjectDict"]: - """Query project by name. - - This call returns project with anatomy data. - - Args: - project_name (str): Name of project. - - Returns: - Optional[ProjectDict]: Project entity data or 'None' if - project was not found. - - """ - if not project_name: - return None - - response = self.get(f"projects/{project_name}") - # TODO ignore only error about not existing project - if response.status != 200: - return None - project = response.data - self._fill_project_entity_data(project) - return project - - def get_rest_projects( - self, - active: Optional[bool] = True, - library: Optional[bool] = None, - ) -> Generator["ProjectDict", None, None]: - """Query available project entities. - - User must be logged in. - - Args: - active (Optional[bool]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Optional[bool]): Filter standard/library projects. Both - are returned if 'None' is passed. - - Returns: - Generator[ProjectDict, None, None]: Available projects. - - """ - for project_name in self.get_project_names(active, library): - project = self.get_rest_project(project_name) - if project: - yield project def get_rest_entity_by_id( self, @@ -4333,239 +4286,6 @@ def get_rest_representation( project_name, "representation", representation_id ) - def get_project_names( - self, - active: "Union[bool, None]" = True, - library: "Union[bool, None]" = None, - ) -> List[str]: - """Receive available project names. - - User must be logged in. - - Args: - active (Union[bool, None]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Union[bool, None]): Filter standard/library projects. Both - are returned if 'None' is passed. - - Returns: - list[str]: List of available project names. - - """ - if active is not None: - active = "true" if active else "false" - - if library is not None: - library = "true" if library else "false" - - query = prepare_query_string({"active": active, "library": library}) - - response = self.get(f"projects{query}") - response.raise_for_status() - data = response.data - project_names = [] - if data: - for project in data["projects"]: - project_names.append(project["name"]) - return project_names - - def get_projects( - self, - active: "Union[bool, None]" = True, - library: "Union[bool, None]" = None, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, - ) -> Generator["ProjectDict", None, None]: - """Get projects. - - Args: - active (Optional[bool]): Filter active or inactive projects. - Filter is disabled when 'None' is passed. - library (Optional[bool]): Filter library projects. Filter is - disabled when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Generator[ProjectDict, None, None]: Queried projects. - - """ - if fields is not None: - fields = set(fields) - - graphql_fields, use_rest = self._get_project_graphql_fields(fields) - projects_by_name = {} - if graphql_fields: - projects = list(self._get_graphql_projects( - active, - library, - fields=graphql_fields, - own_attributes=own_attributes, - )) - if not use_rest: - yield from projects - return - projects_by_name = {p["name"]: p for p in projects} - - for project in self.get_rest_projects(active, library): - name = project["name"] - graphql_p = projects_by_name.get(name) - if graphql_p: - project["productTypes"] = graphql_p["productTypes"] - yield project - - def get_project( - self, - project_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, - ) -> Optional["ProjectDict"]: - """Get project. - - Args: - project_name (str): Name of project. - fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Optional[ProjectDict]: Project entity data or None - if project was not found. - - """ - if fields is not None: - fields = set(fields) - - graphql_fields, use_rest = self._get_project_graphql_fields(fields) - graphql_project = None - if graphql_fields: - graphql_project = next(self._get_graphql_projects( - None, - None, - fields=graphql_fields, - own_attributes=own_attributes, - ), None) - if not graphql_project or not use_rest: - return graphql_project - - project = self.get_rest_project(project_name) - if own_attributes: - fill_own_attribs(project) - if graphql_project: - project["productTypes"] = graphql_project["productTypes"] - return project - - def _get_project_graphql_fields( - self, fields: Optional[Set[str]] - ) -> Tuple[Set[str], bool]: - """Fetch of project must be done using REST endpoint. - - Returns: - set[str]: GraphQl fields. - - """ - if fields is None: - return set(), True - - has_product_types = False - graphql_fields = set() - for field in fields: - # Product types are available only in GraphQl - if field.startswith("productTypes"): - has_product_types = True - graphql_fields.add(field) - - if not has_product_types: - return set(), True - - inters = fields & {"name", "code", "active", "library"} - remainders = fields - (inters | graphql_fields) - if remainders: - graphql_fields.add("name") - return graphql_fields, True - graphql_fields |= inters - return graphql_fields, False - - def _fill_project_entity_data(self, project: Dict[str, Any]) -> None: - # Add fake scope to statuses if not available - if "statuses" in project: - for status in project["statuses"]: - scope = status.get("scope") - if scope is None: - status["scope"] = [ - "folder", - "task", - "product", - "version", - "representation", - "workfile" - ] - - # Convert 'data' from string to dict if needed - if "data" in project: - project_data = project["data"] - if isinstance(project_data, str): - project_data = json.loads(project_data) - project["data"] = project_data - - # Fill 'bundle' from data if is not filled - if "bundle" not in project: - bundle_data = project["data"].get("bundle", {}) - prod_bundle = bundle_data.get("production") - staging_bundle = bundle_data.get("staging") - project["bundle"] = { - "production": prod_bundle, - "staging": staging_bundle, - } - - # Convert 'config' from string to dict if needed - config = project.get("config") - if isinstance(config, str): - project["config"] = json.loads(config) - - # Unifiy 'linkTypes' data structure from REST and GraphQL - if "linkTypes" in project: - for link_type in project["linkTypes"]: - if "data" in link_type: - link_data = link_type.pop("data") - link_type.update(link_data) - if "style" not in link_type: - link_type["style"] = None - if "color" not in link_type: - link_type["color"] = None - - def _get_graphql_projects( - self, - active: Optional[bool], - library: Optional[bool], - fields: Set[str], - own_attributes: bool, - project_name: Optional[str] = None - ): - if active is not None: - fields.add("active") - - if library is not None: - fields.add("library") - - self._prepare_fields("project", fields, own_attributes) - - query = projects_graphql_query(fields) - if project_name is not None: - query.set_variable_value("projectName", project_name) - - for parsed_data in query.continuous_query(self): - for project in parsed_data["projects"]: - if active is not None and active is not project["active"]: - continue - if own_attributes: - fill_own_attribs(project) - self._fill_project_entity_data(project) - yield project - def get_folders_hierarchy( self, project_name: str, From bbd75dff327f5c302d12e4b52c5056d43eef1fd0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:30:55 +0200 Subject: [PATCH 10/53] added annotations imports --- ayon_api/_base.py | 6 ++++-- ayon_api/server_api.py | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ayon_api/_base.py b/ayon_api/_base.py index 4fb92a960..889617a51 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -1,5 +1,7 @@ +from __future__ import annotations + import typing -from typing import Set, Optional +from typing import Optional import requests @@ -49,7 +51,7 @@ def raw_delete(self, entrypoint: str, **kwargs): def get_default_settings_variant(self) -> str: raise NotImplementedError() - def get_default_fields_for_type(self, entity_type: str) -> Set[str]: + def get_default_fields_for_type(self, entity_type: str) -> set[str]: raise NotImplementedError() def upload_file( diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index a92fbb2d1..38f99e32a 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -3,6 +3,8 @@ Provides access to server API. """ +from __future__ import annotations + import os import re import io From ec23b652b04e0906698cbd93690d2dfb53214856 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:38:39 +0200 Subject: [PATCH 11/53] move links to separate file --- automated_api.py | 2 + ayon_api/_links.py | 656 +++++++++++++++++++++++++++++++++++++++++ ayon_api/server_api.py | 641 +--------------------------------------- 3 files changed, 660 insertions(+), 639 deletions(-) create mode 100644 ayon_api/_links.py diff --git a/automated_api.py b/automated_api.py index d13ee7995..a5971552e 100644 --- a/automated_api.py +++ b/automated_api.py @@ -337,6 +337,7 @@ def prepare_api_functions(api_globals): ServerAPI, _ActionsAPI, _AddonsAPI, + _LinksAPI, _ListsAPI, _ProjectsAPI, ) @@ -345,6 +346,7 @@ def prepare_api_functions(api_globals): _items = list(ServerAPI.__dict__.items()) _items.extend(_ActionsAPI.__dict__.items()) _items.extend(_AddonsAPI.__dict__.items()) + _items.extend(_LinksAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) _items.extend(_ProjectsAPI.__dict__.items()) diff --git a/ayon_api/_links.py b/ayon_api/_links.py new file mode 100644 index 000000000..4c7a82237 --- /dev/null +++ b/ayon_api/_links.py @@ -0,0 +1,656 @@ +from __future__ import annotations + +import collections +import typing +from typing import Optional, Any, Iterable + +from .graphql_queries import ( + folders_graphql_query, + tasks_graphql_query, + products_graphql_query, + versions_graphql_query, + representations_graphql_query, +) +from ._base import _BaseServerAPI + +if typing.TYPE_CHECKING: + from .typing import LinkDirection + + +class _LinksAPI(_BaseServerAPI): + def get_full_link_type_name( + self, link_type_name: str, input_type: str, output_type: str + ) -> str: + """Calculate full link type name used for query from server. + + Args: + link_type_name (str): Type of link. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + + Returns: + str: Full name of link type used for query from server. + + """ + return "|".join([link_type_name, input_type, output_type]) + + def get_link_types(self, project_name: str) -> list[dict[str, Any]]: + """All link types available on a project. + + Example output: + [ + { + "name": "reference|folder|folder", + "link_type": "reference", + "input_type": "folder", + "output_type": "folder", + "data": {} + } + ] + + Args: + project_name (str): Name of project where to look for link types. + + Returns: + list[dict[str, Any]]: Link types available on project. + + """ + response = self.get(f"projects/{project_name}/links/types") + response.raise_for_status() + return response.data["types"] + + def get_link_type( + self, + project_name: str, + link_type_name: str, + input_type: str, + output_type: str, + ) -> Optional[dict[str, Any]]: + """Get link type data. + + There is not dedicated REST endpoint to get single link type, + so method 'get_link_types' is used. + + Example output: + { + "name": "reference|folder|folder", + "link_type": "reference", + "input_type": "folder", + "output_type": "folder", + "data": {} + } + + Args: + project_name (str): Project where link type is available. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + + Returns: + Optional[dict[str, Any]]: Link type information. + + """ + full_type_name = self.get_full_link_type_name( + link_type_name, input_type, output_type + ) + for link_type in self.get_link_types(project_name): + if link_type["name"] == full_type_name: + return link_type + return None + + def create_link_type( + self, + project_name: str, + link_type_name: str, + input_type: str, + output_type: str, + data: Optional[dict[str, Any]] = None, + ): + """Create or update link type on server. + + Warning: + Because PUT is used for creation it is also used for update. + + Args: + project_name (str): Project where link type is created. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + data (Optional[dict[str, Any]]): Additional data related to link. + + Raises: + HTTPRequestError: Server error happened. + + """ + if data is None: + data = {} + full_type_name = self.get_full_link_type_name( + link_type_name, input_type, output_type + ) + response = self.put( + f"projects/{project_name}/links/types/{full_type_name}", + **data + ) + response.raise_for_status() + + def delete_link_type( + self, + project_name: str, + link_type_name: str, + input_type: str, + output_type: str, + ): + """Remove link type from project. + + Args: + project_name (str): Project where link type is created. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + + Raises: + HTTPRequestError: Server error happened. + + """ + full_type_name = self.get_full_link_type_name( + link_type_name, input_type, output_type + ) + response = self.delete( + f"projects/{project_name}/links/types/{full_type_name}" + ) + response.raise_for_status() + + def make_sure_link_type_exists( + self, + project_name: str, + link_type_name: str, + input_type: str, + output_type: str, + data: Optional[dict[str, Any]] = None, + ): + """Make sure link type exists on a project. + + Args: + project_name (str): Name of project. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + data (Optional[dict[str, Any]]): Link type related data. + + """ + link_type = self.get_link_type( + project_name, link_type_name, input_type, output_type) + if ( + link_type + and (data is None or data == link_type["data"]) + ): + return + self.create_link_type( + project_name, link_type_name, input_type, output_type, data + ) + + def create_link( + self, + project_name: str, + link_type_name: str, + input_id: str, + input_type: str, + output_id: str, + output_type: str, + link_name: Optional[str] = None, + ): + """Create link between 2 entities. + + Link has a type which must already exists on a project. + + Example output:: + + { + "id": "59a212c0d2e211eda0e20242ac120002" + } + + Args: + project_name (str): Project where the link is created. + link_type_name (str): Type of link. + input_id (str): Input entity id. + input_type (str): Entity type of input entity. + output_id (str): Output entity id. + output_type (str): Entity type of output entity. + link_name (Optional[str]): Name of link. + Available from server version '1.0.0-rc.6'. + + Returns: + dict[str, str]: Information about link. + + Raises: + HTTPRequestError: Server error happened. + + """ + full_link_type_name = self.get_full_link_type_name( + link_type_name, input_type, output_type) + + kwargs = { + "input": input_id, + "output": output_id, + "linkType": full_link_type_name, + } + if link_name: + kwargs["name"] = link_name + + response = self.post( + f"projects/{project_name}/links", **kwargs + ) + response.raise_for_status() + return response.data + + def delete_link(self, project_name: str, link_id: str): + """Remove link by id. + + Args: + project_name (str): Project where link exists. + link_id (str): Id of link. + + Raises: + HTTPRequestError: Server error happened. + + """ + response = self.delete( + f"projects/{project_name}/links/{link_id}" + ) + response.raise_for_status() + + def _prepare_link_filters( + self, + filters: dict[str, Any], + link_types: Optional[Iterable[str], None], + link_direction: Optional["LinkDirection"], + link_names: Optional[Iterable[str]], + link_name_regex: Optional[str], + ) -> bool: + """Add links filters for GraphQl queries. + + Args: + filters (dict[str, Any]): Object where filters will be added. + link_types (Optional[Iterable[str]]): Link types filters. + link_direction (Optional[Literal["in", "out"]]): Direction of + link "in", "out" or 'None' for both. + link_names (Optional[Iterable[str]]): Link name filters. + link_name_regex (Optional[str]): Regex filter for link name. + + Returns: + bool: Links are valid, and query from server can happen. + + """ + if link_types is not None: + link_types = set(link_types) + if not link_types: + return False + filters["linkTypes"] = list(link_types) + + if link_names is not None: + link_names = set(link_names) + if not link_names: + return False + filters["linkNames"] = list(link_names) + + if link_direction is not None: + if link_direction not in ("in", "out"): + return False + filters["linkDirection"] = link_direction + + if link_name_regex is not None: + filters["linkNameRegex"] = link_name_regex + return True + + def get_entities_links( + self, + project_name: str, + entity_type: str, + entity_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + link_names: Optional[Iterable[str]] = None, + link_name_regex: Optional[str] = None, + ) -> dict[str, list[dict[str, Any]]]: + """Helper method to get links from server for entity types. + + .. highlight:: text + .. code-block:: text + + Example output: + { + "59a212c0d2e211eda0e20242ac120001": [ + { + "id": "59a212c0d2e211eda0e20242ac120002", + "linkType": "reference", + "description": "reference link between folders", + "projectName": "my_project", + "author": "frantadmin", + "entityId": "b1df109676db11ed8e8c6c9466b19aa8", + "entityType": "folder", + "direction": "out" + }, + ... + ], + ... + } + + Args: + project_name (str): Project where links are. + entity_type (Literal["folder", "task", "product", + "version", "representations"]): Entity type. + entity_ids (Optional[Iterable[str]]): Ids of entities for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + link_names (Optional[Iterable[str]]): Link name filters. + link_name_regex (Optional[str]): Regex filter for link name. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by entity ids. + + """ + if entity_type == "folder": + query_func = folders_graphql_query + id_filter_key = "folderIds" + project_sub_key = "folders" + elif entity_type == "task": + query_func = tasks_graphql_query + id_filter_key = "taskIds" + project_sub_key = "tasks" + elif entity_type == "product": + query_func = products_graphql_query + id_filter_key = "productIds" + project_sub_key = "products" + elif entity_type == "version": + query_func = versions_graphql_query + id_filter_key = "versionIds" + project_sub_key = "versions" + elif entity_type == "representation": + query_func = representations_graphql_query + id_filter_key = "representationIds" + project_sub_key = "representations" + else: + raise ValueError("Unknown type \"{}\". Expected {}".format( + entity_type, + ", ".join( + ("folder", "task", "product", "version", "representation") + ) + )) + + output = collections.defaultdict(list) + filters = { + "projectName": project_name + } + if entity_ids is not None: + entity_ids = set(entity_ids) + if not entity_ids: + return output + filters[id_filter_key] = list(entity_ids) + + if not self._prepare_link_filters( + filters, link_types, link_direction, link_names, link_name_regex + ): + return output + + link_fields = {"id", "links"} + query = query_func(link_fields) + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + for parsed_data in query.continuous_query(self): + for entity in parsed_data["project"][project_sub_key]: + entity_id = entity["id"] + output[entity_id].extend(entity["links"]) + return output + + def get_folders_links( + self, + project_name: str, + folder_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> dict[str, list[dict[str, Any]]]: + """Query folders links from server. + + Args: + project_name (str): Project where links are. + folder_ids (Optional[Iterable[str]]): Ids of folders for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by folder ids. + + """ + return self.get_entities_links( + project_name, "folder", folder_ids, link_types, link_direction + ) + + def get_folder_links( + self, + project_name: str, + folder_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> list[dict[str, Any]]: + """Query folder links from server. + + Args: + project_name (str): Project where links are. + folder_id (str): Folder id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of folder. + + """ + return self.get_folders_links( + project_name, [folder_id], link_types, link_direction + )[folder_id] + + def get_tasks_links( + self, + project_name: str, + task_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> dict[str, list[dict[str, Any]]]: + """Query tasks links from server. + + Args: + project_name (str): Project where links are. + task_ids (Optional[Iterable[str]]): Ids of tasks for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by task ids. + + """ + return self.get_entities_links( + project_name, "task", task_ids, link_types, link_direction + ) + + def get_task_links( + self, + project_name: str, + task_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> list[dict[str, Any]]: + """Query task links from server. + + Args: + project_name (str): Project where links are. + task_id (str): Task id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of task. + + """ + return self.get_tasks_links( + project_name, [task_id], link_types, link_direction + )[task_id] + + def get_products_links( + self, + project_name: str, + product_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> dict[str, list[dict[str, Any]]]: + """Query products links from server. + + Args: + project_name (str): Project where links are. + product_ids (Optional[Iterable[str]]): Ids of products for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by product ids. + + """ + return self.get_entities_links( + project_name, "product", product_ids, link_types, link_direction + ) + + def get_product_links( + self, + project_name: str, + product_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> list[dict[str, Any]]: + """Query product links from server. + + Args: + project_name (str): Project where links are. + product_id (str): Product id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of product. + + """ + return self.get_products_links( + project_name, [product_id], link_types, link_direction + )[product_id] + + def get_versions_links( + self, + project_name: str, + version_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> dict[str, list[dict[str, Any]]]: + """Query versions links from server. + + Args: + project_name (str): Project where links are. + version_ids (Optional[Iterable[str]]): Ids of versions for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by version ids. + + """ + return self.get_entities_links( + project_name, "version", version_ids, link_types, link_direction + ) + + def get_version_links( + self, + project_name: str, + version_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> list[dict[str, Any]]: + """Query version links from server. + + Args: + project_name (str): Project where links are. + version_id (str): Version id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of version. + + """ + return self.get_versions_links( + project_name, [version_id], link_types, link_direction + )[version_id] + + def get_representations_links( + self, + project_name: str, + representation_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + ) -> dict[str, list[dict[str, Any]]]: + """Query representations links from server. + + Args: + project_name (str): Project where links are. + representation_ids (Optional[Iterable[str]]): Ids of + representations for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by representation ids. + + """ + return self.get_entities_links( + project_name, + "representation", + representation_ids, + link_types, + link_direction + ) + + def get_representation_links( + self, + project_name: str, + representation_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None + ) -> list[dict[str, Any]]: + """Query representation links from server. + + Args: + project_name (str): Project where links are. + representation_id (str): Representation id for which links + should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of representation. + + """ + return self.get_representations_links( + project_name, [representation_id], link_types, link_direction + )[representation_id] \ No newline at end of file diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 38f99e32a..cfa5504bd 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -61,7 +61,6 @@ ) from .graphql import GraphQlQuery, INTROSPECTION_QUERY from .graphql_queries import ( - projects_graphql_query, product_types_query, folders_graphql_query, tasks_graphql_query, @@ -107,6 +106,7 @@ ) from ._actions import _ActionsAPI from ._addons import _AddonsAPI +from ._links import _LinksAPI from ._lists import _ListsAPI from ._projects import _ProjectsAPI @@ -400,6 +400,7 @@ def as_user(self, username): class ServerAPI( _ActionsAPI, _AddonsAPI, + _LinksAPI, _ListsAPI, _ProjectsAPI, ): @@ -7737,644 +7738,6 @@ def delete_project(self, project_name: str): f"Failed to delete project \"{project_name}\". {detail}" ) - # --- Links --- - def get_full_link_type_name( - self, link_type_name: str, input_type: str, output_type: str - ) -> str: - """Calculate full link type name used for query from server. - - Args: - link_type_name (str): Type of link. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - - Returns: - str: Full name of link type used for query from server. - - """ - return "|".join([link_type_name, input_type, output_type]) - - def get_link_types(self, project_name: str) -> List[Dict[str, Any]]: - """All link types available on a project. - - Example output: - [ - { - "name": "reference|folder|folder", - "link_type": "reference", - "input_type": "folder", - "output_type": "folder", - "data": {} - } - ] - - Args: - project_name (str): Name of project where to look for link types. - - Returns: - list[dict[str, Any]]: Link types available on project. - - """ - response = self.get(f"projects/{project_name}/links/types") - response.raise_for_status() - return response.data["types"] - - def get_link_type( - self, - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - ) -> Optional[str]: - """Get link type data. - - There is not dedicated REST endpoint to get single link type, - so method 'get_link_types' is used. - - Example output: - { - "name": "reference|folder|folder", - "link_type": "reference", - "input_type": "folder", - "output_type": "folder", - "data": {} - } - - Args: - project_name (str): Project where link type is available. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - - Returns: - Optional[str]: Link type information. - - """ - full_type_name = self.get_full_link_type_name( - link_type_name, input_type, output_type - ) - for link_type in self.get_link_types(project_name): - if link_type["name"] == full_type_name: - return link_type - return None - - def create_link_type( - self, - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - data: Optional[Dict[str, Any]] = None, - ): - """Create or update link type on server. - - Warning: - Because PUT is used for creation it is also used for update. - - Args: - project_name (str): Project where link type is created. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - data (Optional[dict[str, Any]]): Additional data related to link. - - Raises: - HTTPRequestError: Server error happened. - - """ - if data is None: - data = {} - full_type_name = self.get_full_link_type_name( - link_type_name, input_type, output_type - ) - response = self.put( - f"projects/{project_name}/links/types/{full_type_name}", - **data - ) - response.raise_for_status() - - def delete_link_type( - self, - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - ): - """Remove link type from project. - - Args: - project_name (str): Project where link type is created. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - - Raises: - HTTPRequestError: Server error happened. - - """ - full_type_name = self.get_full_link_type_name( - link_type_name, input_type, output_type - ) - response = self.delete( - f"projects/{project_name}/links/types/{full_type_name}" - ) - response.raise_for_status() - - def make_sure_link_type_exists( - self, - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - data: Optional[Dict[str, Any]] = None, - ): - """Make sure link type exists on a project. - - Args: - project_name (str): Name of project. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - data (Optional[dict[str, Any]]): Link type related data. - - """ - link_type = self.get_link_type( - project_name, link_type_name, input_type, output_type) - if ( - link_type - and (data is None or data == link_type["data"]) - ): - return - self.create_link_type( - project_name, link_type_name, input_type, output_type, data - ) - - def create_link( - self, - project_name: str, - link_type_name: str, - input_id: str, - input_type: str, - output_id: str, - output_type: str, - link_name: Optional[str] = None, - ): - """Create link between 2 entities. - - Link has a type which must already exists on a project. - - Example output:: - - { - "id": "59a212c0d2e211eda0e20242ac120002" - } - - Args: - project_name (str): Project where the link is created. - link_type_name (str): Type of link. - input_id (str): Input entity id. - input_type (str): Entity type of input entity. - output_id (str): Output entity id. - output_type (str): Entity type of output entity. - link_name (Optional[str]): Name of link. - Available from server version '1.0.0-rc.6'. - - Returns: - dict[str, str]: Information about link. - - Raises: - HTTPRequestError: Server error happened. - - """ - full_link_type_name = self.get_full_link_type_name( - link_type_name, input_type, output_type) - - kwargs = { - "input": input_id, - "output": output_id, - "linkType": full_link_type_name, - } - if link_name: - kwargs["name"] = link_name - - response = self.post( - f"projects/{project_name}/links", **kwargs - ) - response.raise_for_status() - return response.data - - def delete_link(self, project_name: str, link_id: str): - """Remove link by id. - - Args: - project_name (str): Project where link exists. - link_id (str): Id of link. - - Raises: - HTTPRequestError: Server error happened. - - """ - response = self.delete( - f"projects/{project_name}/links/{link_id}" - ) - response.raise_for_status() - - def _prepare_link_filters( - self, - filters: Dict[str, Any], - link_types: "Union[Iterable[str], None]", - link_direction: "Union[LinkDirection, None]", - link_names: "Union[Iterable[str], None]", - link_name_regex: "Union[str, None]", - ) -> bool: - """Add links filters for GraphQl queries. - - Args: - filters (dict[str, Any]): Object where filters will be added. - link_types (Union[Iterable[str], None]): Link types filters. - link_direction (Union[Literal["in", "out"], None]): Direction of - link "in", "out" or 'None' for both. - link_names (Union[Iterable[str], None]): Link name filters. - link_name_regex (Union[str, None]): Regex filter for link name. - - Returns: - bool: Links are valid, and query from server can happen. - - """ - if link_types is not None: - link_types = set(link_types) - if not link_types: - return False - filters["linkTypes"] = list(link_types) - - if link_names is not None: - link_names = set(link_names) - if not link_names: - return False - filters["linkNames"] = list(link_names) - - if link_direction is not None: - if link_direction not in ("in", "out"): - return False - filters["linkDirection"] = link_direction - - if link_name_regex is not None: - filters["linkNameRegex"] = link_name_regex - return True - - def get_entities_links( - self, - project_name: str, - entity_type: str, - entity_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - link_names: Optional[Iterable[str]] = None, - link_name_regex: Optional[str] = None, - ) -> Dict[str, List[Dict[str, Any]]]: - """Helper method to get links from server for entity types. - - .. highlight:: text - .. code-block:: text - - Example output: - { - "59a212c0d2e211eda0e20242ac120001": [ - { - "id": "59a212c0d2e211eda0e20242ac120002", - "linkType": "reference", - "description": "reference link between folders", - "projectName": "my_project", - "author": "frantadmin", - "entityId": "b1df109676db11ed8e8c6c9466b19aa8", - "entityType": "folder", - "direction": "out" - }, - ... - ], - ... - } - - Args: - project_name (str): Project where links are. - entity_type (Literal["folder", "task", "product", - "version", "representations"]): Entity type. - entity_ids (Optional[Iterable[str]]): Ids of entities for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - link_names (Optional[Iterable[str]]): Link name filters. - link_name_regex (Optional[str]): Regex filter for link name. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by entity ids. - - """ - if entity_type == "folder": - query_func = folders_graphql_query - id_filter_key = "folderIds" - project_sub_key = "folders" - elif entity_type == "task": - query_func = tasks_graphql_query - id_filter_key = "taskIds" - project_sub_key = "tasks" - elif entity_type == "product": - query_func = products_graphql_query - id_filter_key = "productIds" - project_sub_key = "products" - elif entity_type == "version": - query_func = versions_graphql_query - id_filter_key = "versionIds" - project_sub_key = "versions" - elif entity_type == "representation": - query_func = representations_graphql_query - id_filter_key = "representationIds" - project_sub_key = "representations" - else: - raise ValueError("Unknown type \"{}\". Expected {}".format( - entity_type, - ", ".join( - ("folder", "task", "product", "version", "representation") - ) - )) - - output = collections.defaultdict(list) - filters = { - "projectName": project_name - } - if entity_ids is not None: - entity_ids = set(entity_ids) - if not entity_ids: - return output - filters[id_filter_key] = list(entity_ids) - - if not self._prepare_link_filters( - filters, link_types, link_direction, link_names, link_name_regex - ): - return output - - link_fields = {"id", "links"} - query = query_func(link_fields) - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - for parsed_data in query.continuous_query(self): - for entity in parsed_data["project"][project_sub_key]: - entity_id = entity["id"] - output[entity_id].extend(entity["links"]) - return output - - def get_folders_links( - self, - project_name: str, - folder_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> Dict[str, List[Dict[str, Any]]]: - """Query folders links from server. - - Args: - project_name (str): Project where links are. - folder_ids (Optional[Iterable[str]]): Ids of folders for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by folder ids. - - """ - return self.get_entities_links( - project_name, "folder", folder_ids, link_types, link_direction - ) - - def get_folder_links( - self, - project_name: str, - folder_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> List[Dict[str, Any]]: - """Query folder links from server. - - Args: - project_name (str): Project where links are. - folder_id (str): Folder id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of folder. - - """ - return self.get_folders_links( - project_name, [folder_id], link_types, link_direction - )[folder_id] - - def get_tasks_links( - self, - project_name: str, - task_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> Dict[str, List[Dict[str, Any]]]: - """Query tasks links from server. - - Args: - project_name (str): Project where links are. - task_ids (Optional[Iterable[str]]): Ids of tasks for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by task ids. - - """ - return self.get_entities_links( - project_name, "task", task_ids, link_types, link_direction - ) - - def get_task_links( - self, - project_name: str, - task_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> List[Dict[str, Any]]: - """Query task links from server. - - Args: - project_name (str): Project where links are. - task_id (str): Task id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of task. - - """ - return self.get_tasks_links( - project_name, [task_id], link_types, link_direction - )[task_id] - - def get_products_links( - self, - project_name: str, - product_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> Dict[str, List[Dict[str, Any]]]: - """Query products links from server. - - Args: - project_name (str): Project where links are. - product_ids (Optional[Iterable[str]]): Ids of products for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by product ids. - - """ - return self.get_entities_links( - project_name, "product", product_ids, link_types, link_direction - ) - - def get_product_links( - self, - project_name: str, - product_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> List[Dict[str, Any]]: - """Query product links from server. - - Args: - project_name (str): Project where links are. - product_id (str): Product id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of product. - - """ - return self.get_products_links( - project_name, [product_id], link_types, link_direction - )[product_id] - - def get_versions_links( - self, - project_name: str, - version_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> Dict[str, List[Dict[str, Any]]]: - """Query versions links from server. - - Args: - project_name (str): Project where links are. - version_ids (Optional[Iterable[str]]): Ids of versions for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by version ids. - - """ - return self.get_entities_links( - project_name, "version", version_ids, link_types, link_direction - ) - - def get_version_links( - self, - project_name: str, - version_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> List[Dict[str, Any]]: - """Query version links from server. - - Args: - project_name (str): Project where links are. - version_id (str): Version id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of version. - - """ - return self.get_versions_links( - project_name, [version_id], link_types, link_direction - )[version_id] - - def get_representations_links( - self, - project_name: str, - representation_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - ) -> Dict[str, List[Dict[str, Any]]]: - """Query representations links from server. - - Args: - project_name (str): Project where links are. - representation_ids (Optional[Iterable[str]]): Ids of - representations for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by representation ids. - - """ - return self.get_entities_links( - project_name, - "representation", - representation_ids, - link_types, - link_direction - ) - - def get_representation_links( - self, - project_name: str, - representation_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None - ) -> List[Dict[str, Any]]: - """Query representation links from server. - - Args: - project_name (str): Project where links are. - representation_id (str): Representation id for which links - should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of representation. - - """ - return self.get_representations_links( - project_name, [representation_id], link_types, link_direction - )[representation_id] - # --- Batch operations processing --- def send_batch_operations( self, From 186bc3641e7baf0c25da06b22743daf118a204f8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:05:40 +0200 Subject: [PATCH 12/53] move more methods related to projects --- ayon_api/_projects.py | 149 +++++++++++++++++++++++++++++++++++++++ ayon_api/constants.py | 8 +++ ayon_api/server_api.py | 154 +---------------------------------------- 3 files changed, 158 insertions(+), 153 deletions(-) diff --git a/ayon_api/_projects.py b/ayon_api/_projects.py index 62c0523d2..9edc5948f 100644 --- a/ayon_api/_projects.py +++ b/ayon_api/_projects.py @@ -5,6 +5,7 @@ from typing import Optional, Generator, Iterable, Any from ._base import _BaseServerAPI +from .constants import PROJECT_NAME_REGEX from .utils import prepare_query_string, fill_own_attribs from .graphql_queries import projects_graphql_query @@ -188,6 +189,154 @@ def get_project( project["productTypes"] = graphql_project["productTypes"] return project + def create_project( + self, + project_name: str, + project_code: str, + library_project: bool = False, + preset_name: Optional[str] = None, + ) -> "ProjectDict": + """Create project using AYON settings. + + This project creation function is not validating project entity on + creation. It is because project entity is created blindly with only + minimum required information about project which is name and code. + + Entered project name must be unique and project must not exist yet. + + Note: + This function is here to be OP v4 ready but in v3 has more logic + to do. That's why inner imports are in the body. + + Args: + project_name (str): New project name. Should be unique. + project_code (str): Project's code should be unique too. + library_project (Optional[bool]): Project is library project. + preset_name (Optional[str]): Name of anatomy preset. Default is + used if not passed. + + Raises: + ValueError: When project name already exists. + + Returns: + ProjectDict: Created project entity. + + """ + if self.get_project(project_name): + raise ValueError( + f"Project with name \"{project_name}\" already exists" + ) + + if not PROJECT_NAME_REGEX.match(project_name): + raise ValueError( + f"Project name \"{project_name}\" contain invalid characters" + ) + + preset = self.get_project_anatomy_preset(preset_name) + + result = self.post( + "projects", + name=project_name, + code=project_code, + anatomy=preset, + library=library_project + ) + + if result.status != 201: + details = f"Unknown details ({result.status})" + if result.data: + details = result.data.get("detail") or details + raise ValueError( + f"Failed to create project \"{project_name}\": {details}" + ) + + return self.get_project(project_name) + + def update_project( + self, + project_name: str, + library: Optional[bool] = None, + folder_types: Optional[list[dict[str, Any]]] = None, + task_types: Optional[list[dict[str, Any]]] = None, + link_types: Optional[list[dict[str, Any]]] = None, + statuses: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[dict[str, Any]]] = None, + config: Optional[dict[str, Any]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + active: Optional[bool] = None, + project_code: Optional[str] = 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[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( + f"projects/{project_name}", + **changes + ) + response.raise_for_status() + + def delete_project(self, project_name: str): + """Delete project from server. + + This will completely remove project from server without any step back. + + Args: + project_name (str): Project name that will be removed. + + """ + if not self.get_project(project_name): + raise ValueError( + f"Project with name \"{project_name}\" was not found" + ) + + result = self.delete(f"projects/{project_name}") + if result.status_code != 204: + detail = result.data["detail"] + raise ValueError( + f"Failed to delete project \"{project_name}\". {detail}" + ) + def _get_project_graphql_fields( self, fields: Optional[set[str]] ) -> tuple[set[str], bool]: diff --git a/ayon_api/constants.py b/ayon_api/constants.py index d47e4fc59..6dada2de5 100644 --- a/ayon_api/constants.py +++ b/ayon_api/constants.py @@ -1,3 +1,5 @@ +import re + # Environments where server url and api key are stored for global connection SERVER_URL_ENV_KEY = "AYON_SERVER_URL" SERVER_API_ENV_KEY = "AYON_API_KEY" @@ -11,6 +13,12 @@ # Backwards compatibility SERVER_TOKEN_ENV_KEY = SERVER_API_ENV_KEY +# This should be collected from server schema +PROJECT_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_" +PROJECT_NAME_REGEX = re.compile( + f"^[{PROJECT_NAME_ALLOWED_SYMBOLS}]+$" +) + # --- User --- DEFAULT_USER_FIELDS = { "accessGroups", diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index cfa5504bd..80dc669aa 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -145,11 +145,7 @@ PatternType = type(re.compile("")) JSONDecodeError = getattr(json, "JSONDecodeError", ValueError) -# This should be collected from server schema -PROJECT_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_" -PROJECT_NAME_REGEX = re.compile( - "^[{}]+$".format(PROJECT_NAME_ALLOWED_SYMBOLS) -) + _PLACEHOLDER = object() VERSION_REGEX = re.compile( @@ -7590,154 +7586,6 @@ def update_thumbnail( ) response.raise_for_status() - def create_project( - self, - project_name: str, - project_code: str, - library_project: bool = False, - preset_name: Optional[str] = None, - ) -> "ProjectDict": - """Create project using AYON settings. - - This project creation function is not validating project entity on - creation. It is because project entity is created blindly with only - minimum required information about project which is name and code. - - Entered project name must be unique and project must not exist yet. - - Note: - This function is here to be OP v4 ready but in v3 has more logic - to do. That's why inner imports are in the body. - - Args: - project_name (str): New project name. Should be unique. - project_code (str): Project's code should be unique too. - library_project (Optional[bool]): Project is library project. - preset_name (Optional[str]): Name of anatomy preset. Default is - used if not passed. - - Raises: - ValueError: When project name already exists. - - Returns: - ProjectDict: Created project entity. - - """ - if self.get_project(project_name): - raise ValueError( - f"Project with name \"{project_name}\" already exists" - ) - - if not PROJECT_NAME_REGEX.match(project_name): - raise ValueError( - f"Project name \"{project_name}\" contain invalid characters" - ) - - preset = self.get_project_anatomy_preset(preset_name) - - result = self.post( - "projects", - name=project_name, - code=project_code, - anatomy=preset, - library=library_project - ) - - if result.status != 201: - details = f"Unknown details ({result.status})" - if result.data: - details = result.data.get("detail") or details - raise ValueError( - f"Failed to create project \"{project_name}\": {details}" - ) - - return self.get_project(project_name) - - def update_project( - self, - project_name: str, - library: Optional[bool] = None, - folder_types: Optional[List[Dict[str, Any]]] = None, - task_types: Optional[List[Dict[str, Any]]] = None, - link_types: Optional[List[Dict[str, Any]]] = None, - statuses: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[Dict[str, Any]]] = None, - config: Optional[Dict[str, Any]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - active: Optional[bool] = None, - project_code: Optional[str] = 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[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( - f"projects/{project_name}", - **changes - ) - response.raise_for_status() - - def delete_project(self, project_name: str): - """Delete project from server. - - This will completely remove project from server without any step back. - - Args: - project_name (str): Project name that will be removed. - - """ - if not self.get_project(project_name): - raise ValueError( - f"Project with name \"{project_name}\" was not found" - ) - - result = self.delete(f"projects/{project_name}") - if result.status_code != 204: - detail = result.data["detail"] - raise ValueError( - f"Failed to delete project \"{project_name}\". {detail}" - ) - # --- Batch operations processing --- def send_batch_operations( self, From 4841222568e449a6675a999d7b3f1a0625f8e715 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:09:45 +0200 Subject: [PATCH 13/53] change order of api functions --- ayon_api/__init__.py | 12 +-- ayon_api/_api.py | 232 +++++++++++++++++++++---------------------- 2 files changed, 122 insertions(+), 122 deletions(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index c9fc2af89..abdb78bd6 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -207,9 +207,6 @@ get_workfile_thumbnail, create_thumbnail, update_thumbnail, - create_project, - update_project, - delete_project, get_full_link_type_name, get_link_types, get_link_type, @@ -261,6 +258,9 @@ get_project_names, get_projects, get_project, + create_project, + update_project, + delete_project, ) @@ -471,9 +471,6 @@ "get_workfile_thumbnail", "create_thumbnail", "update_thumbnail", - "create_project", - "update_project", - "delete_project", "get_full_link_type_name", "get_link_types", "get_link_type", @@ -525,4 +522,7 @@ "get_project_names", "get_projects", "get_project", + "create_project", + "update_project", + "delete_project", ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 79d776a22..edd1bd18c 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -5621,122 +5621,6 @@ def update_thumbnail( ) -def create_project( - project_name: str, - project_code: str, - library_project: bool = False, - preset_name: Optional[str] = None, -) -> "ProjectDict": - """Create project using AYON settings. - - This project creation function is not validating project entity on - creation. It is because project entity is created blindly with only - minimum required information about project which is name and code. - - Entered project name must be unique and project must not exist yet. - - Note: - This function is here to be OP v4 ready but in v3 has more logic - to do. That's why inner imports are in the body. - - Args: - project_name (str): New project name. Should be unique. - project_code (str): Project's code should be unique too. - library_project (Optional[bool]): Project is library project. - preset_name (Optional[str]): Name of anatomy preset. Default is - used if not passed. - - Raises: - ValueError: When project name already exists. - - Returns: - ProjectDict: Created project entity. - - """ - con = get_server_api_connection() - return con.create_project( - project_name=project_name, - project_code=project_code, - library_project=library_project, - preset_name=preset_name, - ) - - -def update_project( - project_name: str, - library: Optional[bool] = None, - folder_types: Optional[List[Dict[str, Any]]] = None, - task_types: Optional[List[Dict[str, Any]]] = None, - link_types: Optional[List[Dict[str, Any]]] = None, - statuses: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[Dict[str, Any]]] = None, - config: Optional[Dict[str, Any]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - active: Optional[bool] = None, - project_code: Optional[str] = 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[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. - - """ - con = get_server_api_connection() - return con.update_project( - project_name=project_name, - library=library, - folder_types=folder_types, - task_types=task_types, - link_types=link_types, - statuses=statuses, - tags=tags, - config=config, - attrib=attrib, - data=data, - active=active, - project_code=project_code, - **changes, - ) - - -def delete_project( - project_name: str, -): - """Delete project from server. - - This will completely remove project from server without any step back. - - Args: - project_name (str): Project name that will be removed. - - """ - con = get_server_api_connection() - return con.delete_project( - project_name=project_name, - ) - - def get_full_link_type_name( link_type_name: str, input_type: str, @@ -7316,3 +7200,119 @@ def get_project( fields=fields, own_attributes=own_attributes, ) + + +def create_project( + project_name: str, + project_code: str, + library_project: bool = False, + preset_name: Optional[str] = None, +) -> "ProjectDict": + """Create project using AYON settings. + + This project creation function is not validating project entity on + creation. It is because project entity is created blindly with only + minimum required information about project which is name and code. + + Entered project name must be unique and project must not exist yet. + + Note: + This function is here to be OP v4 ready but in v3 has more logic + to do. That's why inner imports are in the body. + + Args: + project_name (str): New project name. Should be unique. + project_code (str): Project's code should be unique too. + library_project (Optional[bool]): Project is library project. + preset_name (Optional[str]): Name of anatomy preset. Default is + used if not passed. + + Raises: + ValueError: When project name already exists. + + Returns: + ProjectDict: Created project entity. + + """ + con = get_server_api_connection() + return con.create_project( + project_name=project_name, + project_code=project_code, + library_project=library_project, + preset_name=preset_name, + ) + + +def update_project( + project_name: str, + library: Optional[bool] = None, + folder_types: Optional[list[dict[str, Any]]] = None, + task_types: Optional[list[dict[str, Any]]] = None, + link_types: Optional[list[dict[str, Any]]] = None, + statuses: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[dict[str, Any]]] = None, + config: Optional[dict[str, Any]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + active: Optional[bool] = None, + project_code: Optional[str] = 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[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. + + """ + con = get_server_api_connection() + return con.update_project( + project_name=project_name, + library=library, + folder_types=folder_types, + task_types=task_types, + link_types=link_types, + statuses=statuses, + tags=tags, + config=config, + attrib=attrib, + data=data, + active=active, + project_code=project_code, + **changes, + ) + + +def delete_project( + project_name: str, +): + """Delete project from server. + + This will completely remove project from server without any step back. + + Args: + project_name (str): Project name that will be removed. + + """ + con = get_server_api_connection() + return con.delete_project( + project_name=project_name, + ) From f1114ae860df7ef498f9c60d0736a660d99aa48b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:10:17 +0200 Subject: [PATCH 14/53] change links order --- ayon_api/__init__.py | 60 +- ayon_api/_api.py | 1520 +++++++++++++++++++++--------------------- 2 files changed, 790 insertions(+), 790 deletions(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index abdb78bd6..a7c41c734 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -207,6 +207,21 @@ get_workfile_thumbnail, create_thumbnail, update_thumbnail, + send_batch_operations, + send_activities_batch_operations, + get_actions, + trigger_action, + get_action_config, + set_action_config, + take_action, + abort_action, + get_addon_endpoint, + get_addons_info, + get_addon_url, + delete_addon, + delete_addon_version, + upload_addon_zip, + download_addon_private_file, get_full_link_type_name, get_link_types, get_link_type, @@ -226,21 +241,6 @@ get_version_links, get_representations_links, get_representation_links, - send_batch_operations, - send_activities_batch_operations, - get_actions, - trigger_action, - get_action_config, - set_action_config, - take_action, - abort_action, - get_addon_endpoint, - get_addons_info, - get_addon_url, - delete_addon, - delete_addon_version, - upload_addon_zip, - download_addon_private_file, get_entity_lists, get_entity_list_rest, get_entity_list_by_id, @@ -471,6 +471,21 @@ "get_workfile_thumbnail", "create_thumbnail", "update_thumbnail", + "send_batch_operations", + "send_activities_batch_operations", + "get_actions", + "trigger_action", + "get_action_config", + "set_action_config", + "take_action", + "abort_action", + "get_addon_endpoint", + "get_addons_info", + "get_addon_url", + "delete_addon", + "delete_addon_version", + "upload_addon_zip", + "download_addon_private_file", "get_full_link_type_name", "get_link_types", "get_link_type", @@ -490,21 +505,6 @@ "get_version_links", "get_representations_links", "get_representation_links", - "send_batch_operations", - "send_activities_batch_operations", - "get_actions", - "trigger_action", - "get_action_config", - "set_action_config", - "take_action", - "abort_action", - "get_addon_endpoint", - "get_addons_info", - "get_addon_url", - "delete_addon", - "delete_addon_version", - "upload_addon_zip", - "download_addon_private_file", "get_entity_lists", "get_entity_list_rest", "get_entity_list_by_id", diff --git a/ayon_api/_api.py b/ayon_api/_api.py index edd1bd18c..26105e319 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -5621,1094 +5621,1094 @@ def update_thumbnail( ) -def get_full_link_type_name( - link_type_name: str, - input_type: str, - output_type: str, -) -> str: - """Calculate full link type name used for query from server. +def send_batch_operations( + project_name: str, + operations: List[Dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True, +) -> List[Dict[str, Any]]: + """Post multiple CRUD operations to server. + + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. Args: - link_type_name (str): Type of link. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. Returns: - str: Full name of link type used for query from server. + list[dict[str, Any]]: Operations result with process details. """ con = get_server_api_connection() - return con.get_full_link_type_name( - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, + return con.send_batch_operations( + project_name=project_name, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) -def get_link_types( +def send_activities_batch_operations( project_name: str, + operations: List[Dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True, ) -> List[Dict[str, Any]]: - """All link types available on a project. + """Post multiple CRUD activities operations to server. - Example output: - [ - { - "name": "reference|folder|folder", - "link_type": "reference", - "input_type": "folder", - "output_type": "folder", - "data": {} - } - ] + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. Args: - project_name (str): Name of project where to look for link types. + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. Returns: - list[dict[str, Any]]: Link types available on project. + list[dict[str, Any]]: Operations result with process details. """ con = get_server_api_connection() - return con.get_link_types( + return con.send_activities_batch_operations( project_name=project_name, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) -def get_link_type( - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, -) -> Optional[str]: - """Get link type data. - - There is not dedicated REST endpoint to get single link type, - so method 'get_link_types' is used. - - Example output: - { - "name": "reference|folder|folder", - "link_type": "reference", - "input_type": "folder", - "output_type": "folder", - "data": {} - } +def get_actions( + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, + mode: Optional["ActionModeType"] = None, +) -> List["ActionManifestDict"]: + """Get actions for a context. Args: - project_name (str): Project where link type is available. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + mode (Optional[ActionModeType]): Action modes. Returns: - Optional[str]: Link type information. + List[ActionManifestDict]: List of action manifests. """ con = get_server_api_connection() - return con.get_link_type( + return con.get_actions( project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + mode=mode, ) -def create_link_type( - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - data: Optional[Dict[str, Any]] = None, -): - """Create or update link type on server. - - Warning: - Because PUT is used for creation it is also used for update. +def trigger_action( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionTriggerResponse": + """Trigger action. Args: - project_name (str): Project where link type is created. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - data (Optional[dict[str, Any]]): Additional data related to link. - - Raises: - HTTPRequestError: Server error happened. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. """ con = get_server_api_connection() - return con.create_link_type( + return con.trigger_action( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, - data=data, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def delete_link_type( - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, -): - """Remove link type from project. +def get_action_config( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Get action configuration. Args: - project_name (str): Project where link type is created. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. - Raises: - HTTPRequestError: Server error happened. + Returns: + ActionConfigResponse: Action configuration data. """ con = get_server_api_connection() - return con.delete_link_type( + return con.get_action_config( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def make_sure_link_type_exists( - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - data: Optional[Dict[str, Any]] = None, -): - """Make sure link type exists on a project. +def set_action_config( + identifier: str, + addon_name: str, + addon_version: str, + value: Dict[str, Any], + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Set action configuration. Args: - project_name (str): Name of project. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - data (Optional[dict[str, Any]]): Link type related data. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + value (Optional[Dict[str, Any]]): Value of the action + configuration. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + + Returns: + ActionConfigResponse: New action configuration data. """ con = get_server_api_connection() - return con.make_sure_link_type_exists( + return con.set_action_config( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, + value=value, project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, - data=data, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def create_link( - project_name: str, - link_type_name: str, - input_id: str, - input_type: str, - output_id: str, - output_type: str, - link_name: Optional[str] = None, -): - """Create link between 2 entities. - - Link has a type which must already exists on a project. - - Example output:: - - { - "id": "59a212c0d2e211eda0e20242ac120002" - } +def take_action( + action_token: str, +) -> "ActionTakeResponse": + """Take action metadata using an action token. Args: - project_name (str): Project where the link is created. - link_type_name (str): Type of link. - input_id (str): Input entity id. - input_type (str): Entity type of input entity. - output_id (str): Output entity id. - output_type (str): Entity type of output entity. - link_name (Optional[str]): Name of link. - Available from server version '1.0.0-rc.6'. + action_token (str): AYON launcher action token. Returns: - dict[str, str]: Information about link. - - Raises: - HTTPRequestError: Server error happened. + ActionTakeResponse: Action metadata describing how to launch + action. """ con = get_server_api_connection() - return con.create_link( - project_name=project_name, - link_type_name=link_type_name, - input_id=input_id, - input_type=input_type, - output_id=output_id, - output_type=output_type, - link_name=link_name, + return con.take_action( + action_token=action_token, ) -def delete_link( - project_name: str, - link_id: str, -): - """Remove link by id. +def abort_action( + action_token: str, + message: Optional[str] = None, +) -> None: + """Abort action using an action token. Args: - project_name (str): Project where link exists. - link_id (str): Id of link. - - Raises: - HTTPRequestError: Server error happened. + action_token (str): AYON launcher action token. + message (Optional[str]): Message to display in the UI. """ con = get_server_api_connection() - return con.delete_link( - project_name=project_name, - link_id=link_id, + return con.abort_action( + action_token=action_token, + message=message, ) -def get_entities_links( - project_name: str, - entity_type: str, - entity_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - link_names: Optional[Iterable[str]] = None, - link_name_regex: Optional[str] = None, -) -> Dict[str, List[Dict[str, Any]]]: - """Helper method to get links from server for entity types. - - .. highlight:: text - .. code-block:: text +def get_addon_endpoint( + addon_name: str, + addon_version: str, + *subpaths, +) -> str: + """Calculate endpoint to addon route. - Example output: - { - "59a212c0d2e211eda0e20242ac120001": [ - { - "id": "59a212c0d2e211eda0e20242ac120002", - "linkType": "reference", - "description": "reference link between folders", - "projectName": "my_project", - "author": "frantadmin", - "entityId": "b1df109676db11ed8e8c6c9466b19aa8", - "entityType": "folder", - "direction": "out" - }, - ... - ], - ... - } + Examples: + >>> from ayon_api import ServerAPI + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'addons/example/1.0.0/private/my.zip' Args: - project_name (str): Project where links are. - entity_type (Literal["folder", "task", "product", - "version", "representations"]): Entity type. - entity_ids (Optional[Iterable[str]]): Ids of entities for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - link_names (Optional[Iterable[str]]): Link name filters. - link_name_regex (Optional[str]): Regex filter for link name. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. Returns: - dict[str, list[dict[str, Any]]]: Link info by entity ids. + str: Final url. """ con = get_server_api_connection() - return con.get_entities_links( - project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - link_types=link_types, - link_direction=link_direction, - link_names=link_names, - link_name_regex=link_name_regex, + return con.get_addon_endpoint( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, ) -def get_folders_links( - project_name: str, - folder_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> Dict[str, List[Dict[str, Any]]]: - """Query folders links from server. +def get_addons_info( + details: bool = True, +) -> "AddonsInfoDict": + """Get information about addons available on server. Args: - project_name (str): Project where links are. - folder_ids (Optional[Iterable[str]]): Ids of folders for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by folder ids. + details (Optional[bool]): Detailed data with information how + to get client code. """ con = get_server_api_connection() - return con.get_folders_links( - project_name=project_name, - folder_ids=folder_ids, - link_types=link_types, - link_direction=link_direction, + return con.get_addons_info( + details=details, ) -def get_folder_links( - project_name: str, - folder_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> List[Dict[str, Any]]: - """Query folder links from server. +def get_addon_url( + addon_name: str, + addon_version: str, + *subpaths, + use_rest: bool = True, +) -> str: + """Calculate url to addon route. + + Examples: + + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' Args: - project_name (str): Project where links are. - folder_id (str): Folder id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. + use_rest (Optional[bool]): Use rest endpoint. Returns: - list[dict[str, Any]]: Link info of folder. + str: Final url. """ con = get_server_api_connection() - return con.get_folder_links( - project_name=project_name, - folder_id=folder_id, - link_types=link_types, - link_direction=link_direction, + return con.get_addon_url( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, + use_rest=use_rest, ) -def get_tasks_links( - project_name: str, - task_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> Dict[str, List[Dict[str, Any]]]: - """Query tasks links from server. +def delete_addon( + addon_name: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon from server. - Args: - project_name (str): Project where links are. - task_ids (Optional[Iterable[str]]): Ids of tasks for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + Delete all versions of addon from server. - Returns: - dict[str, list[dict[str, Any]]]: Link info by task ids. + Args: + addon_name (str): Addon name. + purge (Optional[bool]): Purge all data related to the addon. """ con = get_server_api_connection() - return con.get_tasks_links( - project_name=project_name, - task_ids=task_ids, - link_types=link_types, - link_direction=link_direction, + return con.delete_addon( + addon_name=addon_name, + purge=purge, ) -def get_task_links( - project_name: str, - task_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> List[Dict[str, Any]]: - """Query task links from server. +def delete_addon_version( + addon_name: str, + addon_version: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon version from server. - Args: - project_name (str): Project where links are. - task_id (str): Task id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + Delete all versions of addon from server. - Returns: - list[dict[str, Any]]: Link info of task. + Args: + addon_name (str): Addon name. + addon_version (str): Addon version. + purge (Optional[bool]): Purge all data related to the addon. """ con = get_server_api_connection() - return con.get_task_links( - project_name=project_name, - task_id=task_id, - link_types=link_types, - link_direction=link_direction, + return con.delete_addon_version( + addon_name=addon_name, + addon_version=addon_version, + purge=purge, ) -def get_products_links( - project_name: str, - product_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> Dict[str, List[Dict[str, Any]]]: - """Query products links from server. - - Args: - project_name (str): Project where links are. - product_ids (Optional[Iterable[str]]): Ids of products for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by product ids. +def upload_addon_zip( + src_filepath: str, + progress: Optional[TransferProgress] = None, +): + """Upload addon zip file to server. - """ - con = get_server_api_connection() - return con.get_products_links( - project_name=project_name, - product_ids=product_ids, - link_types=link_types, - link_direction=link_direction, - ) + File is validated on server. If it is valid, it is installed. It will + create an event job which can be tracked (tracking part is not + implemented yet). + Example output:: -def get_product_links( - project_name: str, - product_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> List[Dict[str, Any]]: - """Query product links from server. + {'eventId': 'a1bfbdee27c611eea7580242ac120003'} Args: - project_name (str): Project where links are. - product_id (str): Product id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + src_filepath (str): Path to a zip file. + progress (Optional[TransferProgress]): Object to keep track about + upload state. Returns: - list[dict[str, Any]]: Link info of product. + dict[str, Any]: Response data from server. """ con = get_server_api_connection() - return con.get_product_links( - project_name=project_name, - product_id=product_id, - link_types=link_types, - link_direction=link_direction, + return con.upload_addon_zip( + src_filepath=src_filepath, + progress=progress, ) -def get_versions_links( - project_name: str, - version_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> Dict[str, List[Dict[str, Any]]]: - """Query versions links from server. +def download_addon_private_file( + addon_name: str, + addon_version: str, + filename: str, + destination_dir: str, + destination_filename: Optional[str] = None, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, +) -> str: + """Download a file from addon private files. + + This method requires to have authorized token available. Private files + are not under '/api' restpoint. Args: - project_name (str): Project where links are. - version_ids (Optional[Iterable[str]]): Ids of versions for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + addon_name (str): Addon name. + addon_version (str): Addon version. + filename (str): Filename in private folder on server. + destination_dir (str): Where the file should be downloaded. + destination_filename (Optional[str]): Name of destination + filename. Source filename is used if not passed. + chunk_size (Optional[int]): Download chunk size. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. Returns: - dict[str, list[dict[str, Any]]]: Link info by version ids. + str: Filepath to downloaded file. """ con = get_server_api_connection() - return con.get_versions_links( - project_name=project_name, - version_ids=version_ids, - link_types=link_types, - link_direction=link_direction, + return con.download_addon_private_file( + addon_name=addon_name, + addon_version=addon_version, + filename=filename, + destination_dir=destination_dir, + destination_filename=destination_filename, + chunk_size=chunk_size, + progress=progress, ) -def get_version_links( - project_name: str, - version_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> List[Dict[str, Any]]: - """Query version links from server. +def get_full_link_type_name( + link_type_name: str, + input_type: str, + output_type: str, +) -> str: + """Calculate full link type name used for query from server. Args: - project_name (str): Project where links are. - version_id (str): Version id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + link_type_name (str): Type of link. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. Returns: - list[dict[str, Any]]: Link info of version. + str: Full name of link type used for query from server. - """ - con = get_server_api_connection() - return con.get_version_links( - project_name=project_name, - version_id=version_id, - link_types=link_types, - link_direction=link_direction, + """ + con = get_server_api_connection() + return con.get_full_link_type_name( + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) -def get_representations_links( +def get_link_types( project_name: str, - representation_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> Dict[str, List[Dict[str, Any]]]: - """Query representations links from server. +) -> list[dict[str, Any]]: + """All link types available on a project. + + Example output: + [ + { + "name": "reference|folder|folder", + "link_type": "reference", + "input_type": "folder", + "output_type": "folder", + "data": {} + } + ] Args: - project_name (str): Project where links are. - representation_ids (Optional[Iterable[str]]): Ids of - representations for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project where to look for link types. Returns: - dict[str, list[dict[str, Any]]]: Link info by representation ids. + list[dict[str, Any]]: Link types available on project. """ con = get_server_api_connection() - return con.get_representations_links( + return con.get_link_types( project_name=project_name, - representation_ids=representation_ids, - link_types=link_types, - link_direction=link_direction, ) -def get_representation_links( +def get_link_type( project_name: str, - representation_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> List[Dict[str, Any]]: - """Query representation links from server. + link_type_name: str, + input_type: str, + output_type: str, +) -> Optional[dict[str, Any]]: + """Get link type data. + + There is not dedicated REST endpoint to get single link type, + so method 'get_link_types' is used. + + Example output: + { + "name": "reference|folder|folder", + "link_type": "reference", + "input_type": "folder", + "output_type": "folder", + "data": {} + } Args: - project_name (str): Project where links are. - representation_id (str): Representation id for which links - should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Project where link type is available. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. Returns: - list[dict[str, Any]]: Link info of representation. + Optional[dict[str, Any]]: Link type information. """ con = get_server_api_connection() - return con.get_representation_links( + return con.get_link_type( project_name=project_name, - representation_id=representation_id, - link_types=link_types, - link_direction=link_direction, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) -def send_batch_operations( +def create_link_type( project_name: str, - operations: List[Dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True, -) -> List[Dict[str, Any]]: - """Post multiple CRUD operations to server. + link_type_name: str, + input_type: str, + output_type: str, + data: Optional[dict[str, Any]] = None, +): + """Create or update link type on server. - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. + Warning: + Because PUT is used for creation it is also used for update. Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. + project_name (str): Project where link type is created. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + data (Optional[dict[str, Any]]): Additional data related to link. Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. - - Returns: - list[dict[str, Any]]: Operations result with process details. + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.send_batch_operations( + return con.create_link_type( project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, + data=data, ) -def send_activities_batch_operations( +def delete_link_type( project_name: str, - operations: List[Dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True, -) -> List[Dict[str, Any]]: - """Post multiple CRUD activities operations to server. - - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. + link_type_name: str, + input_type: str, + output_type: str, +): + """Remove link type from project. Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. + project_name (str): Project where link type is created. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. - - Returns: - list[dict[str, Any]]: Operations result with process details. + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.send_activities_batch_operations( + return con.delete_link_type( project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) -def get_actions( - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, - mode: Optional["ActionModeType"] = None, -) -> List["ActionManifestDict"]: - """Get actions for a context. +def make_sure_link_type_exists( + project_name: str, + link_type_name: str, + input_type: str, + output_type: str, + data: Optional[dict[str, Any]] = None, +): + """Make sure link type exists on a project. Args: - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. - mode (Optional[ActionModeType]): Action modes. - - Returns: - List[ActionManifestDict]: List of action manifests. + project_name (str): Name of project. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + data (Optional[dict[str, Any]]): Link type related data. """ con = get_server_api_connection() - return con.get_actions( + return con.make_sure_link_type_exists( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, - mode=mode, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, + data=data, ) -def trigger_action( - identifier: str, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionTriggerResponse": - """Trigger action. +def create_link( + project_name: str, + link_type_name: str, + input_id: str, + input_type: str, + output_id: str, + output_type: str, + link_name: Optional[str] = None, +): + """Create link between 2 entities. + + Link has a type which must already exists on a project. + + Example output:: + + { + "id": "59a212c0d2e211eda0e20242ac120002" + } + + Args: + project_name (str): Project where the link is created. + link_type_name (str): Type of link. + input_id (str): Input entity id. + input_type (str): Entity type of input entity. + output_id (str): Output entity id. + output_type (str): Entity type of output entity. + link_name (Optional[str]): Name of link. + Available from server version '1.0.0-rc.6'. - Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + Returns: + dict[str, str]: Information about link. + + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.trigger_action( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, + return con.create_link( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + link_type_name=link_type_name, + input_id=input_id, + input_type=input_type, + output_id=output_id, + output_type=output_type, + link_name=link_name, ) -def get_action_config( - identifier: str, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Get action configuration. +def delete_link( + project_name: str, + link_id: str, +): + """Remove link by id. Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + project_name (str): Project where link exists. + link_id (str): Id of link. - Returns: - ActionConfigResponse: Action configuration data. + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_action_config( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, + return con.delete_link( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + link_id=link_id, ) -def set_action_config( - identifier: str, - addon_name: str, - addon_version: str, - value: Dict[str, Any], - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Set action configuration. +def get_entities_links( + project_name: str, + entity_type: str, + entity_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + link_names: Optional[Iterable[str]] = None, + link_name_regex: Optional[str] = None, +) -> dict[str, list[dict[str, Any]]]: + """Helper method to get links from server for entity types. + + .. highlight:: text + .. code-block:: text + + Example output: + { + "59a212c0d2e211eda0e20242ac120001": [ + { + "id": "59a212c0d2e211eda0e20242ac120002", + "linkType": "reference", + "description": "reference link between folders", + "projectName": "my_project", + "author": "frantadmin", + "entityId": "b1df109676db11ed8e8c6c9466b19aa8", + "entityType": "folder", + "direction": "out" + }, + ... + ], + ... + } Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - value (Optional[Dict[str, Any]]): Value of the action - configuration. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + project_name (str): Project where links are. + entity_type (Literal["folder", "task", "product", + "version", "representations"]): Entity type. + entity_ids (Optional[Iterable[str]]): Ids of entities for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + link_names (Optional[Iterable[str]]): Link name filters. + link_name_regex (Optional[str]): Regex filter for link name. Returns: - ActionConfigResponse: New action configuration data. + dict[str, list[dict[str, Any]]]: Link info by entity ids. """ con = get_server_api_connection() - return con.set_action_config( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, - value=value, + return con.get_entities_links( project_name=project_name, entity_type=entity_type, entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + link_types=link_types, + link_direction=link_direction, + link_names=link_names, + link_name_regex=link_name_regex, ) -def take_action( - action_token: str, -) -> "ActionTakeResponse": - """Take action metadata using an action token. +def get_folders_links( + project_name: str, + folder_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query folders links from server. Args: - action_token (str): AYON launcher action token. + project_name (str): Project where links are. + folder_ids (Optional[Iterable[str]]): Ids of folders for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - ActionTakeResponse: Action metadata describing how to launch - action. + dict[str, list[dict[str, Any]]]: Link info by folder ids. """ con = get_server_api_connection() - return con.take_action( - action_token=action_token, + return con.get_folders_links( + project_name=project_name, + folder_ids=folder_ids, + link_types=link_types, + link_direction=link_direction, ) -def abort_action( - action_token: str, - message: Optional[str] = None, -) -> None: - """Abort action using an action token. +def get_folder_links( + project_name: str, + folder_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query folder links from server. Args: - action_token (str): AYON launcher action token. - message (Optional[str]): Message to display in the UI. + project_name (str): Project where links are. + folder_id (str): Folder id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of folder. """ con = get_server_api_connection() - return con.abort_action( - action_token=action_token, - message=message, + return con.get_folder_links( + project_name=project_name, + folder_id=folder_id, + link_types=link_types, + link_direction=link_direction, ) -def get_addon_endpoint( - addon_name: str, - addon_version: str, - *subpaths, -) -> str: - """Calculate endpoint to addon route. - - Examples: - >>> from ayon_api import ServerAPI - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'addons/example/1.0.0/private/my.zip' +def get_tasks_links( + project_name: str, + task_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query tasks links from server. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. + project_name (str): Project where links are. + task_ids (Optional[Iterable[str]]): Ids of tasks for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - str: Final url. + dict[str, list[dict[str, Any]]]: Link info by task ids. """ con = get_server_api_connection() - return con.get_addon_endpoint( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, + return con.get_tasks_links( + project_name=project_name, + task_ids=task_ids, + link_types=link_types, + link_direction=link_direction, ) -def get_addons_info( - details: bool = True, -) -> "AddonsInfoDict": - """Get information about addons available on server. +def get_task_links( + project_name: str, + task_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query task links from server. Args: - details (Optional[bool]): Detailed data with information how - to get client code. + project_name (str): Project where links are. + task_id (str): Task id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of task. """ con = get_server_api_connection() - return con.get_addons_info( - details=details, + return con.get_task_links( + project_name=project_name, + task_id=task_id, + link_types=link_types, + link_direction=link_direction, ) -def get_addon_url( - addon_name: str, - addon_version: str, - *subpaths, - use_rest: bool = True, -) -> str: - """Calculate url to addon route. - - Examples: - - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' +def get_products_links( + project_name: str, + product_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query products links from server. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. - use_rest (Optional[bool]): Use rest endpoint. + project_name (str): Project where links are. + product_ids (Optional[Iterable[str]]): Ids of products for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - str: Final url. + dict[str, list[dict[str, Any]]]: Link info by product ids. """ con = get_server_api_connection() - return con.get_addon_url( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, - use_rest=use_rest, + return con.get_products_links( + project_name=project_name, + product_ids=product_ids, + link_types=link_types, + link_direction=link_direction, ) -def delete_addon( - addon_name: str, - purge: Optional[bool] = None, -) -> None: - """Delete addon from server. - - Delete all versions of addon from server. +def get_product_links( + project_name: str, + product_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query product links from server. Args: - addon_name (str): Addon name. - purge (Optional[bool]): Purge all data related to the addon. + project_name (str): Project where links are. + product_id (str): Product id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of product. """ con = get_server_api_connection() - return con.delete_addon( - addon_name=addon_name, - purge=purge, + return con.get_product_links( + project_name=project_name, + product_id=product_id, + link_types=link_types, + link_direction=link_direction, ) -def delete_addon_version( - addon_name: str, - addon_version: str, - purge: Optional[bool] = None, -) -> None: - """Delete addon version from server. - - Delete all versions of addon from server. +def get_versions_links( + project_name: str, + version_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query versions links from server. Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - purge (Optional[bool]): Purge all data related to the addon. + project_name (str): Project where links are. + version_ids (Optional[Iterable[str]]): Ids of versions for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by version ids. """ con = get_server_api_connection() - return con.delete_addon_version( - addon_name=addon_name, - addon_version=addon_version, - purge=purge, + return con.get_versions_links( + project_name=project_name, + version_ids=version_ids, + link_types=link_types, + link_direction=link_direction, ) -def upload_addon_zip( - src_filepath: str, - progress: Optional[TransferProgress] = None, -): - """Upload addon zip file to server. +def get_version_links( + project_name: str, + version_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query version links from server. - File is validated on server. If it is valid, it is installed. It will - create an event job which can be tracked (tracking part is not - implemented yet). + Args: + project_name (str): Project where links are. + version_id (str): Version id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. - Example output:: + Returns: + list[dict[str, Any]]: Link info of version. - {'eventId': 'a1bfbdee27c611eea7580242ac120003'} + """ + con = get_server_api_connection() + return con.get_version_links( + project_name=project_name, + version_id=version_id, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_representations_links( + project_name: str, + representation_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query representations links from server. Args: - src_filepath (str): Path to a zip file. - progress (Optional[TransferProgress]): Object to keep track about - upload state. + project_name (str): Project where links are. + representation_ids (Optional[Iterable[str]]): Ids of + representations for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - dict[str, Any]: Response data from server. + dict[str, list[dict[str, Any]]]: Link info by representation ids. """ con = get_server_api_connection() - return con.upload_addon_zip( - src_filepath=src_filepath, - progress=progress, + return con.get_representations_links( + project_name=project_name, + representation_ids=representation_ids, + link_types=link_types, + link_direction=link_direction, ) -def download_addon_private_file( - addon_name: str, - addon_version: str, - filename: str, - destination_dir: str, - destination_filename: Optional[str] = None, - chunk_size: Optional[int] = None, - progress: Optional[TransferProgress] = None, -) -> str: - """Download a file from addon private files. - - This method requires to have authorized token available. Private files - are not under '/api' restpoint. +def get_representation_links( + project_name: str, + representation_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query representation links from server. Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - filename (str): Filename in private folder on server. - destination_dir (str): Where the file should be downloaded. - destination_filename (Optional[str]): Name of destination - filename. Source filename is used if not passed. - chunk_size (Optional[int]): Download chunk size. - progress (Optional[TransferProgress]): Object that gives ability - to track download progress. + project_name (str): Project where links are. + representation_id (str): Representation id for which links + should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - str: Filepath to downloaded file. + list[dict[str, Any]]: Link info of representation. """ con = get_server_api_connection() - return con.download_addon_private_file( - addon_name=addon_name, - addon_version=addon_version, - filename=filename, - destination_dir=destination_dir, - destination_filename=destination_filename, - chunk_size=chunk_size, - progress=progress, + return con.get_representation_links( + project_name=project_name, + representation_id=representation_id, + link_types=link_types, + link_direction=link_direction, ) From 41890a0d6b064d6dda8a2a29cfa1006fd56ac903 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:33:56 +0200 Subject: [PATCH 15/53] move 'prepare_list_filters' to utils --- ayon_api/server_api.py | 15 ++++++++------- ayon_api/utils.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 80dc669aa..35c3a1282 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -103,6 +103,8 @@ SortOrder, get_machine_name, fill_own_attribs, + prepare_list_filters, + PatternType, ) from ._actions import _ActionsAPI from ._addons import _AddonsAPI @@ -143,7 +145,6 @@ StreamType, ) -PatternType = type(re.compile("")) JSONDecodeError = getattr(json, "JSONDecodeError", ValueError) _PLACEHOLDER = object() @@ -1583,7 +1584,7 @@ def get_events( statuses = states filters = {} - if not _prepare_list_filters( + if not prepare_list_filters( filters, ("eventTopics", topics), ("eventIds", event_ids), @@ -1923,7 +1924,7 @@ def get_activities( if reference_types is None: reference_types = {"origin"} - if not _prepare_list_filters( + if not prepare_list_filters( filters, ("activityIds", activity_ids), ("activityTypes", activity_types), @@ -4884,7 +4885,7 @@ def get_tasks( filters = { "projectName": project_name } - if not _prepare_list_filters( + if not prepare_list_filters( filters, ("taskIds", task_ids), ("taskNames", task_names), @@ -5040,7 +5041,7 @@ def get_tasks_by_folder_paths( "projectName": project_name, "folderPaths": list(folder_paths), } - if not _prepare_list_filters( + if not prepare_list_filters( filters, ("taskNames", task_names), ("taskTypes", task_types), @@ -5451,7 +5452,7 @@ def get_products( if filter_product_names: filters["productNames"] = list(filter_product_names) - if not _prepare_list_filters( + if not prepare_list_filters( filters, ("productIds", product_ids), ("productTypes", product_types), @@ -5872,7 +5873,7 @@ def get_versions( filters = { "projectName": project_name } - if not _prepare_list_filters( + if not prepare_list_filters( filters, ("taskIds", task_ids), ("versionIds", version_ids), diff --git a/ayon_api/utils.py b/ayon_api/utils.py index 1f863233c..dc5349ef7 100644 --- a/ayon_api/utils.py +++ b/ayon_api/utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import re import datetime @@ -7,6 +9,7 @@ import platform import traceback import collections +import itertools from urllib.parse import urlparse, urlencode import typing from typing import Optional, Dict, Set, Any, Iterable @@ -31,6 +34,8 @@ SLUGIFY_WHITELIST = string.ascii_letters + string.digits SLUGIFY_SEP_WHITELIST = " ,./\\;:!|*^#@~+-_=" +PatternType = type(re.compile("")) + RepresentationParents = collections.namedtuple( "RepresentationParents", ("version", "product", "folder", "project") @@ -112,6 +117,31 @@ def fill_own_attribs(entity: "AnyEntityDict") -> None: own_attrib[key] = copy.deepcopy(value) +def _convert_list_filter_value(value: Any) -> Optional[list[Any]]: + if value is None: + return None + + if isinstance(value, PatternType): + return [value.pattern] + + if isinstance(value, (int, float, str, bool)): + return [value] + return list(set(value)) + + +def prepare_list_filters( + output: dict[str, Any], *args: tuple[str, Any], **kwargs: Any +) -> bool: + for key, value in itertools.chain(args, kwargs.items()): + value = _convert_list_filter_value(value) + if value is None: + continue + if not value: + return False + output[key] = value + return True + + def get_default_timeout() -> float: """Default value for requests timeout. From 531b95e6c84301e0b7ea8eabeb6a626de90e41c0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:34:28 +0200 Subject: [PATCH 16/53] define 'ServerVersion' type --- ayon_api/_base.py | 4 +++- ayon_api/server_api.py | 5 +++-- ayon_api/typing.py | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ayon_api/_base.py b/ayon_api/_base.py index 889617a51..f8cf506e0 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -8,10 +8,12 @@ from .utils import TransferProgress, RequestType if typing.TYPE_CHECKING: - from .typing import AnyEntityDict + from .typing import AnyEntityDict, ServerVersion class _BaseServerAPI: + def get_server_version_tuple(self) -> "ServerVersion": + raise NotImplementedError() def get_base_url(self) -> str: raise NotImplementedError() diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 35c3a1282..a7f378aa2 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -115,6 +115,7 @@ if typing.TYPE_CHECKING: from typing import Union from .typing import ( + ServerVersion, ActivityType, ActivityReferenceType, LinkDirection, @@ -1048,7 +1049,7 @@ def get_server_version(self) -> str: self._server_version = self.get_info()["version"] return self._server_version - def get_server_version_tuple(self) -> Tuple[int, int, int, str, str]: + def get_server_version_tuple(self) -> "ServerVersion": """Get server version as tuple. Version should match semantic version (https://semver.org/). @@ -1073,7 +1074,7 @@ def get_server_version_tuple(self) -> Tuple[int, int, int, str, str]: return self._server_version_tuple server_version = property(get_server_version) - server_version_tuple: Tuple[int, int, int, str, str] = property( + server_version_tuple: "ServerVersion" = property( get_server_version_tuple ) diff --git a/ayon_api/typing.py b/ayon_api/typing.py index 32c582bea..541ed734d 100644 --- a/ayon_api/typing.py +++ b/ayon_api/typing.py @@ -11,6 +11,8 @@ ) +ServerVersion = tuple[int, int, int, str, str] + ActivityType = Literal[ "comment", "watch", From eaf901e582fca8664d1cb777ae86bdb780289391 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:34:39 +0200 Subject: [PATCH 17/53] implement 'get_rest_entity_by_id' --- ayon_api/_base.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ayon_api/_base.py b/ayon_api/_base.py index f8cf506e0..faf8c81bf 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -14,6 +14,7 @@ class _BaseServerAPI: def get_server_version_tuple(self) -> "ServerVersion": raise NotImplementedError() + def get_base_url(self) -> str: raise NotImplementedError() @@ -75,6 +76,14 @@ def download_file( ) -> TransferProgress: raise NotImplementedError() + def get_rest_entity_by_id( + self, + project_name: str, + entity_type: str, + entity_id: str, + ) -> Optional["AnyEntityDict"]: + raise NotImplementedError() + def _prepare_fields( self, entity_type: str, From ec5f5e6286f2814e97041b377ee6c9caa6e94d11 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 18:44:29 +0200 Subject: [PATCH 18/53] move folders to separate class --- automated_api.py | 2 + ayon_api/__init__.py | 48 +- ayon_api/_api.py | 3797 ++++++++++++++++++++-------------------- ayon_api/_folders.py | 637 +++++++ ayon_api/server_api.py | 640 +------ 5 files changed, 2564 insertions(+), 2560 deletions(-) create mode 100644 ayon_api/_folders.py diff --git a/automated_api.py b/automated_api.py index a5971552e..64ec6bd92 100644 --- a/automated_api.py +++ b/automated_api.py @@ -337,6 +337,7 @@ def prepare_api_functions(api_globals): ServerAPI, _ActionsAPI, _AddonsAPI, + _FoldersAPI, _LinksAPI, _ListsAPI, _ProjectsAPI, @@ -346,6 +347,7 @@ def prepare_api_functions(api_globals): _items = list(ServerAPI.__dict__.items()) _items.extend(_ActionsAPI.__dict__.items()) _items.extend(_AddonsAPI.__dict__.items()) + _items.extend(_FoldersAPI.__dict__.items()) _items.extend(_LinksAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) _items.extend(_ProjectsAPI.__dict__.items()) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index a7c41c734..11bf7df5b 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -138,22 +138,10 @@ save_secret, delete_secret, get_rest_entity_by_id, - get_rest_folder, - get_rest_folders, get_rest_task, get_rest_product, get_rest_version, get_rest_representation, - get_folders_hierarchy, - get_folders_rest, - get_folders, - get_folder_by_id, - get_folder_by_path, - get_folder_by_name, - get_folder_ids_with_products, - create_folder, - update_folder, - delete_folder, get_tasks, get_task_by_name, get_task_by_id, @@ -222,6 +210,18 @@ delete_addon_version, upload_addon_zip, download_addon_private_file, + get_rest_folder, + get_rest_folders, + get_folders_hierarchy, + get_folders_rest, + get_folders, + get_folder_by_id, + get_folder_by_path, + get_folder_by_name, + get_folder_ids_with_products, + create_folder, + update_folder, + delete_folder, get_full_link_type_name, get_link_types, get_link_type, @@ -402,22 +402,10 @@ "save_secret", "delete_secret", "get_rest_entity_by_id", - "get_rest_folder", - "get_rest_folders", "get_rest_task", "get_rest_product", "get_rest_version", "get_rest_representation", - "get_folders_hierarchy", - "get_folders_rest", - "get_folders", - "get_folder_by_id", - "get_folder_by_path", - "get_folder_by_name", - "get_folder_ids_with_products", - "create_folder", - "update_folder", - "delete_folder", "get_tasks", "get_task_by_name", "get_task_by_id", @@ -486,6 +474,18 @@ "delete_addon_version", "upload_addon_zip", "download_addon_private_file", + "get_rest_folder", + "get_rest_folders", + "get_folders_hierarchy", + "get_folders_rest", + "get_folders", + "get_folder_by_id", + "get_folder_by_path", + "get_folder_by_name", + "get_folder_ids_with_products", + "create_folder", + "update_folder", + "delete_folder", "get_full_link_type_name", "get_link_types", "get_link_type", diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 26105e319..db11667a1 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -39,6 +39,7 @@ if typing.TYPE_CHECKING: from typing import Union from .typing import ( + ServerVersion, ActivityType, ActivityReferenceType, EntityListEntityType, @@ -696,7 +697,7 @@ def get_server_version() -> str: return con.get_server_version() -def get_server_version_tuple() -> Tuple[int, int, int, str, str]: +def get_server_version_tuple() -> "ServerVersion": """Get server version as tuple. Version should match semantic version (https://semver.org/). @@ -3048,68 +3049,6 @@ def get_rest_entity_by_id( ) -def get_rest_folder( - project_name: str, - folder_id: str, -) -> Optional["FolderDict"]: - con = get_server_api_connection() - return con.get_rest_folder( - project_name=project_name, - folder_id=folder_id, - ) - - -def get_rest_folders( - project_name: str, - include_attrib: bool = False, -) -> List["FlatFolderDict"]: - """Get simplified flat list of all project folders. - - Get all project folders in single REST call. This can be faster than - using 'get_folders' method which is using GraphQl, but does not - allow any filtering, and set of fields is defined - by server backend. - - Example:: - - [ - { - "id": "112233445566", - "parentId": "112233445567", - "path": "/root/parent/child", - "parents": ["root", "parent"], - "name": "child", - "label": "Child", - "folderType": "Folder", - "hasTasks": False, - "hasChildren": False, - "taskNames": [ - "Compositing", - ], - "status": "In Progress", - "attrib": {}, - "ownAttrib": [], - "updatedAt": "2023-06-12T15:37:02.420260", - }, - ... - ] - - Args: - project_name (str): Project name. - include_attrib (Optional[bool]): Include attribute values - in output. Slower to query. - - Returns: - List[FlatFolderDict]: List of folder entities. - - """ - con = get_server_api_connection() - return con.get_rest_folders( - project_name=project_name, - include_attrib=include_attrib, - ) - - def get_rest_task( project_name: str, task_id: str, @@ -3154,165 +3093,41 @@ def get_rest_representation( ) -def get_folders_hierarchy( - project_name: str, - search_string: Optional[str] = None, - folder_types: Optional[Iterable[str]] = None, -) -> "ProjectHierarchyDict": - """Get project hierarchy. - - All folders in project in hierarchy data structure. - - Example output: - { - "hierarchy": [ - { - "id": "...", - "name": "...", - "label": "...", - "status": "...", - "folderType": "...", - "hasTasks": False, - "taskNames": [], - "parents": [], - "parentId": None, - "children": [...children folders...] - }, - ... - ] - } - - Args: - project_name (str): Project where to look for folders. - search_string (Optional[str]): Search string to filter folders. - folder_types (Optional[Iterable[str]]): Folder types to filter. - - Returns: - dict[str, Any]: Response data from server. - - """ - con = get_server_api_connection() - return con.get_folders_hierarchy( - project_name=project_name, - search_string=search_string, - folder_types=folder_types, - ) - - -def get_folders_rest( - project_name: str, - include_attrib: bool = False, -) -> List["FlatFolderDict"]: - """Get simplified flat list of all project folders. - - Get all project folders in single REST call. This can be faster than - using 'get_folders' method which is using GraphQl, but does not - allow any filtering, and set of fields is defined - by server backend. - - Example:: - - [ - { - "id": "112233445566", - "parentId": "112233445567", - "path": "/root/parent/child", - "parents": ["root", "parent"], - "name": "child", - "label": "Child", - "folderType": "Folder", - "hasTasks": False, - "hasChildren": False, - "taskNames": [ - "Compositing", - ], - "status": "In Progress", - "attrib": {}, - "ownAttrib": [], - "updatedAt": "2023-06-12T15:37:02.420260", - }, - ... - ] - - Deprecated: - Use 'get_rest_folders' instead. Function was renamed to match - other rest functions, like 'get_rest_folder', - 'get_rest_project' etc. . - Will be removed in '1.0.7' or '1.1.0'. - - Args: - project_name (str): Project name. - include_attrib (Optional[bool]): Include attribute values - in output. Slower to query. - - Returns: - List[FlatFolderDict]: List of folder entities. - - """ - con = get_server_api_connection() - return con.get_folders_rest( - project_name=project_name, - include_attrib=include_attrib, - ) - - -def get_folders( +def get_tasks( project_name: str, + task_ids: Optional[Iterable[str]] = None, + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, folder_ids: Optional[Iterable[str]] = None, - folder_paths: Optional[Iterable[str]] = None, - folder_names: Optional[Iterable[str]] = None, - folder_types: Optional[Iterable[str]] = None, - parent_ids: Optional[Iterable[str]] = None, - folder_path_regex: Optional[str] = None, - has_products: Optional[bool] = None, - has_tasks: Optional[bool] = None, - has_children: Optional[bool] = None, - statuses: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, tags: Optional[Iterable[str]] = None, active: "Union[bool, None]" = True, - has_links: Optional[bool] = None, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Generator["FolderDict", None, None]: - """Query folders from server. - - Todos: - Folder name won't be unique identifier, so we should add - folder path filtering. - - Notes: - Filter 'active' don't have direct filter in GraphQl. +) -> Generator["TaskDict", None, None]: + """Query task entities from server. Args: project_name (str): Name of project. - folder_ids (Optional[Iterable[str]]): Folder ids to filter. - folder_paths (Optional[Iterable[str]]): Folder paths used - for filtering. - folder_names (Optional[Iterable[str]]): Folder names used - for filtering. - folder_types (Optional[Iterable[str]]): Folder types used - for filtering. - parent_ids (Optional[Iterable[str]]): Ids of folder parents. - Use 'None' if folder is direct child of project. - folder_path_regex (Optional[str]): Folder path regex used - for filtering. - has_products (Optional[bool]): Filter folders with/without - products. Ignored when None, default behavior. - has_tasks (Optional[bool]): Filter folders with/without - tasks. Ignored when None, default behavior. - has_children (Optional[bool]): Filter folders with/without - children. Ignored when None, default behavior. - statuses (Optional[Iterable[str]]): Folder statuses used - for filtering. - assignees_all (Optional[Iterable[str]]): Filter by assigness - on children tasks. Task must have all of passed assignees. - tags (Optional[Iterable[str]]): Folder tags used - for filtering. - active (Optional[bool]): Filter active/inactive folders. + task_ids (Iterable[str]): Task ids to filter. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + folder_ids (Iterable[str]): Ids of task parents. Use 'None' + if folder is direct child of project. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. Both are returned if is set to None. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. fields (Optional[Iterable[str]]): Fields to be queried for folder. All possible folder fields are returned if 'None' is passed. @@ -3320,227 +3135,308 @@ def get_folders( not explicitly set on entity will have 'None' value. Returns: - Generator[FolderDict, None, None]: Queried folder entities. + Generator[TaskDict, None, None]: Queried task entities. """ con = get_server_api_connection() - return con.get_folders( + return con.get_tasks( project_name=project_name, + task_ids=task_ids, + task_names=task_names, + task_types=task_types, folder_ids=folder_ids, - folder_paths=folder_paths, - folder_names=folder_names, - folder_types=folder_types, - parent_ids=parent_ids, - folder_path_regex=folder_path_regex, - has_products=has_products, - has_tasks=has_tasks, - has_children=has_children, - statuses=statuses, + assignees=assignees, assignees_all=assignees_all, + statuses=statuses, tags=tags, active=active, - has_links=has_links, fields=fields, own_attributes=own_attributes, ) -def get_folder_by_id( +def get_task_by_name( project_name: str, folder_id: str, + task_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["FolderDict"]: - """Query folder entity by id. +) -> Optional["TaskDict"]: + """Query task entity by name and folder id. Args: project_name (str): Name of project where to look for queried entities. folder_id (str): Folder id. + task_name (str): Task name fields (Optional[Iterable[str]]): Fields that should be returned. All fields are returned if 'None' is passed. own_attributes (Optional[bool]): Attribute values that are not explicitly set on entity will have 'None' value. Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.get_folder_by_id( + return con.get_task_by_name( project_name=project_name, folder_id=folder_id, + task_name=task_name, fields=fields, own_attributes=own_attributes, ) -def get_folder_by_path( +def get_task_by_id( project_name: str, - folder_path: str, + task_id: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["FolderDict"]: - """Query folder entity by path. - - Folder path is a path to folder with all parent names joined by slash. +) -> Optional["TaskDict"]: + """Query task entity by id. Args: project_name (str): Name of project where to look for queried entities. - folder_path (str): Folder path. + task_id (str): Task id. fields (Optional[Iterable[str]]): Fields that should be returned. All fields are returned if 'None' is passed. own_attributes (Optional[bool]): Attribute values that are not explicitly set on entity will have 'None' value. Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.get_folder_by_path( + return con.get_task_by_id( project_name=project_name, - folder_path=folder_path, + task_id=task_id, fields=fields, own_attributes=own_attributes, ) -def get_folder_by_name( +def get_tasks_by_folder_paths( project_name: str, - folder_name: str, + folder_paths: Iterable[str], + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: "Union[bool, None]" = True, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["FolderDict"]: - """Query folder entity by path. - - Warnings: - Folder name is not a unique identifier of a folder. Function is - kept for OpenPype 3 compatibility. +) -> Dict[str, List["TaskDict"]]: + """Query task entities from server by folder paths. Args: - project_name (str): Name of project where to look for queried - entities. - folder_name (str): Folder name. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. + project_name (str): Name of project. + folder_paths (list[str]): Folder paths. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. own_attributes (Optional[bool]): Attribute values that are not explicitly set on entity will have 'None' value. Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. + Dict[str, List[TaskDict]]: Task entities by + folder path. """ con = get_server_api_connection() - return con.get_folder_by_name( + return con.get_tasks_by_folder_paths( project_name=project_name, - folder_name=folder_name, + folder_paths=folder_paths, + task_names=task_names, + task_types=task_types, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, + tags=tags, + active=active, fields=fields, own_attributes=own_attributes, ) -def get_folder_ids_with_products( +def get_tasks_by_folder_path( project_name: str, - folder_ids: Optional[Iterable[str]] = None, -) -> Set[str]: - """Find folders which have at least one product. - - Folders that have at least one product should be immutable, so they - should not change path -> change of name or name of any parent - is not possible. + folder_path: str, + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: "Union[bool, None]" = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> List["TaskDict"]: + """Query task entities from server by folder path. Args: project_name (str): Name of project. - folder_ids (Optional[Iterable[str]]): Limit folder ids filtering - to a set of folders. If set to None all folders on project are - checked. + folder_path (str): Folder path. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + """ + con = get_server_api_connection() + return con.get_tasks_by_folder_path( + project_name=project_name, + folder_path=folder_path, + task_names=task_names, + task_types=task_types, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, + tags=tags, + active=active, + fields=fields, + own_attributes=own_attributes, + ) + + +def get_task_by_folder_path( + project_name: str, + folder_path: str, + task_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["TaskDict"]: + """Query task entity by folder path and task name. + + Args: + project_name (str): Project name. + folder_path (str): Folder path. + task_name (str): Task name. + fields (Optional[Iterable[str]]): Task fields that should + be returned. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - set[str]: Folder ids that have at least one product. + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.get_folder_ids_with_products( + return con.get_task_by_folder_path( project_name=project_name, - folder_ids=folder_ids, + folder_path=folder_path, + task_name=task_name, + fields=fields, + own_attributes=own_attributes, ) -def create_folder( +def create_task( project_name: str, name: str, - folder_type: Optional[str] = None, - parent_id: Optional[str] = None, + task_type: str, + folder_id: str, label: Optional[str] = None, + assignees: Optional[Iterable[str]] = None, attrib: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, + tags: Optional[List[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = None, - folder_id: Optional[str] = None, + task_id: Optional[str] = None, ) -> str: - """Create new folder. + """Create new task. Args: project_name (str): Project name. name (str): Folder name. - folder_type (Optional[str]): Folder type. - parent_id (Optional[str]): Parent folder id. Parent is project - if is ``None``. + task_type (str): Task type. + folder_id (str): Parent folder id. label (Optional[str]): Label of folder. - attrib (Optional[dict[str, Any]]): Folder attributes. - data (Optional[dict[str, Any]]): Folder data. - tags (Optional[Iterable[str]]): Folder tags. - status (Optional[str]): Folder status. - active (Optional[bool]): Folder active state. - thumbnail_id (Optional[str]): Folder thumbnail id. - folder_id (Optional[str]): Folder id. If not passed new id is + assignees (Optional[Iterable[str]]): Task assignees. + attrib (Optional[dict[str, Any]]): Task attributes. + data (Optional[dict[str, Any]]): Task data. + tags (Optional[Iterable[str]]): Task tags. + status (Optional[str]): Task status. + active (Optional[bool]): Task active state. + thumbnail_id (Optional[str]): Task thumbnail id. + task_id (Optional[str]): Task id. If not passed new id is generated. Returns: - str: Entity id. + str: Task id. """ con = get_server_api_connection() - return con.create_folder( + return con.create_task( project_name=project_name, name=name, - folder_type=folder_type, - parent_id=parent_id, + task_type=task_type, + folder_id=folder_id, label=label, + assignees=assignees, attrib=attrib, data=data, tags=tags, status=status, active=active, thumbnail_id=thumbnail_id, - folder_id=folder_id, + task_id=task_id, ) -def update_folder( +def update_task( project_name: str, - folder_id: str, + task_id: str, name: Optional[str] = None, - folder_type: Optional[str] = None, - parent_id: Optional[str] = NOT_SET, + task_type: Optional[str] = None, + folder_id: Optional[str] = None, label: Optional[str] = NOT_SET, + assignees: Optional[List[str]] = None, attrib: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, + tags: Optional[List[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = NOT_SET, ): - """Update folder entity on server. + """Update task entity on server. - Do not pass ``parent_id``, ``label`` amd ``thumbnail_id`` if you don't + Do not pass ``label`` amd ``thumbnail_id`` if you don't want to change their values. Value ``None`` would unset their value. @@ -3551,11 +3447,12 @@ def update_folder( Args: project_name (str): Project name. - folder_id (str): Folder id. + task_id (str): Task id. name (Optional[str]): New name. - folder_type (Optional[str]): New folder type. - parent_id (Optional[Union[str, None]]): New parent folder id. + task_type (Optional[str]): New task type. + folder_id (Optional[str]): New folder id. label (Optional[Union[str, None]]): New label. + assignees (Optional[str]): New assignees. attrib (Optional[dict[str, Any]]): New attributes. data (Optional[dict[str, Any]]): New data. tags (Optional[Iterable[str]]): New tags. @@ -3565,13 +3462,14 @@ def update_folder( """ con = get_server_api_connection() - return con.update_folder( + return con.update_task( project_name=project_name, - folder_id=folder_id, + task_id=task_id, name=name, - folder_type=folder_type, - parent_id=parent_id, + task_type=task_type, + folder_id=folder_id, label=label, + assignees=assignees, attrib=attrib, data=data, tags=tags, @@ -3581,82 +3479,85 @@ def update_folder( ) -def delete_folder( +def delete_task( project_name: str, - folder_id: str, - force: bool = False, + task_id: str, ): - """Delete folder. + """Delete task. Args: project_name (str): Project name. - folder_id (str): Folder id to delete. - force (Optional[bool]): Folder delete folder with all children - folder, products, versions and representations. + task_id (str): Task id to delete. """ con = get_server_api_connection() - return con.delete_folder( + return con.delete_task( project_name=project_name, - folder_id=folder_id, - force=force, + task_id=task_id, ) -def get_tasks( +def get_products( project_name: str, - task_ids: Optional[Iterable[str]] = None, - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, + product_ids: Optional[Iterable[str]] = None, + product_names: Optional[Iterable[str]] = None, folder_ids: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, + product_types: Optional[Iterable[str]] = None, + product_name_regex: Optional[str] = None, + product_path_regex: Optional[str] = None, + names_by_folder_ids: Optional[Dict[str, Iterable[str]]] = None, statuses: Optional[Iterable[str]] = None, tags: Optional[Iterable[str]] = None, active: "Union[bool, None]" = True, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Generator["TaskDict", None, None]: - """Query task entities from server. + own_attributes=_PLACEHOLDER, +) -> Generator["ProductDict", None, None]: + """Query products from server. + + Todos: + Separate 'name_by_folder_ids' filtering to separated method. It + cannot be combined with some other filters. Args: project_name (str): Name of project. - task_ids (Iterable[str]): Task ids to filter. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - folder_ids (Iterable[str]): Ids of task parents. Use 'None' - if folder is direct child of project. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for + product_ids (Optional[Iterable[str]]): Task ids to filter. + product_names (Optional[Iterable[str]]): Task names used for filtering. - tags (Optional[Iterable[str]]): Task tags used for + folder_ids (Optional[Iterable[str]]): Ids of task parents. + Use 'None' if folder is direct child of project. + product_types (Optional[Iterable[str]]): Product types used for filtering. - active (Optional[bool]): Filter active/inactive tasks. + product_name_regex (Optional[str]): Filter products by name regex. + product_path_regex (Optional[str]): Filter products by path regex. + Path starts with folder path and ends with product name. + names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product + name filtering by folder id. + statuses (Optional[Iterable[str]]): Product statuses used + for filtering. + tags (Optional[Iterable[str]]): Product tags used + for filtering. + active (Optional[bool]): Filter active/inactive products. Both are returned if is set to None. fields (Optional[Iterable[str]]): Fields to be queried for folder. All possible folder fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. Returns: - Generator[TaskDict, None, None]: Queried task entities. + Generator[ProductDict, None, None]: Queried product entities. """ con = get_server_api_connection() - return con.get_tasks( + return con.get_products( project_name=project_name, - task_ids=task_ids, - task_names=task_names, - task_types=task_types, + product_ids=product_ids, + product_names=product_names, folder_ids=folder_ids, - assignees=assignees, - assignees_all=assignees_all, + product_types=product_types, + product_name_regex=product_name_regex, + product_path_regex=product_path_regex, + names_by_folder_ids=names_by_folder_ids, statuses=statuses, tags=tags, active=active, @@ -3665,290 +3566,202 @@ def get_tasks( ) -def get_task_by_name( +def get_product_by_id( project_name: str, - folder_id: str, - task_name: str, + product_id: str, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by name and folder id. + own_attributes=_PLACEHOLDER, +) -> Optional["ProductDict"]: + """Query product entity by id. Args: project_name (str): Name of project where to look for queried entities. - folder_id (str): Folder id. - task_name (str): Task name + product_id (str): Product id. fields (Optional[Iterable[str]]): Fields that should be returned. All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + Optional[ProductDict]: Product entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_task_by_name( + return con.get_product_by_id( project_name=project_name, - folder_id=folder_id, - task_name=task_name, + product_id=product_id, fields=fields, own_attributes=own_attributes, ) -def get_task_by_id( +def get_product_by_name( project_name: str, - task_id: str, + product_name: str, + folder_id: str, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by id. + own_attributes=_PLACEHOLDER, +) -> Optional["ProductDict"]: + """Query product entity by name and folder id. Args: project_name (str): Name of project where to look for queried entities. - task_id (str): Task id. + product_name (str): Product name. + folder_id (str): Folder id (Folder is a parent of products). fields (Optional[Iterable[str]]): Fields that should be returned. All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + Optional[ProductDict]: Product entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_task_by_id( + return con.get_product_by_name( project_name=project_name, - task_id=task_id, + product_name=product_name, + folder_id=folder_id, fields=fields, own_attributes=own_attributes, ) -def get_tasks_by_folder_paths( - project_name: str, - folder_paths: Iterable[str], - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, +def get_product_types( fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Dict[str, List["TaskDict"]]: - """Query task entities from server by folder paths. +) -> List["ProductTypeDict"]: + """Types of products. + + This is server wide information. Product types have 'name', 'icon' and + 'color'. Args: - project_name (str): Name of project. - folder_paths (list[str]): Folder paths. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + fields (Optional[Iterable[str]]): Product types fields to query. Returns: - Dict[str, List[TaskDict]]: Task entities by - folder path. + list[ProductTypeDict]: Product types information. """ con = get_server_api_connection() - return con.get_tasks_by_folder_paths( - project_name=project_name, - folder_paths=folder_paths, - task_names=task_names, - task_types=task_types, - assignees=assignees, - assignees_all=assignees_all, - statuses=statuses, - tags=tags, - active=active, + return con.get_product_types( fields=fields, - own_attributes=own_attributes, ) -def get_tasks_by_folder_path( +def get_project_product_types( project_name: str, - folder_path: str, - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> List["TaskDict"]: - """Query task entities from server by folder path. +) -> List["ProductTypeDict"]: + """DEPRECATED Types of products available in a project. + + Filter only product types available in a project. Args: - project_name (str): Name of project. - folder_path (str): Folder path. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Name of the project where to look for + product types. + fields (Optional[Iterable[str]]): Product types fields to query. + + Returns: + List[ProductTypeDict]: Product types information. """ con = get_server_api_connection() - return con.get_tasks_by_folder_path( + return con.get_project_product_types( project_name=project_name, - folder_path=folder_path, - task_names=task_names, - task_types=task_types, - assignees=assignees, - assignees_all=assignees_all, - statuses=statuses, - tags=tags, - active=active, fields=fields, - own_attributes=own_attributes, ) -def get_task_by_folder_path( - project_name: str, - folder_path: str, - task_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by folder path and task name. +def get_product_type_names( + project_name: Optional[str] = None, + product_ids: Optional[Iterable[str]] = None, +) -> Set[str]: + """DEPRECATED Product type names. - Args: - project_name (str): Project name. - folder_path (str): Folder path. - task_name (str): Task name. - fields (Optional[Iterable[str]]): Task fields that should - be returned. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + Warnings: + This function will be probably removed. Matters if 'products_id' + filter has real use-case. + + Args: + project_name (Optional[str]): Name of project where to look for + queried entities. + product_ids (Optional[Iterable[str]]): Product ids filter. Can be + used only with 'project_name'. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + set[str]: Product type names. """ con = get_server_api_connection() - return con.get_task_by_folder_path( + return con.get_product_type_names( project_name=project_name, - folder_path=folder_path, - task_name=task_name, - fields=fields, - own_attributes=own_attributes, + product_ids=product_ids, ) -def create_task( +def create_product( project_name: str, name: str, - task_type: str, + product_type: str, folder_id: str, - label: Optional[str] = None, - assignees: Optional[Iterable[str]] = None, attrib: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, + tags: Optional[Iterable[str]] = None, status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - task_id: Optional[str] = None, + active: "Union[bool, None]" = None, + product_id: Optional[str] = None, ) -> str: - """Create new task. + """Create new product. Args: project_name (str): Project name. - name (str): Folder name. - task_type (str): Task type. + name (str): Product name. + product_type (str): Product type. folder_id (str): Parent folder id. - label (Optional[str]): Label of folder. - assignees (Optional[Iterable[str]]): Task assignees. - attrib (Optional[dict[str, Any]]): Task attributes. - data (Optional[dict[str, Any]]): Task data. - tags (Optional[Iterable[str]]): Task tags. - status (Optional[str]): Task status. - active (Optional[bool]): Task active state. - thumbnail_id (Optional[str]): Task thumbnail id. - task_id (Optional[str]): Task id. If not passed new id is + attrib (Optional[dict[str, Any]]): Product attributes. + data (Optional[dict[str, Any]]): Product data. + tags (Optional[Iterable[str]]): Product tags. + status (Optional[str]): Product status. + active (Optional[bool]): Product active state. + product_id (Optional[str]): Product id. If not passed new id is generated. Returns: - str: Task id. + str: Product id. """ con = get_server_api_connection() - return con.create_task( + return con.create_product( project_name=project_name, name=name, - task_type=task_type, + product_type=product_type, folder_id=folder_id, - label=label, - assignees=assignees, attrib=attrib, data=data, tags=tags, status=status, active=active, - thumbnail_id=thumbnail_id, - task_id=task_id, + product_id=product_id, ) -def update_task( +def update_product( project_name: str, - task_id: str, + product_id: str, name: Optional[str] = None, - task_type: Optional[str] = None, folder_id: Optional[str] = None, - label: Optional[str] = NOT_SET, - assignees: Optional[List[str]] = None, + product_type: Optional[str] = None, attrib: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, + tags: Optional[Iterable[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, ): - """Update task entity on server. - - Do not pass ``label`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. + """Update product entity on server. Update of ``data`` will override existing value on folder entity. @@ -3957,117 +3770,108 @@ def update_task( Args: project_name (str): Project name. - task_id (str): Task id. - name (Optional[str]): New name. - task_type (Optional[str]): New task type. - folder_id (Optional[str]): New folder id. - label (Optional[Union[str, None]]): New label. - assignees (Optional[str]): New assignees. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - thumbnail_id (Optional[Union[str, None]]): New thumbnail id. + product_id (str): Product id. + name (Optional[str]): New product name. + folder_id (Optional[str]): New product id. + product_type (Optional[str]): New product type. + attrib (Optional[dict[str, Any]]): New product attributes. + data (Optional[dict[str, Any]]): New product data. + tags (Optional[Iterable[str]]): New product tags. + status (Optional[str]): New product status. + active (Optional[bool]): New product active state. """ con = get_server_api_connection() - return con.update_task( + return con.update_product( project_name=project_name, - task_id=task_id, + product_id=product_id, name=name, - task_type=task_type, folder_id=folder_id, - label=label, - assignees=assignees, + product_type=product_type, attrib=attrib, data=data, tags=tags, status=status, active=active, - thumbnail_id=thumbnail_id, ) -def delete_task( +def delete_product( project_name: str, - task_id: str, + product_id: str, ): - """Delete task. + """Delete product. Args: project_name (str): Project name. - task_id (str): Task id to delete. + product_id (str): Product id to delete. """ con = get_server_api_connection() - return con.delete_task( + return con.delete_product( project_name=project_name, - task_id=task_id, + product_id=product_id, ) -def get_products( +def get_versions( project_name: str, + version_ids: Optional[Iterable[str]] = None, product_ids: Optional[Iterable[str]] = None, - product_names: Optional[Iterable[str]] = None, - folder_ids: Optional[Iterable[str]] = None, - product_types: Optional[Iterable[str]] = None, - product_name_regex: Optional[str] = None, - product_path_regex: Optional[str] = None, - names_by_folder_ids: Optional[Dict[str, Iterable[str]]] = None, + task_ids: Optional[Iterable[str]] = None, + versions: Optional[Iterable[str]] = None, + hero: bool = True, + standard: bool = True, + latest: Optional[bool] = None, statuses: Optional[Iterable[str]] = None, tags: Optional[Iterable[str]] = None, active: "Union[bool, None]" = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["ProductDict", None, None]: - """Query products from server. - - Todos: - Separate 'name_by_folder_ids' filtering to separated method. It - cannot be combined with some other filters. +) -> Generator["VersionDict", None, None]: + """Get version entities based on passed filters from server. Args: - project_name (str): Name of project. - product_ids (Optional[Iterable[str]]): Task ids to filter. - product_names (Optional[Iterable[str]]): Task names used for - filtering. - folder_ids (Optional[Iterable[str]]): Ids of task parents. - Use 'None' if folder is direct child of project. - product_types (Optional[Iterable[str]]): Product types used for - filtering. - product_name_regex (Optional[str]): Filter products by name regex. - product_path_regex (Optional[str]): Filter products by path regex. - Path starts with folder path and ends with product name. - names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product - name filtering by folder id. - statuses (Optional[Iterable[str]]): Product statuses used + project_name (str): Name of project where to look for versions. + version_ids (Optional[Iterable[str]]): Version ids used for + version filtering. + product_ids (Optional[Iterable[str]]): Product ids used for + version filtering. + task_ids (Optional[Iterable[str]]): Task ids used for + version filtering. + versions (Optional[Iterable[int]]): Versions we're interested in. + hero (Optional[bool]): Skip hero versions when set to False. + standard (Optional[bool]): Skip standard (non-hero) when + set to False. + latest (Optional[bool]): Return only latest version of standard + versions. This can be combined only with 'standard' attribute + set to True. + statuses (Optional[Iterable[str]]): Representation statuses used for filtering. - tags (Optional[Iterable[str]]): Product tags used + tags (Optional[Iterable[str]]): Representation tags used for filtering. - active (Optional[bool]): Filter active/inactive products. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): Fields to be queried + for version. All possible folder fields are returned if 'None' is passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. + versions. Returns: - Generator[ProductDict, None, None]: Queried product entities. + Generator[VersionDict, None, None]: Queried version entities. """ con = get_server_api_connection() - return con.get_products( + return con.get_versions( project_name=project_name, + version_ids=version_ids, product_ids=product_ids, - product_names=product_names, - folder_ids=folder_ids, - product_types=product_types, - product_name_regex=product_name_regex, - product_path_regex=product_path_regex, - names_by_folder_ids=names_by_folder_ids, + task_ids=task_ids, + versions=versions, + hero=hero, + standard=standard, + latest=latest, statuses=statuses, tags=tags, active=active, @@ -4076,683 +3880,833 @@ def get_products( ) -def get_product_by_id( +def get_version_by_id( project_name: str, - product_id: str, + version_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["ProductDict"]: - """Query product entity by id. +) -> Optional["VersionDict"]: + """Query version entity by id. Args: project_name (str): Name of project where to look for queried entities. - product_id (str): Product id. + version_id (str): Version id. fields (Optional[Iterable[str]]): Fields that should be returned. All fields are returned if 'None' is passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. + versions. Returns: - Optional[ProductDict]: Product entity data or None + Optional[VersionDict]: Version entity data or None if was not found. """ con = get_server_api_connection() - return con.get_product_by_id( + return con.get_version_by_id( project_name=project_name, - product_id=product_id, + version_id=version_id, fields=fields, own_attributes=own_attributes, ) -def get_product_by_name( +def get_version_by_name( project_name: str, - product_name: str, - folder_id: str, + version: int, + product_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["ProductDict"]: - """Query product entity by name and folder id. +) -> Optional["VersionDict"]: + """Query version entity by version and product id. Args: project_name (str): Name of project where to look for queried entities. - product_name (str): Product name. - folder_id (str): Folder id (Folder is a parent of products). + version (int): Version of version entity. + product_id (str): Product id. Product is a parent of version. fields (Optional[Iterable[str]]): Fields that should be returned. All fields are returned if 'None' is passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. + versions. Returns: - Optional[ProductDict]: Product entity data or None + Optional[VersionDict]: Version entity data or None if was not found. """ con = get_server_api_connection() - return con.get_product_by_name( + return con.get_version_by_name( project_name=project_name, - product_name=product_name, - folder_id=folder_id, + version=version, + product_id=product_id, fields=fields, own_attributes=own_attributes, ) -def get_product_types( +def get_hero_version_by_id( + project_name: str, + version_id: str, fields: Optional[Iterable[str]] = None, -) -> List["ProductTypeDict"]: - """Types of products. - - This is server wide information. Product types have 'name', 'icon' and - 'color'. + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query hero version entity by id. Args: - fields (Optional[Iterable[str]]): Product types fields to query. + project_name (str): Name of project where to look for queried + entities. + version_id (int): Hero version id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - list[ProductTypeDict]: Product types information. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_product_types( + return con.get_hero_version_by_id( + project_name=project_name, + version_id=version_id, fields=fields, + own_attributes=own_attributes, ) -def get_project_product_types( +def get_hero_version_by_product_id( project_name: str, + product_id: str, fields: Optional[Iterable[str]] = None, -) -> List["ProductTypeDict"]: - """DEPRECATED Types of products available in a project. + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query hero version entity by product id. - Filter only product types available in a project. + Only one hero version is available on a product. Args: - project_name (str): Name of the project where to look for - product types. - fields (Optional[Iterable[str]]): Product types fields to query. + project_name (str): Name of project where to look for queried + entities. + product_id (int): Product id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - List[ProductTypeDict]: Product types information. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_project_product_types( + return con.get_hero_version_by_product_id( project_name=project_name, + product_id=product_id, fields=fields, + own_attributes=own_attributes, ) -def get_product_type_names( - project_name: Optional[str] = None, +def get_hero_versions( + project_name: str, product_ids: Optional[Iterable[str]] = None, -) -> Set[str]: - """DEPRECATED Product type names. + version_ids: Optional[Iterable[str]] = None, + active: "Union[bool, None]" = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Generator["VersionDict", None, None]: + """Query hero versions by multiple filters. - Warnings: - This function will be probably removed. Matters if 'products_id' - filter has real use-case. + Only one hero version is available on a product. Args: - project_name (Optional[str]): Name of project where to look for - queried entities. - product_ids (Optional[Iterable[str]]): Product ids filter. Can be - used only with 'project_name'. + project_name (str): Name of project where to look for queried + entities. + product_ids (Optional[Iterable[str]]): Product ids. + version_ids (Optional[Iterable[str]]): Version ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - set[str]: Product type names. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_product_type_names( + return con.get_hero_versions( project_name=project_name, product_ids=product_ids, + version_ids=version_ids, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def create_product( +def get_last_versions( project_name: str, - name: str, - product_type: str, - folder_id: str, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: "Union[bool, None]" = None, - product_id: Optional[str] = None, -) -> str: - """Create new product. + product_ids: Iterable[str], + active: "Union[bool, None]" = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Dict[str, Optional["VersionDict"]]: + """Query last version entities by product ids. Args: - project_name (str): Project name. - name (str): Product name. - product_type (str): Product type. - folder_id (str): Parent folder id. - attrib (Optional[dict[str, Any]]): Product attributes. - data (Optional[dict[str, Any]]): Product data. - tags (Optional[Iterable[str]]): Product tags. - status (Optional[str]): Product status. - active (Optional[bool]): Product active state. - product_id (Optional[str]): Product id. If not passed new id is - generated. + project_name (str): Project where to look for representation. + product_ids (Iterable[str]): Product ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - str: Product id. + dict[str, Optional[VersionDict]]: Last versions by product id. """ con = get_server_api_connection() - return con.create_product( + return con.get_last_versions( project_name=project_name, - name=name, - product_type=product_type, - folder_id=folder_id, - attrib=attrib, - data=data, - tags=tags, - status=status, + product_ids=product_ids, active=active, - product_id=product_id, + fields=fields, + own_attributes=own_attributes, ) -def update_product( +def get_last_version_by_product_id( project_name: str, product_id: str, - name: Optional[str] = None, - folder_id: Optional[str] = None, - product_type: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, -): - """Update product entity on server. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. + active: "Union[bool, None]" = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query last version entity by product id. Args: - project_name (str): Project name. + project_name (str): Project where to look for representation. product_id (str): Product id. - name (Optional[str]): New product name. - folder_id (Optional[str]): New product id. - product_type (Optional[str]): New product type. - attrib (Optional[dict[str, Any]]): New product attributes. - data (Optional[dict[str, Any]]): New product data. - tags (Optional[Iterable[str]]): New product tags. - status (Optional[str]): New product status. - active (Optional[bool]): New product active state. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Queried version entity or None. """ con = get_server_api_connection() - return con.update_product( + return con.get_last_version_by_product_id( project_name=project_name, product_id=product_id, - name=name, - folder_id=folder_id, - product_type=product_type, - attrib=attrib, - data=data, - tags=tags, - status=status, active=active, + fields=fields, + own_attributes=own_attributes, ) -def delete_product( +def get_last_version_by_product_name( project_name: str, - product_id: str, -): - """Delete product. + product_name: str, + folder_id: str, + active: "Union[bool, None]" = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query last version entity by product name and folder id. Args: - project_name (str): Project name. - product_id (str): Product id to delete. + project_name (str): Project where to look for representation. + product_name (str): Product name. + folder_id (str): Folder id. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. + + Returns: + Optional[VersionDict]: Queried version entity or None. """ con = get_server_api_connection() - return con.delete_product( + return con.get_last_version_by_product_name( project_name=project_name, - product_id=product_id, + product_name=product_name, + folder_id=folder_id, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def get_versions( +def version_is_latest( project_name: str, - version_ids: Optional[Iterable[str]] = None, - product_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] = None, - versions: Optional[Iterable[str]] = None, - hero: bool = True, - standard: bool = True, - latest: Optional[bool] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["VersionDict", None, None]: - """Get version entities based on passed filters from server. + version_id: str, +) -> bool: + """Is version latest from a product. Args: - project_name (str): Name of project where to look for versions. - version_ids (Optional[Iterable[str]]): Version ids used for - version filtering. - product_ids (Optional[Iterable[str]]): Product ids used for - version filtering. - task_ids (Optional[Iterable[str]]): Task ids used for - version filtering. - versions (Optional[Iterable[int]]): Versions we're interested in. - hero (Optional[bool]): Skip hero versions when set to False. - standard (Optional[bool]): Skip standard (non-hero) when - set to False. - latest (Optional[bool]): Return only latest version of standard - versions. This can be combined only with 'standard' attribute - set to True. - statuses (Optional[Iterable[str]]): Representation statuses used - for filtering. - tags (Optional[Iterable[str]]): Representation tags used - for filtering. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): Fields to be queried - for version. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where to look for representation. + version_id (str): Version id. Returns: - Generator[VersionDict, None, None]: Queried version entities. + bool: Version is latest or not. """ con = get_server_api_connection() - return con.get_versions( + return con.version_is_latest( project_name=project_name, - version_ids=version_ids, - product_ids=product_ids, - task_ids=task_ids, - versions=versions, - hero=hero, - standard=standard, - latest=latest, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, + version_id=version_id, ) -def get_version_by_id( +def create_version( project_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query version entity by id. + version: int, + product_id: str, + task_id: Optional[str] = None, + author: Optional[str] = None, + attrib: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + version_id: Optional[str] = None, +) -> str: + """Create new version. Args: - project_name (str): Name of project where to look for queried - entities. - version_id (str): Version id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project name. + version (int): Version. + product_id (str): Parent product id. + task_id (Optional[str]): Parent task id. + author (Optional[str]): Version author. + attrib (Optional[dict[str, Any]]): Version attributes. + data (Optional[dict[str, Any]]): Version data. + tags (Optional[Iterable[str]]): Version tags. + status (Optional[str]): Version status. + active (Optional[bool]): Version active state. + thumbnail_id (Optional[str]): Version thumbnail id. + version_id (Optional[str]): Version id. If not passed new id is + generated. Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + str: Version id. """ con = get_server_api_connection() - return con.get_version_by_id( + return con.create_version( project_name=project_name, + version=version, + product_id=product_id, + task_id=task_id, + author=author, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, version_id=version_id, - fields=fields, - own_attributes=own_attributes, ) -def get_version_by_name( +def update_version( project_name: str, - version: int, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query version entity by version and product id. + version_id: str, + version: Optional[int] = None, + product_id: Optional[str] = None, + task_id: Optional[str] = NOT_SET, + author: Optional[str] = None, + attrib: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = NOT_SET, +): + """Update version entity on server. - Args: - project_name (str): Name of project where to look for queried - entities. - version (int): Version of version entity. - product_id (str): Product id. Product is a parent of version. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + Do not pass ``task_id`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. + + Args: + project_name (str): Project name. + version_id (str): Version id. + version (Optional[int]): New version. + product_id (Optional[str]): New product id. + task_id (Optional[Union[str, None]]): New task id. + author (Optional[str]): New author username. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[Union[str, None]]): New thumbnail id. """ con = get_server_api_connection() - return con.get_version_by_name( + return con.update_version( project_name=project_name, + version_id=version_id, version=version, product_id=product_id, - fields=fields, - own_attributes=own_attributes, + task_id=task_id, + author=author, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, ) -def get_hero_version_by_id( +def delete_version( project_name: str, version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query hero version entity by id. +): + """Delete version. Args: - project_name (str): Name of project where to look for queried - entities. - version_id (int): Hero version id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + project_name (str): Project name. + version_id (str): Version id to delete. """ con = get_server_api_connection() - return con.get_hero_version_by_id( + return con.delete_version( project_name=project_name, version_id=version_id, - fields=fields, - own_attributes=own_attributes, ) -def get_hero_version_by_product_id( +def get_representations( project_name: str, - product_id: str, + representation_ids: Optional[Iterable[str]] = None, + representation_names: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, + names_by_version_ids: Optional[Dict[str, Iterable[str]]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: "Union[bool, None]" = True, + has_links: Optional[str] = None, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query hero version entity by product id. +) -> Generator["RepresentationDict", None, None]: + """Get representation entities based on passed filters from server. - Only one hero version is available on a product. + .. todo:: + + Add separated function for 'names_by_version_ids' filtering. + Because can't be combined with others. Args: - project_name (str): Name of project where to look for queried - entities. - product_id (int): Product id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. + project_name (str): Name of project where to look for versions. + representation_ids (Optional[Iterable[str]]): Representation ids + used for representation filtering. + representation_names (Optional[Iterable[str]]): Representation + names used for representation filtering. + version_ids (Optional[Iterable[str]]): Version ids used for + representation filtering. Versions are parents of + representations. + names_by_version_ids (Optional[Dict[str, Iterable[str]]]): Find + representations by names and version ids. This filter + discards all other filters. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + representations. Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Generator[RepresentationDict, None, None]: Queried + representation entities. """ con = get_server_api_connection() - return con.get_hero_version_by_product_id( + return con.get_representations( project_name=project_name, - product_id=product_id, + representation_ids=representation_ids, + representation_names=representation_names, + version_ids=version_ids, + names_by_version_ids=names_by_version_ids, + statuses=statuses, + tags=tags, + active=active, + has_links=has_links, fields=fields, own_attributes=own_attributes, ) -def get_hero_versions( +def get_representation_by_id( project_name: str, - product_ids: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, + representation_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["VersionDict", None, None]: - """Query hero versions by multiple filters. - - Only one hero version is available on a product. +) -> Optional["RepresentationDict"]: + """Query representation entity from server based on id filter. Args: - project_name (str): Name of project where to look for queried - entities. - product_ids (Optional[Iterable[str]]): Product ids. - version_ids (Optional[Iterable[str]]): Version ids. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. + project_name (str): Project where to look for representation. + representation_id (str): Id of representation. + fields (Optional[Iterable[str]]): fields to be queried + for representations. own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + representations. Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Optional[RepresentationDict]: Queried representation + entity or None. """ con = get_server_api_connection() - return con.get_hero_versions( + return con.get_representation_by_id( project_name=project_name, - product_ids=product_ids, - version_ids=version_ids, - active=active, + representation_id=representation_id, fields=fields, own_attributes=own_attributes, ) -def get_last_versions( +def get_representation_by_name( project_name: str, - product_ids: Iterable[str], - active: "Union[bool, None]" = True, + representation_name: str, + version_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Dict[str, Optional["VersionDict"]]: - """Query last version entities by product ids. +) -> Optional["RepresentationDict"]: + """Query representation entity by name and version id. Args: project_name (str): Project where to look for representation. - product_ids (Iterable[str]): Product ids. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. + representation_name (str): Representation name. + version_id (str): Version id. fields (Optional[Iterable[str]]): fields to be queried for representations. own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + representations. Returns: - dict[str, Optional[VersionDict]]: Last versions by product id. + Optional[RepresentationDict]: Queried representation entity + or None. """ con = get_server_api_connection() - return con.get_last_versions( + return con.get_representation_by_name( project_name=project_name, - product_ids=product_ids, - active=active, + representation_name=representation_name, + version_id=version_id, fields=fields, own_attributes=own_attributes, ) -def get_last_version_by_product_id( +def get_representations_hierarchy( project_name: str, - product_id: str, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query last version entity by product id. + representation_ids: Iterable[str], + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + task_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + representation_fields: Optional[Iterable[str]] = None, +) -> Dict[str, RepresentationHierarchy]: + """Find representation with parents by representation id. + + Representation entity with parent entities up to project. + + Default fields are used when any fields are set to `None`. But it is + possible to pass in empty iterable (list, set, tuple) to skip + entity. Args: - project_name (str): Project where to look for representation. - product_id (str): Product id. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where to look for entities. + representation_ids (Iterable[str]): Representation ids. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + task_fields (Optional[Iterable[str]]): Task fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + representation_fields (Optional[Iterable[str]]): Representation + fields. Returns: - Optional[VersionDict]: Queried version entity or None. + dict[str, RepresentationHierarchy]: Parent entities by + representation id. """ con = get_server_api_connection() - return con.get_last_version_by_product_id( + return con.get_representations_hierarchy( project_name=project_name, - product_id=product_id, - active=active, - fields=fields, - own_attributes=own_attributes, + representation_ids=representation_ids, + project_fields=project_fields, + folder_fields=folder_fields, + task_fields=task_fields, + product_fields=product_fields, + version_fields=version_fields, + representation_fields=representation_fields, ) -def get_last_version_by_product_name( +def get_representation_hierarchy( project_name: str, - product_name: str, - folder_id: str, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query last version entity by product name and folder id. + representation_id: str, + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + task_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + representation_fields: Optional[Iterable[str]] = None, +) -> Optional[RepresentationHierarchy]: + """Find representation parents by representation id. + + Representation parent entities up to project. Args: - project_name (str): Project where to look for representation. - product_name (str): Product name. - folder_id (str): Folder id. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + project_name (str): Project where to look for entities. + representation_id (str): Representation id. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + task_fields (Optional[Iterable[str]]): Task fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + representation_fields (Optional[Iterable[str]]): Representation + fields. Returns: - Optional[VersionDict]: Queried version entity or None. + RepresentationHierarchy: Representation hierarchy entities. """ con = get_server_api_connection() - return con.get_last_version_by_product_name( + return con.get_representation_hierarchy( project_name=project_name, - product_name=product_name, - folder_id=folder_id, - active=active, - fields=fields, - own_attributes=own_attributes, + representation_id=representation_id, + project_fields=project_fields, + folder_fields=folder_fields, + task_fields=task_fields, + product_fields=product_fields, + version_fields=version_fields, + representation_fields=representation_fields, ) -def version_is_latest( +def get_representations_parents( project_name: str, - version_id: str, -) -> bool: - """Is version latest from a product. + representation_ids: Iterable[str], + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, +) -> Dict[str, RepresentationParents]: + """Find representations parents by representation id. + + Representation parent entities up to project. Args: - project_name (str): Project where to look for representation. - version_id (str): Version id. + project_name (str): Project where to look for entities. + representation_ids (Iterable[str]): Representation ids. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. Returns: - bool: Version is latest or not. + dict[str, RepresentationParents]: Parent entities by + representation id. """ con = get_server_api_connection() - return con.version_is_latest( + return con.get_representations_parents( project_name=project_name, - version_id=version_id, + representation_ids=representation_ids, + project_fields=project_fields, + folder_fields=folder_fields, + product_fields=product_fields, + version_fields=version_fields, ) -def create_version( +def get_representation_parents( project_name: str, - version: int, - product_id: str, - task_id: Optional[str] = None, - author: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - version_id: Optional[str] = None, -) -> str: - """Create new version. + representation_id: str, + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, +) -> Optional["RepresentationParents"]: + """Find representation parents by representation id. + + Representation parent entities up to project. Args: - project_name (str): Project name. - version (int): Version. - product_id (str): Parent product id. - task_id (Optional[str]): Parent task id. - author (Optional[str]): Version author. - attrib (Optional[dict[str, Any]]): Version attributes. - data (Optional[dict[str, Any]]): Version data. - tags (Optional[Iterable[str]]): Version tags. - status (Optional[str]): Version status. - active (Optional[bool]): Version active state. - thumbnail_id (Optional[str]): Version thumbnail id. - version_id (Optional[str]): Version id. If not passed new id is - generated. + project_name (str): Project where to look for entities. + representation_id (str): Representation id. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. Returns: - str: Version id. + RepresentationParents: Representation parent entities. """ con = get_server_api_connection() - return con.create_version( + return con.get_representation_parents( project_name=project_name, - version=version, - product_id=product_id, - task_id=task_id, - author=author, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - thumbnail_id=thumbnail_id, - version_id=version_id, - ) - + representation_id=representation_id, + project_fields=project_fields, + folder_fields=folder_fields, + product_fields=product_fields, + version_fields=version_fields, + ) -def update_version( + +def get_repre_ids_by_context_filters( + project_name: str, + context_filters: Optional[Dict[str, Iterable[str]]], + representation_names: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, +) -> List[str]: + """Find representation ids which match passed context filters. + + Each representation has context integrated on representation entity in + database. The context may contain project, folder, task name or + product name, product type and many more. This implementation gives + option to quickly filter representation based on representation data + in database. + + Context filters have defined structure. To define filter of nested + subfield use dot '.' as delimiter (For example 'task.name'). + Filter values can be regex filters. String or ``re.Pattern`` can + be used. + + Args: + project_name (str): Project where to look for representations. + context_filters (dict[str, list[str]]): Filters of context fields. + representation_names (Optional[Iterable[str]]): Representation + names, can be used as additional filter for representations + by their names. + version_ids (Optional[Iterable[str]]): Version ids, can be used + as additional filter for representations by their parent ids. + + Returns: + list[str]: Representation ids that match passed filters. + + Example: + The function returns just representation ids so if entities are + required for funtionality they must be queried afterwards by + their ids. + >>> project_name = "testProject" + >>> filters = { + ... "task.name": ["[aA]nimation"], + ... "product": [".*[Mm]ain"] + ... } + >>> repre_ids = get_repre_ids_by_context_filters( + ... project_name, filters) + >>> repres = get_representations(project_name, repre_ids) + + """ + con = get_server_api_connection() + return con.get_repre_ids_by_context_filters( + project_name=project_name, + context_filters=context_filters, + representation_names=representation_names, + version_ids=version_ids, + ) + + +def create_representation( project_name: str, + name: str, version_id: str, - version: Optional[int] = None, - product_id: Optional[str] = None, - task_id: Optional[str] = NOT_SET, - author: Optional[str] = None, + files: Optional[List[Dict[str, Any]]] = None, attrib: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, + traits: Optional[Dict[str, Any]] = None, + tags: Optional[List[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, -): - """Update version entity on server. + representation_id: Optional[str] = None, +) -> str: + """Create new representation. - Do not pass ``task_id`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. + Args: + project_name (str): Project name. + name (str): Representation name. + version_id (str): Parent version id. + files (Optional[list[dict]]): Representation files information. + attrib (Optional[dict[str, Any]]): Representation attributes. + data (Optional[dict[str, Any]]): Representation data. + traits (Optional[dict[str, Any]]): Representation traits + serialized data as dict. + tags (Optional[Iterable[str]]): Representation tags. + status (Optional[str]): Representation status. + active (Optional[bool]): Representation active state. + representation_id (Optional[str]): Representation id. If not + passed new id is generated. + + Returns: + str: Representation id. + + """ + con = get_server_api_connection() + return con.create_representation( + project_name=project_name, + name=name, + version_id=version_id, + files=files, + attrib=attrib, + data=data, + traits=traits, + tags=tags, + status=status, + active=active, + representation_id=representation_id, + ) + + +def update_representation( + project_name: str, + representation_id: str, + name: Optional[str] = None, + version_id: Optional[str] = None, + files: Optional[List[Dict[str, Any]]] = None, + attrib: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + traits: Optional[Dict[str, Any]] = None, + tags: Optional[List[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, +): + """Update representation entity on server. Update of ``data`` will override existing value on folder entity. @@ -4761,1360 +4715,1407 @@ def update_version( Args: project_name (str): Project name. - version_id (str): Version id. - version (Optional[int]): New version. - product_id (Optional[str]): New product id. - task_id (Optional[Union[str, None]]): New task id. - author (Optional[str]): New author username. + representation_id (str): Representation id. + name (Optional[str]): New name. + version_id (Optional[str]): New version id. + files (Optional[list[dict]]): New files + information. attrib (Optional[dict[str, Any]]): New attributes. data (Optional[dict[str, Any]]): New data. + traits (Optional[dict[str, Any]]): New traits. tags (Optional[Iterable[str]]): New tags. status (Optional[str]): New status. active (Optional[bool]): New active state. - thumbnail_id (Optional[Union[str, None]]): New thumbnail id. """ con = get_server_api_connection() - return con.update_version( + return con.update_representation( project_name=project_name, + representation_id=representation_id, + name=name, version_id=version_id, - version=version, - product_id=product_id, - task_id=task_id, - author=author, + files=files, attrib=attrib, data=data, + traits=traits, tags=tags, status=status, active=active, - thumbnail_id=thumbnail_id, ) -def delete_version( +def delete_representation( project_name: str, - version_id: str, + representation_id: str, ): - """Delete version. + """Delete representation. Args: project_name (str): Project name. - version_id (str): Version id to delete. + representation_id (str): Representation id to delete. """ con = get_server_api_connection() - return con.delete_version( + return con.delete_representation( project_name=project_name, - version_id=version_id, + representation_id=representation_id, ) -def get_representations( +def get_workfiles_info( project_name: str, - representation_ids: Optional[Iterable[str]] = None, - representation_names: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - names_by_version_ids: Optional[Dict[str, Iterable[str]]] = None, + workfile_ids: Optional[Iterable[str]] = None, + task_ids: Optional[Iterable[str]] = None, + paths: Optional[Iterable[str]] = None, + path_regex: Optional[str] = None, statuses: Optional[Iterable[str]] = None, tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, has_links: Optional[str] = None, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["RepresentationDict", None, None]: - """Get representation entities based on passed filters from server. - - .. todo:: - - Add separated function for 'names_by_version_ids' filtering. - Because can't be combined with others. +) -> Generator["WorkfileInfoDict", None, None]: + """Workfile info entities by passed filters. Args: - project_name (str): Name of project where to look for versions. - representation_ids (Optional[Iterable[str]]): Representation ids - used for representation filtering. - representation_names (Optional[Iterable[str]]): Representation - names used for representation filtering. - version_ids (Optional[Iterable[str]]): Version ids used for - representation filtering. Versions are parents of - representations. - names_by_version_ids (Optional[Dict[str, Iterable[str]]]): Find - representations by names and version ids. This filter - discards all other filters. - statuses (Optional[Iterable[str]]): Representation statuses used + project_name (str): Project under which the entity is located. + workfile_ids (Optional[Iterable[str]]): Workfile ids. + task_ids (Optional[Iterable[str]]): Task ids. + paths (Optional[Iterable[str]]): Rootless workfiles paths. + path_regex (Optional[str]): Regex filter for workfile path. + statuses (Optional[Iterable[str]]): Workfile info statuses used for filtering. - tags (Optional[Iterable[str]]): Representation tags used + tags (Optional[Iterable[str]]): Workfile info tags used for filtering. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. has_links (Optional[Literal[IN, OUT, ANY]]): Filter representations with IN/OUT/ANY links. fields (Optional[Iterable[str]]): Fields to be queried for representation. All possible fields are returned if 'None' is passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + workfiles. Returns: - Generator[RepresentationDict, None, None]: Queried - representation entities. + Generator[WorkfileInfoDict, None, None]: Queried workfile info + entites. """ con = get_server_api_connection() - return con.get_representations( + return con.get_workfiles_info( project_name=project_name, - representation_ids=representation_ids, - representation_names=representation_names, - version_ids=version_ids, - names_by_version_ids=names_by_version_ids, + workfile_ids=workfile_ids, + task_ids=task_ids, + paths=paths, + path_regex=path_regex, statuses=statuses, tags=tags, - active=active, has_links=has_links, fields=fields, own_attributes=own_attributes, ) -def get_representation_by_id( +def get_workfile_info( project_name: str, - representation_id: str, + task_id: str, + path: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["RepresentationDict"]: - """Query representation entity from server based on id filter. +) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by task id and workfile path. Args: - project_name (str): Project where to look for representation. - representation_id (str): Id of representation. - fields (Optional[Iterable[str]]): fields to be queried - for representations. + project_name (str): Project under which the entity is located. + task_id (str): Task id. + path (str): Rootless workfile path. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + workfiles. Returns: - Optional[RepresentationDict]: Queried representation - entity or None. + Optional[WorkfileInfoDict]: Workfile info entity or None. """ con = get_server_api_connection() - return con.get_representation_by_id( + return con.get_workfile_info( project_name=project_name, - representation_id=representation_id, + task_id=task_id, + path=path, fields=fields, own_attributes=own_attributes, ) -def get_representation_by_name( +def get_workfile_info_by_id( project_name: str, - representation_name: str, - version_id: str, + workfile_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["RepresentationDict"]: - """Query representation entity by name and version id. +) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by id. Args: - project_name (str): Project where to look for representation. - representation_name (str): Representation name. - version_id (str): Version id. - fields (Optional[Iterable[str]]): fields to be queried - for representations. + project_name (str): Project under which the entity is located. + workfile_id (str): Workfile info id. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + workfiles. Returns: - Optional[RepresentationDict]: Queried representation entity - or None. + Optional[WorkfileInfoDict]: Workfile info entity or None. """ con = get_server_api_connection() - return con.get_representation_by_name( + return con.get_workfile_info_by_id( project_name=project_name, - representation_name=representation_name, - version_id=version_id, + workfile_id=workfile_id, fields=fields, own_attributes=own_attributes, ) -def get_representations_hierarchy( +def get_thumbnail_by_id( project_name: str, - representation_ids: Iterable[str], - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - task_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - representation_fields: Optional[Iterable[str]] = None, -) -> Dict[str, RepresentationHierarchy]: - """Find representation with parents by representation id. + thumbnail_id: str, +) -> ThumbnailContent: + """Get thumbnail from server by id. - Representation entity with parent entities up to project. + Warnings: + Please keep in mind that used endpoint is allowed only for admins + and managers. Use 'get_thumbnail' with entity type and id + to allow access for artists. - Default fields are used when any fields are set to `None`. But it is - possible to pass in empty iterable (list, set, tuple) to skip - entity. + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. Args: - project_name (str): Project where to look for entities. - representation_ids (Iterable[str]): Representation ids. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - task_fields (Optional[Iterable[str]]): Task fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - representation_fields (Optional[Iterable[str]]): Representation - fields. + project_name (str): Project under which the entity is located. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. Returns: - dict[str, RepresentationHierarchy]: Parent entities by - representation id. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_representations_hierarchy( + return con.get_thumbnail_by_id( project_name=project_name, - representation_ids=representation_ids, - project_fields=project_fields, - folder_fields=folder_fields, - task_fields=task_fields, - product_fields=product_fields, - version_fields=version_fields, - representation_fields=representation_fields, + thumbnail_id=thumbnail_id, ) -def get_representation_hierarchy( +def get_thumbnail( project_name: str, - representation_id: str, - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - task_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - representation_fields: Optional[Iterable[str]] = None, -) -> Optional[RepresentationHierarchy]: - """Find representation parents by representation id. + entity_type: str, + entity_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Get thumbnail from server. - Representation parent entities up to project. + Permissions of thumbnails are related to entities so thumbnails must + be queried per entity. So an entity type and entity id is required + to be passed. + + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. Args: - project_name (str): Project where to look for entities. - representation_id (str): Representation id. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - task_fields (Optional[Iterable[str]]): Task fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - representation_fields (Optional[Iterable[str]]): Representation - fields. + project_name (str): Project under which the entity is located. + entity_type (str): Entity type which passed entity id represents. + entity_id (str): Entity id for which thumbnail should be returned. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. Returns: - RepresentationHierarchy: Representation hierarchy entities. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_representation_hierarchy( + return con.get_thumbnail( project_name=project_name, - representation_id=representation_id, - project_fields=project_fields, - folder_fields=folder_fields, - task_fields=task_fields, - product_fields=product_fields, - version_fields=version_fields, - representation_fields=representation_fields, + entity_type=entity_type, + entity_id=entity_id, + thumbnail_id=thumbnail_id, ) -def get_representations_parents( +def get_folder_thumbnail( project_name: str, - representation_ids: Iterable[str], - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, -) -> Dict[str, RepresentationParents]: - """Find representations parents by representation id. - - Representation parent entities up to project. + folder_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for folder entity. Args: - project_name (str): Project where to look for entities. - representation_ids (Iterable[str]): Representation ids. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. + project_name (str): Project under which the entity is located. + folder_id (str): Folder id for which thumbnail should be returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. Returns: - dict[str, RepresentationParents]: Parent entities by - representation id. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_representations_parents( + return con.get_folder_thumbnail( project_name=project_name, - representation_ids=representation_ids, - project_fields=project_fields, - folder_fields=folder_fields, - product_fields=product_fields, - version_fields=version_fields, + folder_id=folder_id, + thumbnail_id=thumbnail_id, ) -def get_representation_parents( +def get_task_thumbnail( project_name: str, - representation_id: str, - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, -) -> Optional["RepresentationParents"]: - """Find representation parents by representation id. - - Representation parent entities up to project. + task_id: str, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for task entity. Args: - project_name (str): Project where to look for entities. - representation_id (str): Representation id. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. + project_name (str): Project under which the entity is located. + task_id (str): Folder id for which thumbnail should be returned. Returns: - RepresentationParents: Representation parent entities. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_representation_parents( + return con.get_task_thumbnail( project_name=project_name, - representation_id=representation_id, - project_fields=project_fields, - folder_fields=folder_fields, - product_fields=product_fields, - version_fields=version_fields, + task_id=task_id, ) -def get_repre_ids_by_context_filters( +def get_version_thumbnail( project_name: str, - context_filters: Optional[Dict[str, Iterable[str]]], - representation_names: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, -) -> List[str]: - """Find representation ids which match passed context filters. - - Each representation has context integrated on representation entity in - database. The context may contain project, folder, task name or - product name, product type and many more. This implementation gives - option to quickly filter representation based on representation data - in database. - - Context filters have defined structure. To define filter of nested - subfield use dot '.' as delimiter (For example 'task.name'). - Filter values can be regex filters. String or ``re.Pattern`` can - be used. + version_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for version entity. Args: - project_name (str): Project where to look for representations. - context_filters (dict[str, list[str]]): Filters of context fields. - representation_names (Optional[Iterable[str]]): Representation - names, can be used as additional filter for representations - by their names. - version_ids (Optional[Iterable[str]]): Version ids, can be used - as additional filter for representations by their parent ids. + project_name (str): Project under which the entity is located. + version_id (str): Version id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. Returns: - list[str]: Representation ids that match passed filters. - - Example: - The function returns just representation ids so if entities are - required for funtionality they must be queried afterwards by - their ids. - >>> project_name = "testProject" - >>> filters = { - ... "task.name": ["[aA]nimation"], - ... "product": [".*[Mm]ain"] - ... } - >>> repre_ids = get_repre_ids_by_context_filters( - ... project_name, filters) - >>> repres = get_representations(project_name, repre_ids) + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_repre_ids_by_context_filters( + return con.get_version_thumbnail( project_name=project_name, - context_filters=context_filters, - representation_names=representation_names, - version_ids=version_ids, + version_id=version_id, + thumbnail_id=thumbnail_id, ) -def create_representation( +def get_workfile_thumbnail( project_name: str, - name: str, - version_id: str, - files: Optional[List[Dict[str, Any]]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - traits: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - representation_id: Optional[str] = None, -) -> str: - """Create new representation. + workfile_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for workfile entity. Args: - project_name (str): Project name. - name (str): Representation name. - version_id (str): Parent version id. - files (Optional[list[dict]]): Representation files information. - attrib (Optional[dict[str, Any]]): Representation attributes. - data (Optional[dict[str, Any]]): Representation data. - traits (Optional[dict[str, Any]]): Representation traits - serialized data as dict. - tags (Optional[Iterable[str]]): Representation tags. - status (Optional[str]): Representation status. - active (Optional[bool]): Representation active state. - representation_id (Optional[str]): Representation id. If not - passed new id is generated. + project_name (str): Project under which the entity is located. + workfile_id (str): Worfile id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. Returns: - str: Representation id. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.create_representation( + return con.get_workfile_thumbnail( project_name=project_name, - name=name, - version_id=version_id, - files=files, - attrib=attrib, - data=data, - traits=traits, - tags=tags, - status=status, - active=active, - representation_id=representation_id, + workfile_id=workfile_id, + thumbnail_id=thumbnail_id, ) -def update_representation( +def create_thumbnail( project_name: str, - representation_id: str, - name: Optional[str] = None, - version_id: Optional[str] = None, - files: Optional[List[Dict[str, Any]]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - traits: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, -): - """Update representation entity on server. + src_filepath: str, + thumbnail_id: Optional[str] = None, +) -> str: + """Create new thumbnail on server from passed path. - Update of ``data`` will override existing value on folder entity. + Args: + project_name (str): Project where the thumbnail will be created + and can be used. + src_filepath (str): Filepath to thumbnail which should be uploaded. + thumbnail_id (Optional[str]): Prepared if of thumbnail. - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. + Returns: + str: Created thumbnail id. - Args: - project_name (str): Project name. - representation_id (str): Representation id. - name (Optional[str]): New name. - version_id (Optional[str]): New version id. - files (Optional[list[dict]]): New files - information. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - traits (Optional[dict[str, Any]]): New traits. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. + Raises: + ValueError: When thumbnail source cannot be processed. """ con = get_server_api_connection() - return con.update_representation( + return con.create_thumbnail( project_name=project_name, - representation_id=representation_id, - name=name, - version_id=version_id, - files=files, - attrib=attrib, - data=data, - traits=traits, - tags=tags, - status=status, - active=active, + src_filepath=src_filepath, + thumbnail_id=thumbnail_id, ) -def delete_representation( +def update_thumbnail( project_name: str, - representation_id: str, + thumbnail_id: str, + src_filepath: str, ): - """Delete representation. + """Change thumbnail content by id. + + Update can be also used to create new thumbnail. Args: - project_name (str): Project name. - representation_id (str): Representation id to delete. + project_name (str): Project where the thumbnail will be created + and can be used. + thumbnail_id (str): Thumbnail id to update. + src_filepath (str): Filepath to thumbnail which should be uploaded. + + Raises: + ValueError: When thumbnail source cannot be processed. """ con = get_server_api_connection() - return con.delete_representation( + return con.update_thumbnail( project_name=project_name, - representation_id=representation_id, + thumbnail_id=thumbnail_id, + src_filepath=src_filepath, ) -def get_workfiles_info( +def send_batch_operations( project_name: str, - workfile_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] = None, - paths: Optional[Iterable[str]] = None, - path_regex: Optional[str] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - has_links: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["WorkfileInfoDict", None, None]: - """Workfile info entities by passed filters. + operations: List[Dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True, +) -> List[Dict[str, Any]]: + """Post multiple CRUD operations to server. + + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. Args: - project_name (str): Project under which the entity is located. - workfile_ids (Optional[Iterable[str]]): Workfile ids. - task_ids (Optional[Iterable[str]]): Task ids. - paths (Optional[Iterable[str]]): Rootless workfiles paths. - path_regex (Optional[str]): Regex filter for workfile path. - statuses (Optional[Iterable[str]]): Workfile info statuses used - for filtering. - tags (Optional[Iterable[str]]): Workfile info tags used - for filtering. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. Returns: - Generator[WorkfileInfoDict, None, None]: Queried workfile info - entites. + list[dict[str, Any]]: Operations result with process details. """ con = get_server_api_connection() - return con.get_workfiles_info( + return con.send_batch_operations( project_name=project_name, - workfile_ids=workfile_ids, - task_ids=task_ids, - paths=paths, - path_regex=path_regex, - statuses=statuses, - tags=tags, - has_links=has_links, - fields=fields, - own_attributes=own_attributes, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) -def get_workfile_info( +def send_activities_batch_operations( project_name: str, - task_id: str, - path: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by task id and workfile path. + operations: List[Dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True, +) -> List[Dict[str, Any]]: + """Post multiple CRUD activities operations to server. + + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. Args: - project_name (str): Project under which the entity is located. - task_id (str): Task id. - path (str): Rootless workfile path. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. + list[dict[str, Any]]: Operations result with process details. """ con = get_server_api_connection() - return con.get_workfile_info( + return con.send_activities_batch_operations( project_name=project_name, - task_id=task_id, - path=path, - fields=fields, - own_attributes=own_attributes, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) -def get_workfile_info_by_id( - project_name: str, - workfile_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by id. +def get_actions( + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, + mode: Optional["ActionModeType"] = None, +) -> List["ActionManifestDict"]: + """Get actions for a context. Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Workfile info id. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + mode (Optional[ActionModeType]): Action modes. Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. + List[ActionManifestDict]: List of action manifests. """ con = get_server_api_connection() - return con.get_workfile_info_by_id( + return con.get_actions( project_name=project_name, - workfile_id=workfile_id, - fields=fields, - own_attributes=own_attributes, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + mode=mode, ) -def get_thumbnail_by_id( - project_name: str, - thumbnail_id: str, -) -> ThumbnailContent: - """Get thumbnail from server by id. - - Warnings: - Please keep in mind that used endpoint is allowed only for admins - and managers. Use 'get_thumbnail' with entity type and id - to allow access for artists. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. +def trigger_action( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionTriggerResponse": + """Trigger action. Args: - project_name (str): Project under which the entity is located. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. """ con = get_server_api_connection() - return con.get_thumbnail_by_id( + return con.trigger_action( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, project_name=project_name, - thumbnail_id=thumbnail_id, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def get_thumbnail( - project_name: str, - entity_type: str, - entity_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Get thumbnail from server. - - Permissions of thumbnails are related to entities so thumbnails must - be queried per entity. So an entity type and entity id is required - to be passed. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. +def get_action_config( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Get action configuration. Args: - project_name (str): Project under which the entity is located. - entity_type (str): Entity type which passed entity id represents. - entity_id (str): Entity id for which thumbnail should be returned. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + ActionConfigResponse: Action configuration data. """ con = get_server_api_connection() - return con.get_thumbnail( + return con.get_action_config( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, project_name=project_name, entity_type=entity_type, - entity_id=entity_id, - thumbnail_id=thumbnail_id, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def get_folder_thumbnail( - project_name: str, - folder_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for folder entity. +def set_action_config( + identifier: str, + addon_name: str, + addon_version: str, + value: Dict[str, Any], + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Set action configuration. Args: - project_name (str): Project under which the entity is located. - folder_id (str): Folder id for which thumbnail should be returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + value (Optional[Dict[str, Any]]): Value of the action + configuration. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + ActionConfigResponse: New action configuration data. """ con = get_server_api_connection() - return con.get_folder_thumbnail( + return con.set_action_config( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, + value=value, project_name=project_name, - folder_id=folder_id, - thumbnail_id=thumbnail_id, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def get_task_thumbnail( - project_name: str, - task_id: str, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for task entity. +def take_action( + action_token: str, +) -> "ActionTakeResponse": + """Take action metadata using an action token. Args: - project_name (str): Project under which the entity is located. - task_id (str): Folder id for which thumbnail should be returned. + action_token (str): AYON launcher action token. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + ActionTakeResponse: Action metadata describing how to launch + action. """ con = get_server_api_connection() - return con.get_task_thumbnail( - project_name=project_name, - task_id=task_id, + return con.take_action( + action_token=action_token, ) -def get_version_thumbnail( - project_name: str, - version_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for version entity. +def abort_action( + action_token: str, + message: Optional[str] = None, +) -> None: + """Abort action using an action token. Args: - project_name (str): Project under which the entity is located. - version_id (str): Version id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + action_token (str): AYON launcher action token. + message (Optional[str]): Message to display in the UI. """ con = get_server_api_connection() - return con.get_version_thumbnail( - project_name=project_name, - version_id=version_id, - thumbnail_id=thumbnail_id, + return con.abort_action( + action_token=action_token, + message=message, ) -def get_workfile_thumbnail( - project_name: str, - workfile_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for workfile entity. +def get_addon_endpoint( + addon_name: str, + addon_version: str, + *subpaths, +) -> str: + """Calculate endpoint to addon route. + + Examples: + >>> from ayon_api import ServerAPI + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'addons/example/1.0.0/private/my.zip' Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Worfile id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + str: Final url. """ con = get_server_api_connection() - return con.get_workfile_thumbnail( - project_name=project_name, - workfile_id=workfile_id, - thumbnail_id=thumbnail_id, + return con.get_addon_endpoint( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, ) -def create_thumbnail( - project_name: str, - src_filepath: str, - thumbnail_id: Optional[str] = None, -) -> str: - """Create new thumbnail on server from passed path. +def get_addons_info( + details: bool = True, +) -> "AddonsInfoDict": + """Get information about addons available on server. Args: - project_name (str): Project where the thumbnail will be created - and can be used. - src_filepath (str): Filepath to thumbnail which should be uploaded. - thumbnail_id (Optional[str]): Prepared if of thumbnail. - - Returns: - str: Created thumbnail id. - - Raises: - ValueError: When thumbnail source cannot be processed. + details (Optional[bool]): Detailed data with information how + to get client code. """ con = get_server_api_connection() - return con.create_thumbnail( - project_name=project_name, - src_filepath=src_filepath, - thumbnail_id=thumbnail_id, + return con.get_addons_info( + details=details, ) -def update_thumbnail( - project_name: str, - thumbnail_id: str, - src_filepath: str, -): - """Change thumbnail content by id. +def get_addon_url( + addon_name: str, + addon_version: str, + *subpaths, + use_rest: bool = True, +) -> str: + """Calculate url to addon route. - Update can be also used to create new thumbnail. + Examples: + + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' Args: - project_name (str): Project where the thumbnail will be created - and can be used. - thumbnail_id (str): Thumbnail id to update. - src_filepath (str): Filepath to thumbnail which should be uploaded. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. + use_rest (Optional[bool]): Use rest endpoint. - Raises: - ValueError: When thumbnail source cannot be processed. + Returns: + str: Final url. """ con = get_server_api_connection() - return con.update_thumbnail( - project_name=project_name, - thumbnail_id=thumbnail_id, - src_filepath=src_filepath, + return con.get_addon_url( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, + use_rest=use_rest, ) -def send_batch_operations( - project_name: str, - operations: List[Dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True, -) -> List[Dict[str, Any]]: - """Post multiple CRUD operations to server. +def delete_addon( + addon_name: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon from server. - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. + Delete all versions of addon from server. Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. + addon_name (str): Addon name. + purge (Optional[bool]): Purge all data related to the addon. - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. + """ + con = get_server_api_connection() + return con.delete_addon( + addon_name=addon_name, + purge=purge, + ) - Returns: - list[dict[str, Any]]: Operations result with process details. + +def delete_addon_version( + addon_name: str, + addon_version: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon version from server. + + Delete all versions of addon from server. + + Args: + addon_name (str): Addon name. + addon_version (str): Addon version. + purge (Optional[bool]): Purge all data related to the addon. """ con = get_server_api_connection() - return con.send_batch_operations( - project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, + return con.delete_addon_version( + addon_name=addon_name, + addon_version=addon_version, + purge=purge, ) -def send_activities_batch_operations( - project_name: str, - operations: List[Dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True, -) -> List[Dict[str, Any]]: - """Post multiple CRUD activities operations to server. +def upload_addon_zip( + src_filepath: str, + progress: Optional[TransferProgress] = None, +): + """Upload addon zip file to server. - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. + File is validated on server. If it is valid, it is installed. It will + create an event job which can be tracked (tracking part is not + implemented yet). - Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. + Example output:: - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. + {'eventId': 'a1bfbdee27c611eea7580242ac120003'} + + Args: + src_filepath (str): Path to a zip file. + progress (Optional[TransferProgress]): Object to keep track about + upload state. Returns: - list[dict[str, Any]]: Operations result with process details. + dict[str, Any]: Response data from server. """ con = get_server_api_connection() - return con.send_activities_batch_operations( - project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, + return con.upload_addon_zip( + src_filepath=src_filepath, + progress=progress, ) -def get_actions( - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, - mode: Optional["ActionModeType"] = None, -) -> List["ActionManifestDict"]: - """Get actions for a context. +def download_addon_private_file( + addon_name: str, + addon_version: str, + filename: str, + destination_dir: str, + destination_filename: Optional[str] = None, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, +) -> str: + """Download a file from addon private files. + + This method requires to have authorized token available. Private files + are not under '/api' restpoint. Args: - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. - mode (Optional[ActionModeType]): Action modes. + addon_name (str): Addon name. + addon_version (str): Addon version. + filename (str): Filename in private folder on server. + destination_dir (str): Where the file should be downloaded. + destination_filename (Optional[str]): Name of destination + filename. Source filename is used if not passed. + chunk_size (Optional[int]): Download chunk size. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. Returns: - List[ActionManifestDict]: List of action manifests. + str: Filepath to downloaded file. """ con = get_server_api_connection() - return con.get_actions( - project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, - mode=mode, + return con.download_addon_private_file( + addon_name=addon_name, + addon_version=addon_version, + filename=filename, + destination_dir=destination_dir, + destination_filename=destination_filename, + chunk_size=chunk_size, + progress=progress, ) -def trigger_action( - identifier: str, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionTriggerResponse": - """Trigger action. - - Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. - - """ +def get_rest_folder( + project_name: str, + folder_id: str, +) -> Optional["FolderDict"]: con = get_server_api_connection() - return con.trigger_action( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, + return con.get_rest_folder( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + folder_id=folder_id, ) -def get_action_config( - identifier: str, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Get action configuration. +def get_rest_folders( + project_name: str, + include_attrib: bool = False, +) -> list["FlatFolderDict"]: + """Get simplified flat list of all project folders. + + Get all project folders in single REST call. This can be faster than + using 'get_folders' method which is using GraphQl, but does not + allow any filtering, and set of fields is defined + by server backend. + + Example:: + + [ + { + "id": "112233445566", + "parentId": "112233445567", + "path": "/root/parent/child", + "parents": ["root", "parent"], + "name": "child", + "label": "Child", + "folderType": "Folder", + "hasTasks": False, + "hasChildren": False, + "taskNames": [ + "Compositing", + ], + "status": "In Progress", + "attrib": {}, + "ownAttrib": [], + "updatedAt": "2023-06-12T15:37:02.420260", + }, + ... + ] Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + project_name (str): Project name. + include_attrib (Optional[bool]): Include attribute values + in output. Slower to query. Returns: - ActionConfigResponse: Action configuration data. + List[FlatFolderDict]: List of folder entities. """ con = get_server_api_connection() - return con.get_action_config( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, + return con.get_rest_folders( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + include_attrib=include_attrib, ) -def set_action_config( - identifier: str, - addon_name: str, - addon_version: str, - value: Dict[str, Any], - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Set action configuration. +def get_folders_hierarchy( + project_name: str, + search_string: Optional[str] = None, + folder_types: Optional[Iterable[str]] = None, +) -> "ProjectHierarchyDict": + """Get project hierarchy. + + All folders in project in hierarchy data structure. + + Example output: + { + "hierarchy": [ + { + "id": "...", + "name": "...", + "label": "...", + "status": "...", + "folderType": "...", + "hasTasks": False, + "taskNames": [], + "parents": [], + "parentId": None, + "children": [...children folders...] + }, + ... + ] + } Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - value (Optional[Dict[str, Any]]): Value of the action - configuration. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + project_name (str): Project where to look for folders. + search_string (Optional[str]): Search string to filter folders. + folder_types (Optional[Iterable[str]]): Folder types to filter. Returns: - ActionConfigResponse: New action configuration data. + dict[str, Any]: Response data from server. """ con = get_server_api_connection() - return con.set_action_config( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, - value=value, + return con.get_folders_hierarchy( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + search_string=search_string, + folder_types=folder_types, ) -def take_action( - action_token: str, -) -> "ActionTakeResponse": - """Take action metadata using an action token. +def get_folders_rest( + project_name: str, + include_attrib: bool = False, +) -> list["FlatFolderDict"]: + """Get simplified flat list of all project folders. + + Get all project folders in single REST call. This can be faster than + using 'get_folders' method which is using GraphQl, but does not + allow any filtering, and set of fields is defined + by server backend. + + Example:: + + [ + { + "id": "112233445566", + "parentId": "112233445567", + "path": "/root/parent/child", + "parents": ["root", "parent"], + "name": "child", + "label": "Child", + "folderType": "Folder", + "hasTasks": False, + "hasChildren": False, + "taskNames": [ + "Compositing", + ], + "status": "In Progress", + "attrib": {}, + "ownAttrib": [], + "updatedAt": "2023-06-12T15:37:02.420260", + }, + ... + ] + + Deprecated: + Use 'get_rest_folders' instead. Function was renamed to match + other rest functions, like 'get_rest_folder', + 'get_rest_project' etc. . + Will be removed in '1.0.7' or '1.1.0'. Args: - action_token (str): AYON launcher action token. + project_name (str): Project name. + include_attrib (Optional[bool]): Include attribute values + in output. Slower to query. Returns: - ActionTakeResponse: Action metadata describing how to launch - action. + List[FlatFolderDict]: List of folder entities. """ con = get_server_api_connection() - return con.take_action( - action_token=action_token, + return con.get_folders_rest( + project_name=project_name, + include_attrib=include_attrib, ) -def abort_action( - action_token: str, - message: Optional[str] = None, -) -> None: - """Abort action using an action token. +def get_folders( + project_name: str, + folder_ids: Optional[Iterable[str]] = None, + folder_paths: Optional[Iterable[str]] = None, + folder_names: Optional[Iterable[str]] = None, + folder_types: Optional[Iterable[str]] = None, + parent_ids: Optional[Iterable[str]] = None, + folder_path_regex: Optional[str] = None, + has_products: Optional[bool] = None, + has_tasks: Optional[bool] = None, + has_children: Optional[bool] = None, + statuses: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + has_links: Optional[bool] = None, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Generator["FolderDict", None, None]: + """Query folders from server. + + Todos: + Folder name won't be unique identifier, so we should add + folder path filtering. + + Notes: + Filter 'active' don't have direct filter in GraphQl. Args: - action_token (str): AYON launcher action token. - message (Optional[str]): Message to display in the UI. + project_name (str): Name of project. + folder_ids (Optional[Iterable[str]]): Folder ids to filter. + folder_paths (Optional[Iterable[str]]): Folder paths used + for filtering. + folder_names (Optional[Iterable[str]]): Folder names used + for filtering. + folder_types (Optional[Iterable[str]]): Folder types used + for filtering. + parent_ids (Optional[Iterable[str]]): Ids of folder parents. + Use 'None' if folder is direct child of project. + folder_path_regex (Optional[str]): Folder path regex used + for filtering. + has_products (Optional[bool]): Filter folders with/without + products. Ignored when None, default behavior. + has_tasks (Optional[bool]): Filter folders with/without + tasks. Ignored when None, default behavior. + has_children (Optional[bool]): Filter folders with/without + children. Ignored when None, default behavior. + statuses (Optional[Iterable[str]]): Folder statuses used + for filtering. + assignees_all (Optional[Iterable[str]]): Filter by assigness + on children tasks. Task must have all of passed assignees. + tags (Optional[Iterable[str]]): Folder tags used + for filtering. + active (Optional[bool]): Filter active/inactive folders. + Both are returned if is set to None. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Generator[FolderDict, None, None]: Queried folder entities. """ con = get_server_api_connection() - return con.abort_action( - action_token=action_token, - message=message, + return con.get_folders( + project_name=project_name, + folder_ids=folder_ids, + folder_paths=folder_paths, + folder_names=folder_names, + folder_types=folder_types, + parent_ids=parent_ids, + folder_path_regex=folder_path_regex, + has_products=has_products, + has_tasks=has_tasks, + has_children=has_children, + statuses=statuses, + assignees_all=assignees_all, + tags=tags, + active=active, + has_links=has_links, + fields=fields, + own_attributes=own_attributes, ) -def get_addon_endpoint( - addon_name: str, - addon_version: str, - *subpaths, -) -> str: - """Calculate endpoint to addon route. - - Examples: - >>> from ayon_api import ServerAPI - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'addons/example/1.0.0/private/my.zip' +def get_folder_by_id( + project_name: str, + folder_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["FolderDict"]: + """Query folder entity by id. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. + project_name (str): Name of project where to look for queried + entities. + folder_id (str): Folder id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - str: Final url. + Optional[FolderDict]: Folder entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_addon_endpoint( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, + return con.get_folder_by_id( + project_name=project_name, + folder_id=folder_id, + fields=fields, + own_attributes=own_attributes, ) -def get_addons_info( - details: bool = True, -) -> "AddonsInfoDict": - """Get information about addons available on server. +def get_folder_by_path( + project_name: str, + folder_path: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["FolderDict"]: + """Query folder entity by path. + + Folder path is a path to folder with all parent names joined by slash. + + Args: + project_name (str): Name of project where to look for queried + entities. + folder_path (str): Folder path. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. - Args: - details (Optional[bool]): Detailed data with information how - to get client code. + Returns: + Optional[FolderDict]: Folder entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_addons_info( - details=details, + return con.get_folder_by_path( + project_name=project_name, + folder_path=folder_path, + fields=fields, + own_attributes=own_attributes, ) -def get_addon_url( - addon_name: str, - addon_version: str, - *subpaths, - use_rest: bool = True, -) -> str: - """Calculate url to addon route. - - Examples: +def get_folder_by_name( + project_name: str, + folder_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["FolderDict"]: + """Query folder entity by path. - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' + Warnings: + Folder name is not a unique identifier of a folder. Function is + kept for OpenPype 3 compatibility. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. - use_rest (Optional[bool]): Use rest endpoint. + project_name (str): Name of project where to look for queried + entities. + folder_name (str): Folder name. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - str: Final url. + Optional[FolderDict]: Folder entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_addon_url( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, - use_rest=use_rest, + return con.get_folder_by_name( + project_name=project_name, + folder_name=folder_name, + fields=fields, + own_attributes=own_attributes, ) -def delete_addon( - addon_name: str, - purge: Optional[bool] = None, -) -> None: - """Delete addon from server. +def get_folder_ids_with_products( + project_name: str, + folder_ids: Optional[Iterable[str]] = None, +) -> set[str]: + """Find folders which have at least one product. - Delete all versions of addon from server. + Folders that have at least one product should be immutable, so they + should not change path -> change of name or name of any parent + is not possible. Args: - addon_name (str): Addon name. - purge (Optional[bool]): Purge all data related to the addon. + project_name (str): Name of project. + folder_ids (Optional[Iterable[str]]): Limit folder ids filtering + to a set of folders. If set to None all folders on project are + checked. + + Returns: + set[str]: Folder ids that have at least one product. """ con = get_server_api_connection() - return con.delete_addon( - addon_name=addon_name, - purge=purge, + return con.get_folder_ids_with_products( + project_name=project_name, + folder_ids=folder_ids, ) -def delete_addon_version( - addon_name: str, - addon_version: str, - purge: Optional[bool] = None, -) -> None: - """Delete addon version from server. - - Delete all versions of addon from server. +def create_folder( + project_name: str, + name: str, + folder_type: Optional[str] = None, + parent_id: Optional[str] = None, + label: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + folder_id: Optional[str] = None, +) -> str: + """Create new folder. Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - purge (Optional[bool]): Purge all data related to the addon. + project_name (str): Project name. + name (str): Folder name. + folder_type (Optional[str]): Folder type. + parent_id (Optional[str]): Parent folder id. Parent is project + if is ``None``. + label (Optional[str]): Label of folder. + attrib (Optional[dict[str, Any]]): Folder attributes. + data (Optional[dict[str, Any]]): Folder data. + tags (Optional[Iterable[str]]): Folder tags. + status (Optional[str]): Folder status. + active (Optional[bool]): Folder active state. + thumbnail_id (Optional[str]): Folder thumbnail id. + folder_id (Optional[str]): Folder id. If not passed new id is + generated. + + Returns: + str: Entity id. """ con = get_server_api_connection() - return con.delete_addon_version( - addon_name=addon_name, - addon_version=addon_version, - purge=purge, + return con.create_folder( + project_name=project_name, + name=name, + folder_type=folder_type, + parent_id=parent_id, + label=label, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, + folder_id=folder_id, ) -def upload_addon_zip( - src_filepath: str, - progress: Optional[TransferProgress] = None, +def update_folder( + project_name: str, + folder_id: str, + name: Optional[str] = None, + folder_type: Optional[str] = None, + parent_id: Optional[str] = NOT_SET, + label: Optional[str] = NOT_SET, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = NOT_SET, ): - """Upload addon zip file to server. + """Update folder entity on server. - File is validated on server. If it is valid, it is installed. It will - create an event job which can be tracked (tracking part is not - implemented yet). + Do not pass ``parent_id``, ``label`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. - Example output:: + Update of ``data`` will override existing value on folder entity. - {'eventId': 'a1bfbdee27c611eea7580242ac120003'} + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. Args: - src_filepath (str): Path to a zip file. - progress (Optional[TransferProgress]): Object to keep track about - upload state. - - Returns: - dict[str, Any]: Response data from server. + project_name (str): Project name. + folder_id (str): Folder id. + name (Optional[str]): New name. + folder_type (Optional[str]): New folder type. + parent_id (Optional[str]): New parent folder id. + label (Optional[str]): New label. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[str]): New thumbnail id. """ con = get_server_api_connection() - return con.upload_addon_zip( - src_filepath=src_filepath, - progress=progress, + return con.update_folder( + project_name=project_name, + folder_id=folder_id, + name=name, + folder_type=folder_type, + parent_id=parent_id, + label=label, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, ) -def download_addon_private_file( - addon_name: str, - addon_version: str, - filename: str, - destination_dir: str, - destination_filename: Optional[str] = None, - chunk_size: Optional[int] = None, - progress: Optional[TransferProgress] = None, -) -> str: - """Download a file from addon private files. - - This method requires to have authorized token available. Private files - are not under '/api' restpoint. +def delete_folder( + project_name: str, + folder_id: str, + force: bool = False, +): + """Delete folder. Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - filename (str): Filename in private folder on server. - destination_dir (str): Where the file should be downloaded. - destination_filename (Optional[str]): Name of destination - filename. Source filename is used if not passed. - chunk_size (Optional[int]): Download chunk size. - progress (Optional[TransferProgress]): Object that gives ability - to track download progress. - - Returns: - str: Filepath to downloaded file. + project_name (str): Project name. + folder_id (str): Folder id to delete. + force (Optional[bool]): Folder delete folder with all children + folder, products, versions and representations. """ con = get_server_api_connection() - return con.download_addon_private_file( - addon_name=addon_name, - addon_version=addon_version, - filename=filename, - destination_dir=destination_dir, - destination_filename=destination_filename, - chunk_size=chunk_size, - progress=progress, + return con.delete_folder( + project_name=project_name, + folder_id=folder_id, + force=force, ) diff --git a/ayon_api/_folders.py b/ayon_api/_folders.py new file mode 100644 index 000000000..76181c8f9 --- /dev/null +++ b/ayon_api/_folders.py @@ -0,0 +1,637 @@ +from __future__ import annotations + +import warnings +import typing +from typing import Optional, Iterable, Generator, Any + +from ._base import _BaseServerAPI +from .exceptions import UnsupportedServerVersion +from .utils import ( + prepare_query_string, + prepare_list_filters, + fill_own_attribs, + create_entity_id, + NOT_SET, +) +from .graphql_queries import folders_graphql_query + +if typing.TYPE_CHECKING: + from .typing import ( + FolderDict, + FlatFolderDict, + ProjectHierarchyDict, + ) + + +class _FoldersAPI(_BaseServerAPI): + def get_rest_folder( + self, project_name: str, folder_id: str + ) -> Optional["FolderDict"]: + return self.get_rest_entity_by_id( + project_name, "folder", folder_id + ) + + def get_rest_folders( + self, project_name: str, include_attrib: bool = False + ) -> list["FlatFolderDict"]: + """Get simplified flat list of all project folders. + + Get all project folders in single REST call. This can be faster than + using 'get_folders' method which is using GraphQl, but does not + allow any filtering, and set of fields is defined + by server backend. + + Example:: + + [ + { + "id": "112233445566", + "parentId": "112233445567", + "path": "/root/parent/child", + "parents": ["root", "parent"], + "name": "child", + "label": "Child", + "folderType": "Folder", + "hasTasks": False, + "hasChildren": False, + "taskNames": [ + "Compositing", + ], + "status": "In Progress", + "attrib": {}, + "ownAttrib": [], + "updatedAt": "2023-06-12T15:37:02.420260", + }, + ... + ] + + Args: + project_name (str): Project name. + include_attrib (Optional[bool]): Include attribute values + in output. Slower to query. + + Returns: + List[FlatFolderDict]: List of folder entities. + + """ + major, minor, patch, _, _ = self.get_server_version_tuple() + if (major, minor, patch) < (1, 0, 8): + raise UnsupportedServerVersion( + "Function 'get_folders_rest' is supported" + " for AYON server 1.0.8 and above." + ) + query = prepare_query_string({ + "attrib": "true" if include_attrib else "false" + }) + response = self.get( + f"projects/{project_name}/folders{query}" + ) + response.raise_for_status() + return response.data["folders"] + + def get_folders_hierarchy( + self, + project_name: str, + search_string: Optional[str] = None, + folder_types: Optional[Iterable[str]] = None + ) -> "ProjectHierarchyDict": + """Get project hierarchy. + + All folders in project in hierarchy data structure. + + Example output: + { + "hierarchy": [ + { + "id": "...", + "name": "...", + "label": "...", + "status": "...", + "folderType": "...", + "hasTasks": False, + "taskNames": [], + "parents": [], + "parentId": None, + "children": [...children folders...] + }, + ... + ] + } + + Args: + project_name (str): Project where to look for folders. + search_string (Optional[str]): Search string to filter folders. + folder_types (Optional[Iterable[str]]): Folder types to filter. + + Returns: + dict[str, Any]: Response data from server. + + """ + if folder_types: + folder_types = ",".join(folder_types) + + query = prepare_query_string({ + "search": search_string or None, + "types": folder_types or None, + }) + response = self.get( + f"projects/{project_name}/hierarchy{query}" + ) + response.raise_for_status() + return response.data + + def get_folders_rest( + self, project_name: str, include_attrib: bool = False + ) -> list["FlatFolderDict"]: + """Get simplified flat list of all project folders. + + Get all project folders in single REST call. This can be faster than + using 'get_folders' method which is using GraphQl, but does not + allow any filtering, and set of fields is defined + by server backend. + + Example:: + + [ + { + "id": "112233445566", + "parentId": "112233445567", + "path": "/root/parent/child", + "parents": ["root", "parent"], + "name": "child", + "label": "Child", + "folderType": "Folder", + "hasTasks": False, + "hasChildren": False, + "taskNames": [ + "Compositing", + ], + "status": "In Progress", + "attrib": {}, + "ownAttrib": [], + "updatedAt": "2023-06-12T15:37:02.420260", + }, + ... + ] + + Deprecated: + Use 'get_rest_folders' instead. Function was renamed to match + other rest functions, like 'get_rest_folder', + 'get_rest_project' etc. . + Will be removed in '1.0.7' or '1.1.0'. + + Args: + project_name (str): Project name. + include_attrib (Optional[bool]): Include attribute values + in output. Slower to query. + + Returns: + List[FlatFolderDict]: List of folder entities. + + """ + warnings.warn( + ( + "DEPRECATION: Used deprecated 'get_folders_rest'," + " use 'get_rest_folders' instead." + ), + DeprecationWarning + ) + return self.get_rest_folders(project_name, include_attrib) + + def get_folders( + self, + project_name: str, + folder_ids: Optional[Iterable[str]] = None, + folder_paths: Optional[Iterable[str]] = None, + folder_names: Optional[Iterable[str]] = None, + folder_types: Optional[Iterable[str]] = None, + parent_ids: Optional[Iterable[str]] = None, + folder_path_regex: Optional[str] = None, + has_products: Optional[bool] = None, + has_tasks: Optional[bool] = None, + has_children: Optional[bool] = None, + statuses: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + has_links: Optional[bool] = None, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False + ) -> Generator["FolderDict", None, None]: + """Query folders from server. + + Todos: + Folder name won't be unique identifier, so we should add + folder path filtering. + + Notes: + Filter 'active' don't have direct filter in GraphQl. + + Args: + project_name (str): Name of project. + folder_ids (Optional[Iterable[str]]): Folder ids to filter. + folder_paths (Optional[Iterable[str]]): Folder paths used + for filtering. + folder_names (Optional[Iterable[str]]): Folder names used + for filtering. + folder_types (Optional[Iterable[str]]): Folder types used + for filtering. + parent_ids (Optional[Iterable[str]]): Ids of folder parents. + Use 'None' if folder is direct child of project. + folder_path_regex (Optional[str]): Folder path regex used + for filtering. + has_products (Optional[bool]): Filter folders with/without + products. Ignored when None, default behavior. + has_tasks (Optional[bool]): Filter folders with/without + tasks. Ignored when None, default behavior. + has_children (Optional[bool]): Filter folders with/without + children. Ignored when None, default behavior. + statuses (Optional[Iterable[str]]): Folder statuses used + for filtering. + assignees_all (Optional[Iterable[str]]): Filter by assigness + on children tasks. Task must have all of passed assignees. + tags (Optional[Iterable[str]]): Folder tags used + for filtering. + active (Optional[bool]): Filter active/inactive folders. + Both are returned if is set to None. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Generator[FolderDict, None, None]: Queried folder entities. + + """ + if not project_name: + return + + filters = { + "projectName": project_name + } + if not prepare_list_filters( + filters, + ("folderIds", folder_ids), + ("folderPaths", folder_paths), + ("folderNames", folder_names), + ("folderTypes", folder_types), + ("folderStatuses", statuses), + ("folderTags", tags), + ("folderAssigneesAll", assignees_all), + ): + return + + for filter_key, filter_value in ( + ("folderPathRegex", folder_path_regex), + ("folderHasProducts", has_products), + ("folderHasTasks", has_tasks), + ("folderHasLinks", has_links), + ("folderHasChildren", has_children), + ): + if filter_value is not None: + filters[filter_key] = filter_value + + if parent_ids is not None: + parent_ids = set(parent_ids) + if not parent_ids: + return + if None in parent_ids: + # Replace 'None' with '"root"' which is used during GraphQl + # query for parent ids filter for folders without folder + # parent + parent_ids.remove(None) + parent_ids.add("root") + + if project_name in parent_ids: + # Replace project name with '"root"' which is used during + # GraphQl query for parent ids filter for folders without + # folder parent + parent_ids.remove(project_name) + parent_ids.add("root") + + filters["parentFolderIds"] = list(parent_ids) + + if not fields: + fields = self.get_default_fields_for_type("folder") + else: + fields = set(fields) + self._prepare_fields("folder", fields) + + if active is not None: + fields.add("active") + + if own_attributes: + fields.add("ownAttrib") + + query = folders_graphql_query(fields) + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + for parsed_data in query.continuous_query(self): + for folder in parsed_data["project"]["folders"]: + if active is not None and active is not folder["active"]: + continue + + self._convert_entity_data(folder) + + if own_attributes: + fill_own_attribs(folder) + yield folder + + def get_folder_by_id( + self, + project_name: str, + folder_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, + ) -> Optional["FolderDict"]: + """Query folder entity by id. + + Args: + project_name (str): Name of project where to look for queried + entities. + folder_id (str): Folder id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[FolderDict]: Folder entity data or None + if was not found. + + """ + folders = self.get_folders( + project_name, + folder_ids=[folder_id], + active=None, + fields=fields, + own_attributes=own_attributes + ) + for folder in folders: + return folder + return None + + def get_folder_by_path( + self, + project_name: str, + folder_path: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, + ) -> Optional["FolderDict"]: + """Query folder entity by path. + + Folder path is a path to folder with all parent names joined by slash. + + Args: + project_name (str): Name of project where to look for queried + entities. + folder_path (str): Folder path. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[FolderDict]: Folder entity data or None + if was not found. + + """ + folders = self.get_folders( + project_name, + folder_paths=[folder_path], + active=None, + fields=fields, + own_attributes=own_attributes + ) + for folder in folders: + return folder + return None + + def get_folder_by_name( + self, + project_name: str, + folder_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, + ) -> Optional["FolderDict"]: + """Query folder entity by path. + + Warnings: + Folder name is not a unique identifier of a folder. Function is + kept for OpenPype 3 compatibility. + + Args: + project_name (str): Name of project where to look for queried + entities. + folder_name (str): Folder name. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[FolderDict]: Folder entity data or None + if was not found. + + """ + folders = self.get_folders( + project_name, + folder_names=[folder_name], + active=None, + fields=fields, + own_attributes=own_attributes + ) + for folder in folders: + return folder + return None + + def get_folder_ids_with_products( + self, project_name: str, folder_ids: Optional[Iterable[str]] = None + ) -> set[str]: + """Find folders which have at least one product. + + Folders that have at least one product should be immutable, so they + should not change path -> change of name or name of any parent + is not possible. + + Args: + project_name (str): Name of project. + folder_ids (Optional[Iterable[str]]): Limit folder ids filtering + to a set of folders. If set to None all folders on project are + checked. + + Returns: + set[str]: Folder ids that have at least one product. + + """ + if folder_ids is not None: + folder_ids = set(folder_ids) + if not folder_ids: + return set() + + query = folders_graphql_query({"id"}) + query.set_variable_value("projectName", project_name) + query.set_variable_value("folderHasProducts", True) + if folder_ids: + query.set_variable_value("folderIds", list(folder_ids)) + + parsed_data = query.query(self) + folders = parsed_data["project"]["folders"] + return { + folder["id"] + for folder in folders + } + + def create_folder( + self, + project_name: str, + name: str, + folder_type: Optional[str] = None, + parent_id: Optional[str] = None, + label: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + folder_id: Optional[str] = None, + ) -> str: + """Create new folder. + + Args: + project_name (str): Project name. + name (str): Folder name. + folder_type (Optional[str]): Folder type. + parent_id (Optional[str]): Parent folder id. Parent is project + if is ``None``. + label (Optional[str]): Label of folder. + attrib (Optional[dict[str, Any]]): Folder attributes. + data (Optional[dict[str, Any]]): Folder data. + tags (Optional[Iterable[str]]): Folder tags. + status (Optional[str]): Folder status. + active (Optional[bool]): Folder active state. + thumbnail_id (Optional[str]): Folder thumbnail id. + folder_id (Optional[str]): Folder id. If not passed new id is + generated. + + Returns: + str: Entity id. + + """ + if not folder_id: + folder_id = create_entity_id() + create_data = { + "id": folder_id, + "name": name, + } + for key, value in ( + ("folderType", folder_type), + ("parentId", parent_id), + ("label", label), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ("thumbnailId", thumbnail_id), + ): + if value is not None: + create_data[key] = value + + response = self.post( + f"projects/{project_name}/folders", + **create_data + ) + response.raise_for_status() + return folder_id + + def update_folder( + self, + project_name: str, + folder_id: str, + name: Optional[str] = None, + folder_type: Optional[str] = None, + parent_id: Optional[str] = NOT_SET, + label: Optional[str] = NOT_SET, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = NOT_SET, + ): + """Update folder entity on server. + + Do not pass ``parent_id``, ``label`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. + + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. + + Args: + project_name (str): Project name. + folder_id (str): Folder id. + name (Optional[str]): New name. + folder_type (Optional[str]): New folder type. + parent_id (Optional[str]): New parent folder id. + label (Optional[str]): New label. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[str]): New thumbnail id. + + """ + update_data = {} + for key, value in ( + ("name", name), + ("folderType", folder_type), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ): + if value is not None: + update_data[key] = value + + for key, value in ( + ("label", label), + ("parentId", parent_id), + ("thumbnailId", thumbnail_id), + ): + if value is not NOT_SET: + update_data[key] = value + + response = self.patch( + f"projects/{project_name}/folders/{folder_id}", + **update_data + ) + response.raise_for_status() + + def delete_folder( + self, project_name: str, folder_id: str, force: bool = False + ): + """Delete folder. + + Args: + project_name (str): Project name. + folder_id (str): Folder id to delete. + force (Optional[bool]): Folder delete folder with all children + folder, products, versions and representations. + + """ + url = f"projects/{project_name}/folders/{folder_id}" + if force: + url += "?force=true" + response = self.delete(url) + response.raise_for_status() \ No newline at end of file diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index a7f378aa2..8164b0d38 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -81,7 +81,6 @@ ServerNotReached, ServerError, HTTPRequestError, - UnsupportedServerVersion, ) from .utils import ( RequestType, @@ -108,6 +107,7 @@ ) from ._actions import _ActionsAPI from ._addons import _AddonsAPI +from ._folders import _FoldersAPI from ._links import _LinksAPI from ._lists import _ListsAPI from ._projects import _ProjectsAPI @@ -118,7 +118,6 @@ ServerVersion, ActivityType, ActivityReferenceType, - LinkDirection, EventFilter, AttributeScope, AttributeSchemaDataDict, @@ -132,7 +131,6 @@ SecretDict, AnyEntityDict, - ProjectDict, FolderDict, TaskDict, ProductDict, @@ -159,29 +157,6 @@ ) -def _convert_list_filter_value(value): - if value is None: - return None - - if isinstance(value, PatternType): - return [value.pattern] - - if isinstance(value, (int, float, str, bool)): - return [value] - return list(set(value)) - - -def _prepare_list_filters(output, *args, **kwargs): - for key, value in itertools.chain(args, kwargs.items()): - value = _convert_list_filter_value(value) - if value is None: - continue - if not value: - return False - output[key] = value - return True - - def _get_description(response): if HTTPStatus is None: return str(response.orig_response) @@ -398,6 +373,7 @@ def as_user(self, username): class ServerAPI( _ActionsAPI, _AddonsAPI, + _FoldersAPI, _LinksAPI, _ListsAPI, _ProjectsAPI, @@ -4200,71 +4176,6 @@ def get_rest_entity_by_id( return response.data return None - def get_rest_folder( - self, project_name: str, folder_id: str - ) -> Optional["FolderDict"]: - return self.get_rest_entity_by_id( - project_name, "folder", folder_id - ) - - def get_rest_folders( - self, project_name: str, include_attrib: bool = False - ) -> List["FlatFolderDict"]: - """Get simplified flat list of all project folders. - - Get all project folders in single REST call. This can be faster than - using 'get_folders' method which is using GraphQl, but does not - allow any filtering, and set of fields is defined - by server backend. - - Example:: - - [ - { - "id": "112233445566", - "parentId": "112233445567", - "path": "/root/parent/child", - "parents": ["root", "parent"], - "name": "child", - "label": "Child", - "folderType": "Folder", - "hasTasks": False, - "hasChildren": False, - "taskNames": [ - "Compositing", - ], - "status": "In Progress", - "attrib": {}, - "ownAttrib": [], - "updatedAt": "2023-06-12T15:37:02.420260", - }, - ... - ] - - Args: - project_name (str): Project name. - include_attrib (Optional[bool]): Include attribute values - in output. Slower to query. - - Returns: - List[FlatFolderDict]: List of folder entities. - - """ - major, minor, patch, _, _ = self.server_version_tuple - if (major, minor, patch) < (1, 0, 8): - raise UnsupportedServerVersion( - "Function 'get_folders_rest' is supported" - " for AYON server 1.0.8 and above." - ) - query = prepare_query_string({ - "attrib": "true" if include_attrib else "false" - }) - response = self.get( - f"projects/{project_name}/folders{query}" - ) - response.raise_for_status() - return response.data["folders"] - def get_rest_task( self, project_name: str, task_id: str ) -> Optional["TaskDict"]: @@ -4287,553 +4198,6 @@ def get_rest_representation( project_name, "representation", representation_id ) - def get_folders_hierarchy( - self, - project_name: str, - search_string: Optional[str] = None, - folder_types: Optional[Iterable[str]] = None - ) -> "ProjectHierarchyDict": - """Get project hierarchy. - - All folders in project in hierarchy data structure. - - Example output: - { - "hierarchy": [ - { - "id": "...", - "name": "...", - "label": "...", - "status": "...", - "folderType": "...", - "hasTasks": False, - "taskNames": [], - "parents": [], - "parentId": None, - "children": [...children folders...] - }, - ... - ] - } - - Args: - project_name (str): Project where to look for folders. - search_string (Optional[str]): Search string to filter folders. - folder_types (Optional[Iterable[str]]): Folder types to filter. - - Returns: - dict[str, Any]: Response data from server. - - """ - if folder_types: - folder_types = ",".join(folder_types) - - query = prepare_query_string({ - "search": search_string or None, - "types": folder_types or None, - }) - response = self.get( - f"projects/{project_name}/hierarchy{query}" - ) - response.raise_for_status() - return response.data - - def get_folders_rest( - self, project_name: str, include_attrib: bool = False - ) -> List["FlatFolderDict"]: - """Get simplified flat list of all project folders. - - Get all project folders in single REST call. This can be faster than - using 'get_folders' method which is using GraphQl, but does not - allow any filtering, and set of fields is defined - by server backend. - - Example:: - - [ - { - "id": "112233445566", - "parentId": "112233445567", - "path": "/root/parent/child", - "parents": ["root", "parent"], - "name": "child", - "label": "Child", - "folderType": "Folder", - "hasTasks": False, - "hasChildren": False, - "taskNames": [ - "Compositing", - ], - "status": "In Progress", - "attrib": {}, - "ownAttrib": [], - "updatedAt": "2023-06-12T15:37:02.420260", - }, - ... - ] - - Deprecated: - Use 'get_rest_folders' instead. Function was renamed to match - other rest functions, like 'get_rest_folder', - 'get_rest_project' etc. . - Will be removed in '1.0.7' or '1.1.0'. - - Args: - project_name (str): Project name. - include_attrib (Optional[bool]): Include attribute values - in output. Slower to query. - - Returns: - List[FlatFolderDict]: List of folder entities. - - """ - warnings.warn( - ( - "DEPRECATION: Used deprecated 'get_folders_rest'," - " use 'get_rest_folders' instead." - ), - DeprecationWarning - ) - return self.get_rest_folders(project_name, include_attrib) - - def get_folders( - self, - project_name: str, - folder_ids: Optional[Iterable[str]] = None, - folder_paths: Optional[Iterable[str]] = None, - folder_names: Optional[Iterable[str]] = None, - folder_types: Optional[Iterable[str]] = None, - parent_ids: Optional[Iterable[str]] = None, - folder_path_regex: Optional[str] = None, - has_products: Optional[bool] = None, - has_tasks: Optional[bool] = None, - has_children: Optional[bool] = None, - statuses: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - has_links: Optional[bool] = None, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False - ) -> Generator["FolderDict", None, None]: - """Query folders from server. - - Todos: - Folder name won't be unique identifier, so we should add - folder path filtering. - - Notes: - Filter 'active' don't have direct filter in GraphQl. - - Args: - project_name (str): Name of project. - folder_ids (Optional[Iterable[str]]): Folder ids to filter. - folder_paths (Optional[Iterable[str]]): Folder paths used - for filtering. - folder_names (Optional[Iterable[str]]): Folder names used - for filtering. - folder_types (Optional[Iterable[str]]): Folder types used - for filtering. - parent_ids (Optional[Iterable[str]]): Ids of folder parents. - Use 'None' if folder is direct child of project. - folder_path_regex (Optional[str]): Folder path regex used - for filtering. - has_products (Optional[bool]): Filter folders with/without - products. Ignored when None, default behavior. - has_tasks (Optional[bool]): Filter folders with/without - tasks. Ignored when None, default behavior. - has_children (Optional[bool]): Filter folders with/without - children. Ignored when None, default behavior. - statuses (Optional[Iterable[str]]): Folder statuses used - for filtering. - assignees_all (Optional[Iterable[str]]): Filter by assigness - on children tasks. Task must have all of passed assignees. - tags (Optional[Iterable[str]]): Folder tags used - for filtering. - active (Optional[bool]): Filter active/inactive folders. - Both are returned if is set to None. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Generator[FolderDict, None, None]: Queried folder entities. - - """ - if not project_name: - return - - filters = { - "projectName": project_name - } - if not _prepare_list_filters( - filters, - ("folderIds", folder_ids), - ("folderPaths", folder_paths), - ("folderNames", folder_names), - ("folderTypes", folder_types), - ("folderStatuses", statuses), - ("folderTags", tags), - ("folderAssigneesAll", assignees_all), - ): - return - - for filter_key, filter_value in ( - ("folderPathRegex", folder_path_regex), - ("folderHasProducts", has_products), - ("folderHasTasks", has_tasks), - ("folderHasLinks", has_links), - ("folderHasChildren", has_children), - ): - if filter_value is not None: - filters[filter_key] = filter_value - - if parent_ids is not None: - parent_ids = set(parent_ids) - if not parent_ids: - return - if None in parent_ids: - # Replace 'None' with '"root"' which is used during GraphQl - # query for parent ids filter for folders without folder - # parent - parent_ids.remove(None) - parent_ids.add("root") - - if project_name in parent_ids: - # Replace project name with '"root"' which is used during - # GraphQl query for parent ids filter for folders without - # folder parent - parent_ids.remove(project_name) - parent_ids.add("root") - - filters["parentFolderIds"] = list(parent_ids) - - if not fields: - fields = self.get_default_fields_for_type("folder") - else: - fields = set(fields) - self._prepare_fields("folder", fields) - - if active is not None: - fields.add("active") - - if own_attributes: - fields.add("ownAttrib") - - query = folders_graphql_query(fields) - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - for parsed_data in query.continuous_query(self): - for folder in parsed_data["project"]["folders"]: - if active is not None and active is not folder["active"]: - continue - - self._convert_entity_data(folder) - - if own_attributes: - fill_own_attribs(folder) - yield folder - - def get_folder_by_id( - self, - project_name: str, - folder_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, - ) -> Optional["FolderDict"]: - """Query folder entity by id. - - Args: - project_name (str): Name of project where to look for queried - entities. - folder_id (str): Folder id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. - - """ - folders = self.get_folders( - project_name, - folder_ids=[folder_id], - active=None, - fields=fields, - own_attributes=own_attributes - ) - for folder in folders: - return folder - return None - - def get_folder_by_path( - self, - project_name: str, - folder_path: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, - ) -> Optional["FolderDict"]: - """Query folder entity by path. - - Folder path is a path to folder with all parent names joined by slash. - - Args: - project_name (str): Name of project where to look for queried - entities. - folder_path (str): Folder path. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. - - """ - folders = self.get_folders( - project_name, - folder_paths=[folder_path], - active=None, - fields=fields, - own_attributes=own_attributes - ) - for folder in folders: - return folder - return None - - def get_folder_by_name( - self, - project_name: str, - folder_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, - ) -> Optional["FolderDict"]: - """Query folder entity by path. - - Warnings: - Folder name is not a unique identifier of a folder. Function is - kept for OpenPype 3 compatibility. - - Args: - project_name (str): Name of project where to look for queried - entities. - folder_name (str): Folder name. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. - - """ - folders = self.get_folders( - project_name, - folder_names=[folder_name], - active=None, - fields=fields, - own_attributes=own_attributes - ) - for folder in folders: - return folder - return None - - def get_folder_ids_with_products( - self, project_name: str, folder_ids: Optional[Iterable[str]] = None - ) -> Set[str]: - """Find folders which have at least one product. - - Folders that have at least one product should be immutable, so they - should not change path -> change of name or name of any parent - is not possible. - - Args: - project_name (str): Name of project. - folder_ids (Optional[Iterable[str]]): Limit folder ids filtering - to a set of folders. If set to None all folders on project are - checked. - - Returns: - set[str]: Folder ids that have at least one product. - - """ - if folder_ids is not None: - folder_ids = set(folder_ids) - if not folder_ids: - return set() - - query = folders_graphql_query({"id"}) - query.set_variable_value("projectName", project_name) - query.set_variable_value("folderHasProducts", True) - if folder_ids: - query.set_variable_value("folderIds", list(folder_ids)) - - parsed_data = query.query(self) - folders = parsed_data["project"]["folders"] - return { - folder["id"] - for folder in folders - } - - def create_folder( - self, - project_name: str, - name: str, - folder_type: Optional[str] = None, - parent_id: Optional[str] = None, - label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - folder_id: Optional[str] = None, - ) -> str: - """Create new folder. - - Args: - project_name (str): Project name. - name (str): Folder name. - folder_type (Optional[str]): Folder type. - parent_id (Optional[str]): Parent folder id. Parent is project - if is ``None``. - label (Optional[str]): Label of folder. - attrib (Optional[dict[str, Any]]): Folder attributes. - data (Optional[dict[str, Any]]): Folder data. - tags (Optional[Iterable[str]]): Folder tags. - status (Optional[str]): Folder status. - active (Optional[bool]): Folder active state. - thumbnail_id (Optional[str]): Folder thumbnail id. - folder_id (Optional[str]): Folder id. If not passed new id is - generated. - - Returns: - str: Entity id. - - """ - if not folder_id: - folder_id = create_entity_id() - create_data = { - "id": folder_id, - "name": name, - } - for key, value in ( - ("folderType", folder_type), - ("parentId", parent_id), - ("label", label), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ("thumbnailId", thumbnail_id), - ): - if value is not None: - create_data[key] = value - - response = self.post( - f"projects/{project_name}/folders", - **create_data - ) - response.raise_for_status() - return folder_id - - def update_folder( - self, - project_name: str, - folder_id: str, - name: Optional[str] = None, - folder_type: Optional[str] = None, - parent_id: Optional[str] = NOT_SET, - label: Optional[str] = NOT_SET, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, - ): - """Update folder entity on server. - - Do not pass ``parent_id``, ``label`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. - - Args: - project_name (str): Project name. - folder_id (str): Folder id. - name (Optional[str]): New name. - folder_type (Optional[str]): New folder type. - parent_id (Optional[Union[str, None]]): New parent folder id. - label (Optional[Union[str, None]]): New label. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - thumbnail_id (Optional[Union[str, None]]): New thumbnail id. - - """ - update_data = {} - for key, value in ( - ("name", name), - ("folderType", folder_type), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ): - if value is not None: - update_data[key] = value - - for key, value in ( - ("label", label), - ("parentId", parent_id), - ("thumbnailId", thumbnail_id), - ): - if value is not NOT_SET: - update_data[key] = value - - response = self.patch( - f"projects/{project_name}/folders/{folder_id}", - **update_data - ) - response.raise_for_status() - - def delete_folder( - self, project_name: str, folder_id: str, force: bool = False - ): - """Delete folder. - - Args: - project_name (str): Project name. - folder_id (str): Folder id to delete. - force (Optional[bool]): Folder delete folder with all children - folder, products, versions and representations. - - """ - url = f"projects/{project_name}/folders/{folder_id}" - if force: - url += "?force=true" - response = self.delete(url) - response.raise_for_status() - def get_tasks( self, project_name: str, From f02e25cd8f8179ac464c766cc92976658197f8e6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:05:03 +0200 Subject: [PATCH 19/53] moved events api to separate file --- automated_api.py | 2 + ayon_api/__init__.py | 24 +- ayon_api/_api.py | 790 ++++++++++++++++++++--------------------- ayon_api/_events.py | 381 ++++++++++++++++++++ ayon_api/server_api.py | 648 +-------------------------------- 5 files changed, 792 insertions(+), 1053 deletions(-) create mode 100644 ayon_api/_events.py diff --git a/automated_api.py b/automated_api.py index 64ec6bd92..f630ed123 100644 --- a/automated_api.py +++ b/automated_api.py @@ -337,6 +337,7 @@ def prepare_api_functions(api_globals): ServerAPI, _ActionsAPI, _AddonsAPI, + _EventsAPI, _FoldersAPI, _LinksAPI, _ListsAPI, @@ -347,6 +348,7 @@ def prepare_api_functions(api_globals): _items = list(ServerAPI.__dict__.items()) _items.extend(_ActionsAPI.__dict__.items()) _items.extend(_AddonsAPI.__dict__.items()) + _items.extend(_EventsAPI.__dict__.items()) _items.extend(_FoldersAPI.__dict__.items()) _items.extend(_LinksAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 11bf7df5b..66379e097 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -67,12 +67,6 @@ patch, get, delete, - get_event, - get_events, - update_event, - dispatch_event, - delete_event, - enroll_event_job, get_activities, get_activity_by_id, create_activity, @@ -210,6 +204,12 @@ delete_addon_version, upload_addon_zip, download_addon_private_file, + get_event, + get_events, + update_event, + dispatch_event, + delete_event, + enroll_event_job, get_rest_folder, get_rest_folders, get_folders_hierarchy, @@ -331,12 +331,6 @@ "patch", "get", "delete", - "get_event", - "get_events", - "update_event", - "dispatch_event", - "delete_event", - "enroll_event_job", "get_activities", "get_activity_by_id", "create_activity", @@ -474,6 +468,12 @@ "delete_addon_version", "upload_addon_zip", "download_addon_private_file", + "get_event", + "get_events", + "update_event", + "dispatch_event", + "delete_event", + "enroll_event_job", "get_rest_folder", "get_rest_folders", "get_folders_hierarchy", diff --git a/ayon_api/_api.py b/ayon_api/_api.py index db11667a1..d6a6f8ba7 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -904,444 +904,155 @@ def delete( ) -def get_event( - event_id: str, -) -> Optional[Dict[str, Any]]: - """Query full event data by id. - - Events received using event server do not contain full information. To - get the full event information is required to receive it explicitly. - - Args: - event_id (str): Event id. - - Returns: - dict[str, Any]: Full event data. - - """ - con = get_server_api_connection() - return con.get_event( - event_id=event_id, - ) - - -def get_events( - topics: Optional[Iterable[str]] = None, - event_ids: Optional[Iterable[str]] = None, - project_names: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - users: Optional[Iterable[str]] = None, - include_logs: Optional[bool] = None, - has_children: Optional[bool] = None, - newer_than: Optional[str] = None, - older_than: Optional[str] = None, +def get_activities( + project_name: str, + activity_ids: Optional[Iterable[str]] = None, + activity_types: Optional[Iterable["ActivityType"]] = None, + entity_ids: Optional[Iterable[str]] = None, + entity_names: Optional[Iterable[str]] = None, + entity_type: Optional[str] = None, + changed_after: Optional[str] = None, + changed_before: Optional[str] = None, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, fields: Optional[Iterable[str]] = None, limit: Optional[int] = None, order: Optional[SortOrder] = None, - states: Optional[Iterable[str]] = None, ) -> Generator[Dict[str, Any], None, None]: - """Get events from server with filtering options. - - Notes: - Not all event happen on a project. + """Get activities from server with filtering options. Args: - topics (Optional[Iterable[str]]): Name of topics. - event_ids (Optional[Iterable[str]]): Event ids. - project_names (Optional[Iterable[str]]): Project on which - event happened. - statuses (Optional[Iterable[str]]): Filtering by statuses. - users (Optional[Iterable[str]]): Filtering by users - who created/triggered an event. - include_logs (Optional[bool]): Query also log events. - has_children (Optional[bool]): Event is with/without children - events. If 'None' then all events are returned, default. - newer_than (Optional[str]): Return only events newer than given - iso datetime string. - older_than (Optional[str]): Return only events older than given - iso datetime string. + project_name (str): Project on which activities happened. + activity_ids (Optional[Iterable[str]]): Activity ids. + activity_types (Optional[Iterable[ActivityType]]): Activity types. + entity_ids (Optional[Iterable[str]]): Entity ids. + entity_names (Optional[Iterable[str]]): Entity names. + entity_type (Optional[str]): Entity type. + changed_after (Optional[str]): Return only activities changed + after given iso datetime string. + changed_before (Optional[str]): Return only activities changed + before given iso datetime string. + reference_types (Optional[Iterable[ActivityReferenceType]]): + Reference types filter. Defaults to `['origin']`. fields (Optional[Iterable[str]]): Fields that should be received - for each event. - limit (Optional[int]): Limit number of events to be fetched. - order (Optional[SortOrder]): Order events in ascending + for each activity. + limit (Optional[int]): Limit number of activities to be fetched. + order (Optional[SortOrder]): Order activities in ascending or descending order. It is recommended to set 'limit' when used descending. - states (Optional[Iterable[str]]): DEPRECATED Filtering by states. - Use 'statuses' instead. Returns: - Generator[dict[str, Any]]: Available events matching filters. + Generator[dict[str, Any]]: Available activities matching filters. """ con = get_server_api_connection() - return con.get_events( - topics=topics, - event_ids=event_ids, - project_names=project_names, - statuses=statuses, - users=users, - include_logs=include_logs, - has_children=has_children, - newer_than=newer_than, - older_than=older_than, + return con.get_activities( + project_name=project_name, + activity_ids=activity_ids, + activity_types=activity_types, + entity_ids=entity_ids, + entity_names=entity_names, + entity_type=entity_type, + changed_after=changed_after, + changed_before=changed_before, + reference_types=reference_types, fields=fields, limit=limit, order=order, - states=states, ) -def update_event( - event_id: str, - sender: Optional[str] = None, - project_name: Optional[str] = None, - username: Optional[str] = None, - status: Optional[str] = None, - description: Optional[str] = None, - summary: Optional[Dict[str, Any]] = None, - payload: Optional[Dict[str, Any]] = None, - progress: Optional[int] = None, - retries: Optional[int] = None, -): - """Update event data. +def get_activity_by_id( + project_name: str, + activity_id: str, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + fields: Optional[Iterable[str]] = None, +) -> Optional[Dict[str, Any]]: + """Get activity by id. Args: - event_id (str): Event id. - sender (Optional[str]): New sender of event. - project_name (Optional[str]): New project name. - username (Optional[str]): New username. - status (Optional[str]): New event status. Enum: "pending", - "in_progress", "finished", "failed", "aborted", "restarted" - description (Optional[str]): New description. - summary (Optional[dict[str, Any]]): New summary. - payload (Optional[dict[str, Any]]): New payload. - progress (Optional[int]): New progress. Range [0-100]. - retries (Optional[int]): New retries. + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + reference_types: Optional[Iterable[ActivityReferenceType]]: Filter + by reference types. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. + + Returns: + Optional[Dict[str, Any]]: Activity data or None if activity is not + found. """ con = get_server_api_connection() - return con.update_event( - event_id=event_id, - sender=sender, + return con.get_activity_by_id( project_name=project_name, - username=username, - status=status, - description=description, - summary=summary, - payload=payload, - progress=progress, - retries=retries, + activity_id=activity_id, + reference_types=reference_types, + fields=fields, ) -def dispatch_event( - topic: str, - sender: Optional[str] = None, - event_hash: Optional[str] = None, - project_name: Optional[str] = None, - username: Optional[str] = None, - depends_on: Optional[str] = None, - description: Optional[str] = None, - summary: Optional[Dict[str, Any]] = None, - payload: Optional[Dict[str, Any]] = None, - finished: bool = True, - store: bool = True, - dependencies: Optional[List[str]] = None, -): - """Dispatch event to server. +def create_activity( + project_name: str, + entity_id: str, + entity_type: str, + activity_type: "ActivityType", + activity_id: Optional[str] = None, + body: Optional[str] = None, + file_ids: Optional[List[str]] = None, + timestamp: Optional[str] = None, + data: Optional[Dict[str, Any]] = None, +) -> str: + """Create activity on a project. Args: - topic (str): Event topic used for filtering of listeners. - sender (Optional[str]): Sender of event. - event_hash (Optional[str]): Event hash. - project_name (Optional[str]): Project name. - depends_on (Optional[str]): Add dependency to another event. - username (Optional[str]): Username which triggered event. - description (Optional[str]): Description of event. - summary (Optional[dict[str, Any]]): Summary of event that can - be used for simple filtering on listeners. - payload (Optional[dict[str, Any]]): Full payload of event data with - all details. - finished (Optional[bool]): Mark event as finished on dispatch. - store (Optional[bool]): Store event in event queue for possible - future processing otherwise is event send only - to active listeners. - dependencies (Optional[list[str]]): Deprecated. - List of event id dependencies. + project_name (str): Project on which activity happened. + entity_id (str): Entity id. + entity_type (str): Entity type. + activity_type (ActivityType): Activity type. + activity_id (Optional[str]): Activity id. + body (Optional[str]): Activity body. + file_ids (Optional[List[str]]): List of file ids attached + to activity. + timestamp (Optional[str]): Activity timestamp. + data (Optional[Dict[str, Any]]): Additional data. Returns: - RestApiResponse: Response from server. + str: Activity id. """ con = get_server_api_connection() - return con.dispatch_event( - topic=topic, - sender=sender, - event_hash=event_hash, + return con.create_activity( project_name=project_name, - username=username, - depends_on=depends_on, - description=description, - summary=summary, - payload=payload, - finished=finished, - store=store, - dependencies=dependencies, + entity_id=entity_id, + entity_type=entity_type, + activity_type=activity_type, + activity_id=activity_id, + body=body, + file_ids=file_ids, + timestamp=timestamp, + data=data, ) -def delete_event( - event_id: str, +def update_activity( + project_name: str, + activity_id: str, + body: Optional[str] = None, + file_ids: Optional[List[str]] = None, + append_file_ids: Optional[bool] = False, + data: Optional[Dict[str, Any]] = None, ): - """Delete event by id. - - Supported since AYON server 1.6.0. + """Update activity by id. Args: - event_id (str): Event id. - - Returns: - RestApiResponse: Response from server. - - """ - con = get_server_api_connection() - return con.delete_event( - event_id=event_id, - ) - - -def enroll_event_job( - source_topic: "Union[str, List[str]]", - target_topic: str, - sender: str, - description: Optional[str] = None, - sequential: Optional[bool] = None, - events_filter: Optional["EventFilter"] = None, - max_retries: Optional[int] = None, - ignore_older_than: Optional[str] = None, - ignore_sender_types: Optional[str] = None, -): - """Enroll job based on events. - - Enroll will find first unprocessed event with 'source_topic' and will - create new event with 'target_topic' for it and return the new event - data. - - Use 'sequential' to control that only single target event is created - at same time. Creation of new target events is blocked while there is - at least one unfinished event with target topic, when set to 'True'. - This helps when order of events matter and more than one process using - the same target is running at the same time. - - Make sure the new event has updated status to '"finished"' status - when you're done with logic - - Target topic should not clash with other processes/services. - - Created target event have 'dependsOn' key where is id of source topic. - - Use-case: - - Service 1 is creating events with topic 'my.leech' - - Service 2 process 'my.leech' and uses target topic 'my.process' - - this service can run on 1-n machines - - all events must be processed in a sequence by their creation - time and only one event can be processed at a time - - in this case 'sequential' should be set to 'True' so only - one machine is actually processing events, but if one goes - down there are other that can take place - - Service 3 process 'my.leech' and uses target topic 'my.discover' - - this service can run on 1-n machines - - order of events is not important - - 'sequential' should be 'False' - - Args: - source_topic (Union[str, List[str]]): Source topic to enroll with - wildcards '*', or explicit list of topics. - target_topic (str): Topic of dependent event. - sender (str): Identifier of sender (e.g. service name or username). - description (Optional[str]): Human readable text shown - in target event. - sequential (Optional[bool]): The source topic must be processed - in sequence. - events_filter (Optional[dict[str, Any]]): Filtering conditions - to filter the source event. For more technical specifications - look to server backed 'ayon_server.sqlfilter.Filter'. - TODO: Add example of filters. - max_retries (Optional[int]): How many times can be event retried. - Default value is based on server (3 at the time of this PR). - ignore_older_than (Optional[int]): Ignore events older than - given number in days. - ignore_sender_types (Optional[List[str]]): Ignore events triggered - by given sender types. - - Returns: - Union[None, dict[str, Any]]: None if there is no event matching - filters. Created event with 'target_topic'. - - """ - con = get_server_api_connection() - return con.enroll_event_job( - source_topic=source_topic, - target_topic=target_topic, - sender=sender, - description=description, - sequential=sequential, - events_filter=events_filter, - max_retries=max_retries, - ignore_older_than=ignore_older_than, - ignore_sender_types=ignore_sender_types, - ) - - -def get_activities( - project_name: str, - activity_ids: Optional[Iterable[str]] = None, - activity_types: Optional[Iterable["ActivityType"]] = None, - entity_ids: Optional[Iterable[str]] = None, - entity_names: Optional[Iterable[str]] = None, - entity_type: Optional[str] = None, - changed_after: Optional[str] = None, - changed_before: Optional[str] = None, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, - limit: Optional[int] = None, - order: Optional[SortOrder] = None, -) -> Generator[Dict[str, Any], None, None]: - """Get activities from server with filtering options. - - Args: - project_name (str): Project on which activities happened. - activity_ids (Optional[Iterable[str]]): Activity ids. - activity_types (Optional[Iterable[ActivityType]]): Activity types. - entity_ids (Optional[Iterable[str]]): Entity ids. - entity_names (Optional[Iterable[str]]): Entity names. - entity_type (Optional[str]): Entity type. - changed_after (Optional[str]): Return only activities changed - after given iso datetime string. - changed_before (Optional[str]): Return only activities changed - before given iso datetime string. - reference_types (Optional[Iterable[ActivityReferenceType]]): - Reference types filter. Defaults to `['origin']`. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. - limit (Optional[int]): Limit number of activities to be fetched. - order (Optional[SortOrder]): Order activities in ascending - or descending order. It is recommended to set 'limit' - when used descending. - - Returns: - Generator[dict[str, Any]]: Available activities matching filters. - - """ - con = get_server_api_connection() - return con.get_activities( - project_name=project_name, - activity_ids=activity_ids, - activity_types=activity_types, - entity_ids=entity_ids, - entity_names=entity_names, - entity_type=entity_type, - changed_after=changed_after, - changed_before=changed_before, - reference_types=reference_types, - fields=fields, - limit=limit, - order=order, - ) - - -def get_activity_by_id( - project_name: str, - activity_id: str, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, -) -> Optional[Dict[str, Any]]: - """Get activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - reference_types: Optional[Iterable[ActivityReferenceType]]: Filter - by reference types. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. - - Returns: - Optional[Dict[str, Any]]: Activity data or None if activity is not - found. - - """ - con = get_server_api_connection() - return con.get_activity_by_id( - project_name=project_name, - activity_id=activity_id, - reference_types=reference_types, - fields=fields, - ) - - -def create_activity( - project_name: str, - entity_id: str, - entity_type: str, - activity_type: "ActivityType", - activity_id: Optional[str] = None, - body: Optional[str] = None, - file_ids: Optional[List[str]] = None, - timestamp: Optional[str] = None, - data: Optional[Dict[str, Any]] = None, -) -> str: - """Create activity on a project. - - Args: - project_name (str): Project on which activity happened. - entity_id (str): Entity id. - entity_type (str): Entity type. - activity_type (ActivityType): Activity type. - activity_id (Optional[str]): Activity id. - body (Optional[str]): Activity body. - file_ids (Optional[List[str]]): List of file ids attached - to activity. - timestamp (Optional[str]): Activity timestamp. - data (Optional[Dict[str, Any]]): Additional data. - - Returns: - str: Activity id. - - """ - con = get_server_api_connection() - return con.create_activity( - project_name=project_name, - entity_id=entity_id, - entity_type=entity_type, - activity_type=activity_type, - activity_id=activity_id, - body=body, - file_ids=file_ids, - timestamp=timestamp, - data=data, - ) - - -def update_activity( - project_name: str, - activity_id: str, - body: Optional[str] = None, - file_ids: Optional[List[str]] = None, - append_file_ids: Optional[bool] = False, - data: Optional[Dict[str, Any]] = None, -): - """Update activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - body (str): Activity body. - file_ids (Optional[List[str]]): List of file ids attached - to activity. - append_file_ids (Optional[bool]): Append file ids to existing - list of file ids. - data (Optional[Dict[str, Any]]): Update data in activity. + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + body (str): Activity body. + file_ids (Optional[List[str]]): List of file ids attached + to activity. + append_file_ids (Optional[bool]): Append file ids to existing + list of file ids. + data (Optional[Dict[str, Any]]): Update data in activity. """ con = get_server_api_connection() @@ -5608,6 +5319,295 @@ def download_addon_private_file( ) +def get_event( + event_id: str, +) -> Optional[dict[str, Any]]: + """Query full event data by id. + + Events received using event server do not contain full information. To + get the full event information is required to receive it explicitly. + + Args: + event_id (str): Event id. + + Returns: + dict[str, Any]: Full event data. + + """ + con = get_server_api_connection() + return con.get_event( + event_id=event_id, + ) + + +def get_events( + topics: Optional[Iterable[str]] = None, + event_ids: Optional[Iterable[str]] = None, + project_names: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + users: Optional[Iterable[str]] = None, + include_logs: Optional[bool] = None, + has_children: Optional[bool] = None, + newer_than: Optional[str] = None, + older_than: Optional[str] = None, + fields: Optional[Iterable[str]] = None, + limit: Optional[int] = None, + order: Optional[SortOrder] = None, + states: Optional[Iterable[str]] = None, +) -> Generator[dict[str, Any], None, None]: + """Get events from server with filtering options. + + Notes: + Not all event happen on a project. + + Args: + topics (Optional[Iterable[str]]): Name of topics. + event_ids (Optional[Iterable[str]]): Event ids. + project_names (Optional[Iterable[str]]): Project on which + event happened. + statuses (Optional[Iterable[str]]): Filtering by statuses. + users (Optional[Iterable[str]]): Filtering by users + who created/triggered an event. + include_logs (Optional[bool]): Query also log events. + has_children (Optional[bool]): Event is with/without children + events. If 'None' then all events are returned, default. + newer_than (Optional[str]): Return only events newer than given + iso datetime string. + older_than (Optional[str]): Return only events older than given + iso datetime string. + fields (Optional[Iterable[str]]): Fields that should be received + for each event. + limit (Optional[int]): Limit number of events to be fetched. + order (Optional[SortOrder]): Order events in ascending + or descending order. It is recommended to set 'limit' + when used descending. + states (Optional[Iterable[str]]): DEPRECATED Filtering by states. + Use 'statuses' instead. + + Returns: + Generator[dict[str, Any]]: Available events matching filters. + + """ + con = get_server_api_connection() + return con.get_events( + topics=topics, + event_ids=event_ids, + project_names=project_names, + statuses=statuses, + users=users, + include_logs=include_logs, + has_children=has_children, + newer_than=newer_than, + older_than=older_than, + fields=fields, + limit=limit, + order=order, + states=states, + ) + + +def update_event( + event_id: str, + sender: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + status: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + progress: Optional[int] = None, + retries: Optional[int] = None, +): + """Update event data. + + Args: + event_id (str): Event id. + sender (Optional[str]): New sender of event. + project_name (Optional[str]): New project name. + username (Optional[str]): New username. + status (Optional[str]): New event status. Enum: "pending", + "in_progress", "finished", "failed", "aborted", "restarted" + description (Optional[str]): New description. + summary (Optional[dict[str, Any]]): New summary. + payload (Optional[dict[str, Any]]): New payload. + progress (Optional[int]): New progress. Range [0-100]. + retries (Optional[int]): New retries. + + """ + con = get_server_api_connection() + return con.update_event( + event_id=event_id, + sender=sender, + project_name=project_name, + username=username, + status=status, + description=description, + summary=summary, + payload=payload, + progress=progress, + retries=retries, + ) + + +def dispatch_event( + topic: str, + sender: Optional[str] = None, + event_hash: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + depends_on: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + finished: bool = True, + store: bool = True, + dependencies: Optional[list[str]] = None, +): + """Dispatch event to server. + + Args: + topic (str): Event topic used for filtering of listeners. + sender (Optional[str]): Sender of event. + event_hash (Optional[str]): Event hash. + project_name (Optional[str]): Project name. + depends_on (Optional[str]): Add dependency to another event. + username (Optional[str]): Username which triggered event. + description (Optional[str]): Description of event. + summary (Optional[dict[str, Any]]): Summary of event that can + be used for simple filtering on listeners. + payload (Optional[dict[str, Any]]): Full payload of event data with + all details. + finished (Optional[bool]): Mark event as finished on dispatch. + store (Optional[bool]): Store event in event queue for possible + future processing otherwise is event send only + to active listeners. + dependencies (Optional[list[str]]): Deprecated. + List of event id dependencies. + + Returns: + RestApiResponse: Response from server. + + """ + con = get_server_api_connection() + return con.dispatch_event( + topic=topic, + sender=sender, + event_hash=event_hash, + project_name=project_name, + username=username, + depends_on=depends_on, + description=description, + summary=summary, + payload=payload, + finished=finished, + store=store, + dependencies=dependencies, + ) + + +def delete_event( + event_id: str, +): + """Delete event by id. + + Supported since AYON server 1.6.0. + + Args: + event_id (str): Event id. + + Returns: + RestApiResponse: Response from server. + + """ + con = get_server_api_connection() + return con.delete_event( + event_id=event_id, + ) + + +def enroll_event_job( + source_topic: "Union[str, list[str]]", + target_topic: str, + sender: str, + description: Optional[str] = None, + sequential: Optional[bool] = None, + events_filter: Optional["EventFilter"] = None, + max_retries: Optional[int] = None, + ignore_older_than: Optional[str] = None, + ignore_sender_types: Optional[str] = None, +): + """Enroll job based on events. + + Enroll will find first unprocessed event with 'source_topic' and will + create new event with 'target_topic' for it and return the new event + data. + + Use 'sequential' to control that only single target event is created + at same time. Creation of new target events is blocked while there is + at least one unfinished event with target topic, when set to 'True'. + This helps when order of events matter and more than one process using + the same target is running at the same time. + + Make sure the new event has updated status to '"finished"' status + when you're done with logic + + Target topic should not clash with other processes/services. + + Created target event have 'dependsOn' key where is id of source topic. + + Use-case: + - Service 1 is creating events with topic 'my.leech' + - Service 2 process 'my.leech' and uses target topic 'my.process' + - this service can run on 1-n machines + - all events must be processed in a sequence by their creation + time and only one event can be processed at a time + - in this case 'sequential' should be set to 'True' so only + one machine is actually processing events, but if one goes + down there are other that can take place + - Service 3 process 'my.leech' and uses target topic 'my.discover' + - this service can run on 1-n machines + - order of events is not important + - 'sequential' should be 'False' + + Args: + source_topic (Union[str, list[str]]): Source topic to enroll with + wildcards '*', or explicit list of topics. + target_topic (str): Topic of dependent event. + sender (str): Identifier of sender (e.g. service name or username). + description (Optional[str]): Human readable text shown + in target event. + sequential (Optional[bool]): The source topic must be processed + in sequence. + events_filter (Optional[dict[str, Any]]): Filtering conditions + to filter the source event. For more technical specifications + look to server backed 'ayon_server.sqlfilter.Filter'. + TODO: Add example of filters. + max_retries (Optional[int]): How many times can be event retried. + Default value is based on server (3 at the time of this PR). + ignore_older_than (Optional[int]): Ignore events older than + given number in days. + ignore_sender_types (Optional[list[str]]): Ignore events triggered + by given sender types. + + Returns: + Optional[dict[str, Any]]: None if there is no event matching + filters. Created event with 'target_topic'. + + """ + con = get_server_api_connection() + return con.enroll_event_job( + source_topic=source_topic, + target_topic=target_topic, + sender=sender, + description=description, + sequential=sequential, + events_filter=events_filter, + max_retries=max_retries, + ignore_older_than=ignore_older_than, + ignore_sender_types=ignore_sender_types, + ) + + def get_rest_folder( project_name: str, folder_id: str, diff --git a/ayon_api/_events.py b/ayon_api/_events.py new file mode 100644 index 000000000..d91aebb07 --- /dev/null +++ b/ayon_api/_events.py @@ -0,0 +1,381 @@ +import warnings +import typing +from typing import Optional, Any, Iterable, Generator + +from ._base import _BaseServerAPI +from .utils import SortOrder, prepare_list_filters +from .graphql_queries import events_graphql_query + +if typing.TYPE_CHECKING: + from typing import Union + from .typing import EventFilter + + +class _EventsAPI(_BaseServerAPI): + def get_event(self, event_id: str) -> Optional[dict[str, Any]]: + """Query full event data by id. + + Events received using event server do not contain full information. To + get the full event information is required to receive it explicitly. + + Args: + event_id (str): Event id. + + Returns: + dict[str, Any]: Full event data. + + """ + response = self.get(f"events/{event_id}") + response.raise_for_status() + return response.data + + def get_events( + self, + topics: Optional[Iterable[str]] = None, + event_ids: Optional[Iterable[str]] = None, + project_names: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + users: Optional[Iterable[str]] = None, + include_logs: Optional[bool] = None, + has_children: Optional[bool] = None, + newer_than: Optional[str] = None, + older_than: Optional[str] = None, + fields: Optional[Iterable[str]] = None, + limit: Optional[int] = None, + order: Optional[SortOrder] = None, + states: Optional[Iterable[str]] = None, + ) -> Generator[dict[str, Any], None, None]: + """Get events from server with filtering options. + + Notes: + Not all event happen on a project. + + Args: + topics (Optional[Iterable[str]]): Name of topics. + event_ids (Optional[Iterable[str]]): Event ids. + project_names (Optional[Iterable[str]]): Project on which + event happened. + statuses (Optional[Iterable[str]]): Filtering by statuses. + users (Optional[Iterable[str]]): Filtering by users + who created/triggered an event. + include_logs (Optional[bool]): Query also log events. + has_children (Optional[bool]): Event is with/without children + events. If 'None' then all events are returned, default. + newer_than (Optional[str]): Return only events newer than given + iso datetime string. + older_than (Optional[str]): Return only events older than given + iso datetime string. + fields (Optional[Iterable[str]]): Fields that should be received + for each event. + limit (Optional[int]): Limit number of events to be fetched. + order (Optional[SortOrder]): Order events in ascending + or descending order. It is recommended to set 'limit' + when used descending. + states (Optional[Iterable[str]]): DEPRECATED Filtering by states. + Use 'statuses' instead. + + Returns: + Generator[dict[str, Any]]: Available events matching filters. + + """ + if statuses is None and states is not None: + warnings.warn( + ( + "Used deprecated argument 'states' in 'get_events'." + " Use 'statuses' instead." + ), + DeprecationWarning + ) + statuses = states + + filters = {} + if not prepare_list_filters( + filters, + ("eventTopics", topics), + ("eventIds", event_ids), + ("projectNames", project_names), + ("eventStatuses", statuses), + ("eventUsers", users), + ): + return + + if include_logs is None: + include_logs = False + + for filter_key, filter_value in ( + ("includeLogsFilter", include_logs), + ("hasChildrenFilter", has_children), + ("newerThanFilter", newer_than), + ("olderThanFilter", older_than), + ): + if filter_value is not None: + filters[filter_key] = filter_value + + if not fields: + fields = self.get_default_fields_for_type("event") + + major, minor, patch, _, _ = self.server_version_tuple + use_states = (major, minor, patch) <= (1, 5, 6) + + query = events_graphql_query(set(fields), order, use_states) + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + if limit: + events_field = query.get_field_by_path("events") + events_field.set_limit(limit) + + for parsed_data in query.continuous_query(self): + for event in parsed_data["events"]: + yield event + + def update_event( + self, + event_id: str, + sender: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + status: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + progress: Optional[int] = None, + retries: Optional[int] = None, + ): + """Update event data. + + Args: + event_id (str): Event id. + sender (Optional[str]): New sender of event. + project_name (Optional[str]): New project name. + username (Optional[str]): New username. + status (Optional[str]): New event status. Enum: "pending", + "in_progress", "finished", "failed", "aborted", "restarted" + description (Optional[str]): New description. + summary (Optional[dict[str, Any]]): New summary. + payload (Optional[dict[str, Any]]): New payload. + progress (Optional[int]): New progress. Range [0-100]. + retries (Optional[int]): New retries. + + """ + kwargs = { + key: value + for key, value in ( + ("sender", sender), + ("project", project_name), + ("user", username), + ("status", status), + ("description", description), + ("summary", summary), + ("payload", payload), + ("progress", progress), + ("retries", retries), + ) + if value is not None + } + + response = self.patch( + f"events/{event_id}", + **kwargs + ) + response.raise_for_status() + + def dispatch_event( + self, + topic: str, + sender: Optional[str] = None, + event_hash: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + depends_on: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + finished: bool = True, + store: bool = True, + dependencies: Optional[list[str]] = None, + ): + """Dispatch event to server. + + Args: + topic (str): Event topic used for filtering of listeners. + sender (Optional[str]): Sender of event. + event_hash (Optional[str]): Event hash. + project_name (Optional[str]): Project name. + depends_on (Optional[str]): Add dependency to another event. + username (Optional[str]): Username which triggered event. + description (Optional[str]): Description of event. + summary (Optional[dict[str, Any]]): Summary of event that can + be used for simple filtering on listeners. + payload (Optional[dict[str, Any]]): Full payload of event data with + all details. + finished (Optional[bool]): Mark event as finished on dispatch. + store (Optional[bool]): Store event in event queue for possible + future processing otherwise is event send only + to active listeners. + dependencies (Optional[list[str]]): Deprecated. + List of event id dependencies. + + Returns: + RestApiResponse: Response from server. + + """ + if summary is None: + summary = {} + if payload is None: + payload = {} + event_data = { + "topic": topic, + "sender": sender, + "hash": event_hash, + "project": project_name, + "user": username, + "description": description, + "summary": summary, + "payload": payload, + "finished": finished, + "store": store, + } + if depends_on: + event_data["dependsOn"] = depends_on + + if dependencies: + warnings.warn( + ( + "Used deprecated argument 'dependencies' in" + " 'dispatch_event'. Use 'depends_on' instead." + ), + DeprecationWarning + ) + + response = self.post("events", **event_data) + response.raise_for_status() + return response + + def delete_event(self, event_id: str): + """Delete event by id. + + Supported since AYON server 1.6.0. + + Args: + event_id (str): Event id. + + Returns: + RestApiResponse: Response from server. + + """ + response = self.delete(f"events/{event_id}") + response.raise_for_status() + return response + + def enroll_event_job( + self, + source_topic: "Union[str, list[str]]", + target_topic: str, + sender: str, + description: Optional[str] = None, + sequential: Optional[bool] = None, + events_filter: Optional["EventFilter"] = None, + max_retries: Optional[int] = None, + ignore_older_than: Optional[str] = None, + ignore_sender_types: Optional[str] = None, + ): + """Enroll job based on events. + + Enroll will find first unprocessed event with 'source_topic' and will + create new event with 'target_topic' for it and return the new event + data. + + Use 'sequential' to control that only single target event is created + at same time. Creation of new target events is blocked while there is + at least one unfinished event with target topic, when set to 'True'. + This helps when order of events matter and more than one process using + the same target is running at the same time. + + Make sure the new event has updated status to '"finished"' status + when you're done with logic + + Target topic should not clash with other processes/services. + + Created target event have 'dependsOn' key where is id of source topic. + + Use-case: + - Service 1 is creating events with topic 'my.leech' + - Service 2 process 'my.leech' and uses target topic 'my.process' + - this service can run on 1-n machines + - all events must be processed in a sequence by their creation + time and only one event can be processed at a time + - in this case 'sequential' should be set to 'True' so only + one machine is actually processing events, but if one goes + down there are other that can take place + - Service 3 process 'my.leech' and uses target topic 'my.discover' + - this service can run on 1-n machines + - order of events is not important + - 'sequential' should be 'False' + + Args: + source_topic (Union[str, list[str]]): Source topic to enroll with + wildcards '*', or explicit list of topics. + target_topic (str): Topic of dependent event. + sender (str): Identifier of sender (e.g. service name or username). + description (Optional[str]): Human readable text shown + in target event. + sequential (Optional[bool]): The source topic must be processed + in sequence. + events_filter (Optional[dict[str, Any]]): Filtering conditions + to filter the source event. For more technical specifications + look to server backed 'ayon_server.sqlfilter.Filter'. + TODO: Add example of filters. + max_retries (Optional[int]): How many times can be event retried. + Default value is based on server (3 at the time of this PR). + ignore_older_than (Optional[int]): Ignore events older than + given number in days. + ignore_sender_types (Optional[list[str]]): Ignore events triggered + by given sender types. + + Returns: + Optional[dict[str, Any]]: None if there is no event matching + filters. Created event with 'target_topic'. + + """ + kwargs: dict[str, Any] = { + "sourceTopic": source_topic, + "targetTopic": target_topic, + "sender": sender, + } + major, minor, patch, _, _ = self.get_server_version_tuple() + if max_retries is not None: + kwargs["maxRetries"] = max_retries + if sequential is not None: + kwargs["sequential"] = sequential + if description is not None: + kwargs["description"] = description + if events_filter is not None: + kwargs["filter"] = events_filter + if ( + ignore_older_than is not None + and (major, minor, patch) > (1, 5, 1) + ): + kwargs["ignoreOlderThan"] = ignore_older_than + if ignore_sender_types is not None: + if (major, minor, patch) <= (1, 5, 4): + raise ValueError( + "Ignore sender types are not supported for" + f" your version of server {self.get_server_version()}." + ) + kwargs["ignoreSenderTypes"] = list(ignore_sender_types) + + response = self.post("enroll", **kwargs) + if response.status_code == 204: + return None + + if response.status_code == 503: + # Server is busy + self.log.info("Server is busy. Can't enroll event now.") + return None + + if response.status_code >= 400: + self.log.error(response.text) + return None + + return response.data \ No newline at end of file diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 8164b0d38..9dff80799 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -62,7 +62,6 @@ from .graphql import GraphQlQuery, INTROSPECTION_QUERY from .graphql_queries import ( product_types_query, - folders_graphql_query, tasks_graphql_query, tasks_by_folder_paths_graphql_query, products_graphql_query, @@ -70,9 +69,7 @@ representations_graphql_query, representations_hierarchy_qraphql_query, workfiles_info_graphql_query, - events_graphql_query, users_graphql_query, - activities_graphql_query, ) from .exceptions import ( FailedOperations, @@ -107,6 +104,7 @@ ) from ._actions import _ActionsAPI from ._addons import _AddonsAPI +from ._events import _EventsAPI from ._folders import _FoldersAPI from ._links import _LinksAPI from ._lists import _ListsAPI @@ -118,7 +116,6 @@ ServerVersion, ActivityType, ActivityReferenceType, - EventFilter, AttributeScope, AttributeSchemaDataDict, AttributeSchemaDict, @@ -373,6 +370,7 @@ def as_user(self, username): class ServerAPI( _ActionsAPI, _AddonsAPI, + _EventsAPI, _FoldersAPI, _LinksAPI, _ListsAPI, @@ -1484,609 +1482,6 @@ def get(self, entrypoint: str, **kwargs): def delete(self, entrypoint: str, **kwargs): return self.raw_delete(entrypoint, params=kwargs) - def get_event(self, event_id: str) -> Optional[Dict[str, Any]]: - """Query full event data by id. - - Events received using event server do not contain full information. To - get the full event information is required to receive it explicitly. - - Args: - event_id (str): Event id. - - Returns: - dict[str, Any]: Full event data. - - """ - response = self.get(f"events/{event_id}") - response.raise_for_status() - return response.data - - def get_events( - self, - topics: Optional[Iterable[str]] = None, - event_ids: Optional[Iterable[str]] = None, - project_names: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - users: Optional[Iterable[str]] = None, - include_logs: Optional[bool] = None, - has_children: Optional[bool] = None, - newer_than: Optional[str] = None, - older_than: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - limit: Optional[int] = None, - order: Optional[SortOrder] = None, - states: Optional[Iterable[str]] = None, - ) -> Generator[Dict[str, Any], None, None]: - """Get events from server with filtering options. - - Notes: - Not all event happen on a project. - - Args: - topics (Optional[Iterable[str]]): Name of topics. - event_ids (Optional[Iterable[str]]): Event ids. - project_names (Optional[Iterable[str]]): Project on which - event happened. - statuses (Optional[Iterable[str]]): Filtering by statuses. - users (Optional[Iterable[str]]): Filtering by users - who created/triggered an event. - include_logs (Optional[bool]): Query also log events. - has_children (Optional[bool]): Event is with/without children - events. If 'None' then all events are returned, default. - newer_than (Optional[str]): Return only events newer than given - iso datetime string. - older_than (Optional[str]): Return only events older than given - iso datetime string. - fields (Optional[Iterable[str]]): Fields that should be received - for each event. - limit (Optional[int]): Limit number of events to be fetched. - order (Optional[SortOrder]): Order events in ascending - or descending order. It is recommended to set 'limit' - when used descending. - states (Optional[Iterable[str]]): DEPRECATED Filtering by states. - Use 'statuses' instead. - - Returns: - Generator[dict[str, Any]]: Available events matching filters. - - """ - if statuses is None and states is not None: - warnings.warn( - ( - "Used deprecated argument 'states' in 'get_events'." - " Use 'statuses' instead." - ), - DeprecationWarning - ) - statuses = states - - filters = {} - if not prepare_list_filters( - filters, - ("eventTopics", topics), - ("eventIds", event_ids), - ("projectNames", project_names), - ("eventStatuses", statuses), - ("eventUsers", users), - ): - return - - if include_logs is None: - include_logs = False - - for filter_key, filter_value in ( - ("includeLogsFilter", include_logs), - ("hasChildrenFilter", has_children), - ("newerThanFilter", newer_than), - ("olderThanFilter", older_than), - ): - if filter_value is not None: - filters[filter_key] = filter_value - - if not fields: - fields = self.get_default_fields_for_type("event") - - major, minor, patch, _, _ = self.server_version_tuple - use_states = (major, minor, patch) <= (1, 5, 6) - - query = events_graphql_query(set(fields), order, use_states) - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - if limit: - events_field = query.get_field_by_path("events") - events_field.set_limit(limit) - - for parsed_data in query.continuous_query(self): - for event in parsed_data["events"]: - yield event - - def update_event( - self, - event_id: str, - sender: Optional[str] = None, - project_name: Optional[str] = None, - username: Optional[str] = None, - status: Optional[str] = None, - description: Optional[str] = None, - summary: Optional[Dict[str, Any]] = None, - payload: Optional[Dict[str, Any]] = None, - progress: Optional[int] = None, - retries: Optional[int] = None, - ): - """Update event data. - - Args: - event_id (str): Event id. - sender (Optional[str]): New sender of event. - project_name (Optional[str]): New project name. - username (Optional[str]): New username. - status (Optional[str]): New event status. Enum: "pending", - "in_progress", "finished", "failed", "aborted", "restarted" - description (Optional[str]): New description. - summary (Optional[dict[str, Any]]): New summary. - payload (Optional[dict[str, Any]]): New payload. - progress (Optional[int]): New progress. Range [0-100]. - retries (Optional[int]): New retries. - - """ - kwargs = { - key: value - for key, value in ( - ("sender", sender), - ("project", project_name), - ("user", username), - ("status", status), - ("description", description), - ("summary", summary), - ("payload", payload), - ("progress", progress), - ("retries", retries), - ) - if value is not None - } - - response = self.patch( - f"events/{event_id}", - **kwargs - ) - response.raise_for_status() - - def dispatch_event( - self, - topic: str, - sender: Optional[str] = None, - event_hash: Optional[str] = None, - project_name: Optional[str] = None, - username: Optional[str] = None, - depends_on: Optional[str] = None, - description: Optional[str] = None, - summary: Optional[Dict[str, Any]] = None, - payload: Optional[Dict[str, Any]] = None, - finished: bool = True, - store: bool = True, - dependencies: Optional[List[str]] = None, - ): - """Dispatch event to server. - - Args: - topic (str): Event topic used for filtering of listeners. - sender (Optional[str]): Sender of event. - event_hash (Optional[str]): Event hash. - project_name (Optional[str]): Project name. - depends_on (Optional[str]): Add dependency to another event. - username (Optional[str]): Username which triggered event. - description (Optional[str]): Description of event. - summary (Optional[dict[str, Any]]): Summary of event that can - be used for simple filtering on listeners. - payload (Optional[dict[str, Any]]): Full payload of event data with - all details. - finished (Optional[bool]): Mark event as finished on dispatch. - store (Optional[bool]): Store event in event queue for possible - future processing otherwise is event send only - to active listeners. - dependencies (Optional[list[str]]): Deprecated. - List of event id dependencies. - - Returns: - RestApiResponse: Response from server. - - """ - if summary is None: - summary = {} - if payload is None: - payload = {} - event_data = { - "topic": topic, - "sender": sender, - "hash": event_hash, - "project": project_name, - "user": username, - "description": description, - "summary": summary, - "payload": payload, - "finished": finished, - "store": store, - } - if depends_on: - event_data["dependsOn"] = depends_on - - if dependencies: - warnings.warn( - ( - "Used deprecated argument 'dependencies' in" - " 'dispatch_event'. Use 'depends_on' instead." - ), - DeprecationWarning - ) - - response = self.post("events", **event_data) - response.raise_for_status() - return response - - def delete_event(self, event_id: str): - """Delete event by id. - - Supported since AYON server 1.6.0. - - Args: - event_id (str): Event id. - - Returns: - RestApiResponse: Response from server. - - """ - response = self.delete(f"events/{event_id}") - response.raise_for_status() - return response - - def enroll_event_job( - self, - source_topic: "Union[str, List[str]]", - target_topic: str, - sender: str, - description: Optional[str] = None, - sequential: Optional[bool] = None, - events_filter: Optional["EventFilter"] = None, - max_retries: Optional[int] = None, - ignore_older_than: Optional[str] = None, - ignore_sender_types: Optional[str] = None, - ): - """Enroll job based on events. - - Enroll will find first unprocessed event with 'source_topic' and will - create new event with 'target_topic' for it and return the new event - data. - - Use 'sequential' to control that only single target event is created - at same time. Creation of new target events is blocked while there is - at least one unfinished event with target topic, when set to 'True'. - This helps when order of events matter and more than one process using - the same target is running at the same time. - - Make sure the new event has updated status to '"finished"' status - when you're done with logic - - Target topic should not clash with other processes/services. - - Created target event have 'dependsOn' key where is id of source topic. - - Use-case: - - Service 1 is creating events with topic 'my.leech' - - Service 2 process 'my.leech' and uses target topic 'my.process' - - this service can run on 1-n machines - - all events must be processed in a sequence by their creation - time and only one event can be processed at a time - - in this case 'sequential' should be set to 'True' so only - one machine is actually processing events, but if one goes - down there are other that can take place - - Service 3 process 'my.leech' and uses target topic 'my.discover' - - this service can run on 1-n machines - - order of events is not important - - 'sequential' should be 'False' - - Args: - source_topic (Union[str, List[str]]): Source topic to enroll with - wildcards '*', or explicit list of topics. - target_topic (str): Topic of dependent event. - sender (str): Identifier of sender (e.g. service name or username). - description (Optional[str]): Human readable text shown - in target event. - sequential (Optional[bool]): The source topic must be processed - in sequence. - events_filter (Optional[dict[str, Any]]): Filtering conditions - to filter the source event. For more technical specifications - look to server backed 'ayon_server.sqlfilter.Filter'. - TODO: Add example of filters. - max_retries (Optional[int]): How many times can be event retried. - Default value is based on server (3 at the time of this PR). - ignore_older_than (Optional[int]): Ignore events older than - given number in days. - ignore_sender_types (Optional[List[str]]): Ignore events triggered - by given sender types. - - Returns: - Union[None, dict[str, Any]]: None if there is no event matching - filters. Created event with 'target_topic'. - - """ - kwargs = { - "sourceTopic": source_topic, - "targetTopic": target_topic, - "sender": sender, - } - major, minor, patch, _, _ = self.server_version_tuple - if max_retries is not None: - kwargs["maxRetries"] = max_retries - if sequential is not None: - kwargs["sequential"] = sequential - if description is not None: - kwargs["description"] = description - if events_filter is not None: - kwargs["filter"] = events_filter - if ( - ignore_older_than is not None - and (major, minor, patch) > (1, 5, 1) - ): - kwargs["ignoreOlderThan"] = ignore_older_than - if ignore_sender_types is not None: - if (major, minor, patch) <= (1, 5, 4): - raise ValueError( - "Ignore sender types are not supported for" - f" your version of server {self.server_version}." - ) - kwargs["ignoreSenderTypes"] = list(ignore_sender_types) - - response = self.post("enroll", **kwargs) - if response.status_code == 204: - return None - - if response.status_code == 503: - # Server is busy - self.log.info("Server is busy. Can't enroll event now.") - return None - - if response.status_code >= 400: - self.log.error(response.text) - return None - - return response.data - - def get_activities( - self, - project_name: str, - activity_ids: Optional[Iterable[str]] = None, - activity_types: Optional[Iterable["ActivityType"]] = None, - entity_ids: Optional[Iterable[str]] = None, - entity_names: Optional[Iterable[str]] = None, - entity_type: Optional[str] = None, - changed_after: Optional[str] = None, - changed_before: Optional[str] = None, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, - limit: Optional[int] = None, - order: Optional[SortOrder] = None, - ) -> Generator[Dict[str, Any], None, None]: - """Get activities from server with filtering options. - - Args: - project_name (str): Project on which activities happened. - activity_ids (Optional[Iterable[str]]): Activity ids. - activity_types (Optional[Iterable[ActivityType]]): Activity types. - entity_ids (Optional[Iterable[str]]): Entity ids. - entity_names (Optional[Iterable[str]]): Entity names. - entity_type (Optional[str]): Entity type. - changed_after (Optional[str]): Return only activities changed - after given iso datetime string. - changed_before (Optional[str]): Return only activities changed - before given iso datetime string. - reference_types (Optional[Iterable[ActivityReferenceType]]): - Reference types filter. Defaults to `['origin']`. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. - limit (Optional[int]): Limit number of activities to be fetched. - order (Optional[SortOrder]): Order activities in ascending - or descending order. It is recommended to set 'limit' - when used descending. - - Returns: - Generator[dict[str, Any]]: Available activities matching filters. - - """ - if not project_name: - return - filters = { - "projectName": project_name, - } - if reference_types is None: - reference_types = {"origin"} - - if not prepare_list_filters( - filters, - ("activityIds", activity_ids), - ("activityTypes", activity_types), - ("entityIds", entity_ids), - ("entityNames", entity_names), - ("referenceTypes", reference_types), - ): - return - - for filter_key, filter_value in ( - ("entityType", entity_type), - ("changedAfter", changed_after), - ("changedBefore", changed_before), - ): - if filter_value is not None: - filters[filter_key] = filter_value - - if not fields: - fields = self.get_default_fields_for_type("activity") - - query = activities_graphql_query(set(fields), order) - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - if limit: - activities_field = query.get_field_by_path("activities") - activities_field.set_limit(limit) - - for parsed_data in query.continuous_query(self): - for activity in parsed_data["project"]["activities"]: - activity_data = activity.get("activityData") - if isinstance(activity_data, str): - activity["activityData"] = json.loads(activity_data) - yield activity - - def get_activity_by_id( - self, - project_name: str, - activity_id: str, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, - ) -> Optional[Dict[str, Any]]: - """Get activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - reference_types: Optional[Iterable[ActivityReferenceType]]: Filter - by reference types. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. - - Returns: - Optional[Dict[str, Any]]: Activity data or None if activity is not - found. - - """ - for activity in self.get_activities( - project_name=project_name, - activity_ids={activity_id}, - reference_types=reference_types, - fields=fields, - ): - return activity - return None - - def create_activity( - self, - project_name: str, - entity_id: str, - entity_type: str, - activity_type: "ActivityType", - activity_id: Optional[str] = None, - body: Optional[str] = None, - file_ids: Optional[List[str]] = None, - timestamp: Optional[str] = None, - data: Optional[Dict[str, Any]] = None, - ) -> str: - """Create activity on a project. - - Args: - project_name (str): Project on which activity happened. - entity_id (str): Entity id. - entity_type (str): Entity type. - activity_type (ActivityType): Activity type. - activity_id (Optional[str]): Activity id. - body (Optional[str]): Activity body. - file_ids (Optional[List[str]]): List of file ids attached - to activity. - timestamp (Optional[str]): Activity timestamp. - data (Optional[Dict[str, Any]]): Additional data. - - Returns: - str: Activity id. - - """ - post_data = { - "activityType": activity_type, - } - for key, value in ( - ("id", activity_id), - ("body", body), - ("files", file_ids), - ("timestamp", timestamp), - ("data", data), - ): - if value is not None: - post_data[key] = value - - response = self.post( - f"projects/{project_name}/{entity_type}/{entity_id}/activities", - **post_data - ) - response.raise_for_status() - return response.data["id"] - - def update_activity( - self, - project_name: str, - activity_id: str, - body: Optional[str] = None, - file_ids: Optional[List[str]] = None, - append_file_ids: Optional[bool] = False, - data: Optional[Dict[str, Any]] = None, - ): - """Update activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - body (str): Activity body. - file_ids (Optional[List[str]]): List of file ids attached - to activity. - append_file_ids (Optional[bool]): Append file ids to existing - list of file ids. - data (Optional[Dict[str, Any]]): Update data in activity. - - """ - update_data = {} - major, minor, patch, _, _ = self.server_version_tuple - new_patch_model = (major, minor, patch) > (1, 5, 6) - if body is None and not new_patch_model: - raise ValueError( - "Update without 'body' is supported" - " after server version 1.5.6." - ) - - if body is not None: - update_data["body"] = body - - if file_ids is not None: - update_data["files"] = file_ids - if new_patch_model: - update_data["appendFiles"] = append_file_ids - elif append_file_ids: - raise ValueError( - "Append file ids is supported after server version 1.5.6." - ) - - if data is not None: - if not new_patch_model: - raise ValueError( - "Update of data is supported after server version 1.5.6." - ) - update_data["data"] = data - - response = self.patch( - f"projects/{project_name}/activities/{activity_id}", - **update_data - ) - response.raise_for_status() - - def delete_activity(self, project_name: str, activity_id: str): - """Delete activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id to remove. - - """ - response = self.delete( - f"projects/{project_name}/activities/{activity_id}" - ) - response.raise_for_status() - def _endpoint_to_url( self, endpoint: str, @@ -6992,45 +6387,6 @@ def send_batch_operations( raise_on_fail, ) - def send_activities_batch_operations( - self, - project_name: str, - operations: List[Dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True - ) -> List[Dict[str, Any]]: - """Post multiple CRUD activities operations to server. - - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. - - Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. - - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. - - Returns: - list[dict[str, Any]]: Operations result with process details. - - """ - return self._send_batch_operations( - f"projects/{project_name}/operations/activities", - operations, - can_fail, - raise_on_fail, - ) - def _send_batch_operations( self, uri: str, From a34be6fb8365ed22043f35560805d3fa9615b47b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:05:28 +0200 Subject: [PATCH 20/53] moved activities api --- automated_api.py | 2 + ayon_api/__init__.py | 24 +-- ayon_api/_activities.py | 292 ++++++++++++++++++++++++++ ayon_api/_api.py | 440 ++++++++++++++++++++-------------------- ayon_api/_base.py | 14 +- ayon_api/server_api.py | 7 +- 6 files changed, 541 insertions(+), 238 deletions(-) create mode 100644 ayon_api/_activities.py diff --git a/automated_api.py b/automated_api.py index f630ed123..1fff22de0 100644 --- a/automated_api.py +++ b/automated_api.py @@ -336,6 +336,7 @@ def prepare_api_functions(api_globals): from ayon_api.server_api import ( # noqa: E402 ServerAPI, _ActionsAPI, + _ActivitiesAPI, _AddonsAPI, _EventsAPI, _FoldersAPI, @@ -347,6 +348,7 @@ def prepare_api_functions(api_globals): functions = [] _items = list(ServerAPI.__dict__.items()) _items.extend(_ActionsAPI.__dict__.items()) + _items.extend(_ActivitiesAPI.__dict__.items()) _items.extend(_AddonsAPI.__dict__.items()) _items.extend(_EventsAPI.__dict__.items()) _items.extend(_FoldersAPI.__dict__.items()) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 66379e097..9b4a956bb 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -67,11 +67,6 @@ patch, get, delete, - get_activities, - get_activity_by_id, - create_activity, - update_activity, - delete_activity, download_file_to_stream, download_file, upload_file_from_stream, @@ -190,13 +185,18 @@ create_thumbnail, update_thumbnail, send_batch_operations, - send_activities_batch_operations, get_actions, trigger_action, get_action_config, set_action_config, take_action, abort_action, + get_activities, + get_activity_by_id, + create_activity, + update_activity, + delete_activity, + send_activities_batch_operations, get_addon_endpoint, get_addons_info, get_addon_url, @@ -331,11 +331,6 @@ "patch", "get", "delete", - "get_activities", - "get_activity_by_id", - "create_activity", - "update_activity", - "delete_activity", "download_file_to_stream", "download_file", "upload_file_from_stream", @@ -454,13 +449,18 @@ "create_thumbnail", "update_thumbnail", "send_batch_operations", - "send_activities_batch_operations", "get_actions", "trigger_action", "get_action_config", "set_action_config", "take_action", "abort_action", + "get_activities", + "get_activity_by_id", + "create_activity", + "update_activity", + "delete_activity", + "send_activities_batch_operations", "get_addon_endpoint", "get_addons_info", "get_addon_url", diff --git a/ayon_api/_activities.py b/ayon_api/_activities.py new file mode 100644 index 000000000..f9d7796eb --- /dev/null +++ b/ayon_api/_activities.py @@ -0,0 +1,292 @@ +import json +import typing +from typing import Optional, Iterable, Generator, Any + +from ._base import _BaseServerAPI +from .utils import ( + SortOrder, + prepare_list_filters, +) +from .graphql_queries import activities_graphql_query + +if typing.TYPE_CHECKING: + from .typing import ( + ActivityType, + ActivityReferenceType, + ) + + +class _ActivitiesAPI(_BaseServerAPI): + def get_activities( + self, + project_name: str, + activity_ids: Optional[Iterable[str]] = None, + activity_types: Optional[Iterable["ActivityType"]] = None, + entity_ids: Optional[Iterable[str]] = None, + entity_names: Optional[Iterable[str]] = None, + entity_type: Optional[str] = None, + changed_after: Optional[str] = None, + changed_before: Optional[str] = None, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + fields: Optional[Iterable[str]] = None, + limit: Optional[int] = None, + order: Optional[SortOrder] = None, + ) -> Generator[dict[str, Any], None, None]: + """Get activities from server with filtering options. + + Args: + project_name (str): Project on which activities happened. + activity_ids (Optional[Iterable[str]]): Activity ids. + activity_types (Optional[Iterable[ActivityType]]): Activity types. + entity_ids (Optional[Iterable[str]]): Entity ids. + entity_names (Optional[Iterable[str]]): Entity names. + entity_type (Optional[str]): Entity type. + changed_after (Optional[str]): Return only activities changed + after given iso datetime string. + changed_before (Optional[str]): Return only activities changed + before given iso datetime string. + reference_types (Optional[Iterable[ActivityReferenceType]]): + Reference types filter. Defaults to `['origin']`. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. + limit (Optional[int]): Limit number of activities to be fetched. + order (Optional[SortOrder]): Order activities in ascending + or descending order. It is recommended to set 'limit' + when used descending. + + Returns: + Generator[dict[str, Any]]: Available activities matching filters. + + """ + if not project_name: + return + filters = { + "projectName": project_name, + } + if reference_types is None: + reference_types = {"origin"} + + if not prepare_list_filters( + filters, + ("activityIds", activity_ids), + ("activityTypes", activity_types), + ("entityIds", entity_ids), + ("entityNames", entity_names), + ("referenceTypes", reference_types), + ): + return + + for filter_key, filter_value in ( + ("entityType", entity_type), + ("changedAfter", changed_after), + ("changedBefore", changed_before), + ): + if filter_value is not None: + filters[filter_key] = filter_value + + if not fields: + fields = self.get_default_fields_for_type("activity") + + query = activities_graphql_query(set(fields), order) + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + if limit: + activities_field = query.get_field_by_path("activities") + activities_field.set_limit(limit) + + for parsed_data in query.continuous_query(self): + for activity in parsed_data["project"]["activities"]: + activity_data = activity.get("activityData") + if isinstance(activity_data, str): + activity["activityData"] = json.loads(activity_data) + yield activity + + def get_activity_by_id( + self, + project_name: str, + activity_id: str, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + fields: Optional[Iterable[str]] = None, + ) -> Optional[dict[str, Any]]: + """Get activity by id. + + Args: + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + reference_types: Optional[Iterable[ActivityReferenceType]]: Filter + by reference types. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. + + Returns: + Optional[dict[str, Any]]: Activity data or None if activity is not + found. + + """ + for activity in self.get_activities( + project_name=project_name, + activity_ids={activity_id}, + reference_types=reference_types, + fields=fields, + ): + return activity + return None + + def create_activity( + self, + project_name: str, + entity_id: str, + entity_type: str, + activity_type: "ActivityType", + activity_id: Optional[str] = None, + body: Optional[str] = None, + file_ids: Optional[list[str]] = None, + timestamp: Optional[str] = None, + data: Optional[dict[str, Any]] = None, + ) -> str: + """Create activity on a project. + + Args: + project_name (str): Project on which activity happened. + entity_id (str): Entity id. + entity_type (str): Entity type. + activity_type (ActivityType): Activity type. + activity_id (Optional[str]): Activity id. + body (Optional[str]): Activity body. + file_ids (Optional[list[str]]): List of file ids attached + to activity. + timestamp (Optional[str]): Activity timestamp. + data (Optional[dict[str, Any]]): Additional data. + + Returns: + str: Activity id. + + """ + post_data = { + "activityType": activity_type, + } + for key, value in ( + ("id", activity_id), + ("body", body), + ("files", file_ids), + ("timestamp", timestamp), + ("data", data), + ): + if value is not None: + post_data[key] = value + + response = self.post( + f"projects/{project_name}/{entity_type}/{entity_id}/activities", + **post_data + ) + response.raise_for_status() + return response.data["id"] + + def update_activity( + self, + project_name: str, + activity_id: str, + body: Optional[str] = None, + file_ids: Optional[list[str]] = None, + append_file_ids: Optional[bool] = False, + data: Optional[dict[str, Any]] = None, + ): + """Update activity by id. + + Args: + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + body (str): Activity body. + file_ids (Optional[list[str]]): List of file ids attached + to activity. + append_file_ids (Optional[bool]): Append file ids to existing + list of file ids. + data (Optional[dict[str, Any]]): Update data in activity. + + """ + update_data = {} + major, minor, patch, _, _ = self.get_server_version_tuple() + new_patch_model = (major, minor, patch) > (1, 5, 6) + if body is None and not new_patch_model: + raise ValueError( + "Update without 'body' is supported" + " after server version 1.5.6." + ) + + if body is not None: + update_data["body"] = body + + if file_ids is not None: + update_data["files"] = file_ids + if new_patch_model: + update_data["appendFiles"] = append_file_ids + elif append_file_ids: + raise ValueError( + "Append file ids is supported after server version 1.5.6." + ) + + if data is not None: + if not new_patch_model: + raise ValueError( + "Update of data is supported after server version 1.5.6." + ) + update_data["data"] = data + + response = self.patch( + f"projects/{project_name}/activities/{activity_id}", + **update_data + ) + response.raise_for_status() + + def delete_activity(self, project_name: str, activity_id: str): + """Delete activity by id. + + Args: + project_name (str): Project on which activity happened. + activity_id (str): Activity id to remove. + + """ + response = self.delete( + f"projects/{project_name}/activities/{activity_id}" + ) + response.raise_for_status() + + def send_activities_batch_operations( + self, + project_name: str, + operations: list[dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True + ) -> list[dict[str, Any]]: + """Post multiple CRUD activities operations to server. + + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. + + Args: + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. + + Returns: + list[dict[str, Any]]: Operations result with process details. + + """ + return self._send_batch_operations( + f"projects/{project_name}/operations/activities", + operations, + can_fail, + raise_on_fail, + ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index d6a6f8ba7..70bcda06d 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -904,186 +904,6 @@ def delete( ) -def get_activities( - project_name: str, - activity_ids: Optional[Iterable[str]] = None, - activity_types: Optional[Iterable["ActivityType"]] = None, - entity_ids: Optional[Iterable[str]] = None, - entity_names: Optional[Iterable[str]] = None, - entity_type: Optional[str] = None, - changed_after: Optional[str] = None, - changed_before: Optional[str] = None, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, - limit: Optional[int] = None, - order: Optional[SortOrder] = None, -) -> Generator[Dict[str, Any], None, None]: - """Get activities from server with filtering options. - - Args: - project_name (str): Project on which activities happened. - activity_ids (Optional[Iterable[str]]): Activity ids. - activity_types (Optional[Iterable[ActivityType]]): Activity types. - entity_ids (Optional[Iterable[str]]): Entity ids. - entity_names (Optional[Iterable[str]]): Entity names. - entity_type (Optional[str]): Entity type. - changed_after (Optional[str]): Return only activities changed - after given iso datetime string. - changed_before (Optional[str]): Return only activities changed - before given iso datetime string. - reference_types (Optional[Iterable[ActivityReferenceType]]): - Reference types filter. Defaults to `['origin']`. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. - limit (Optional[int]): Limit number of activities to be fetched. - order (Optional[SortOrder]): Order activities in ascending - or descending order. It is recommended to set 'limit' - when used descending. - - Returns: - Generator[dict[str, Any]]: Available activities matching filters. - - """ - con = get_server_api_connection() - return con.get_activities( - project_name=project_name, - activity_ids=activity_ids, - activity_types=activity_types, - entity_ids=entity_ids, - entity_names=entity_names, - entity_type=entity_type, - changed_after=changed_after, - changed_before=changed_before, - reference_types=reference_types, - fields=fields, - limit=limit, - order=order, - ) - - -def get_activity_by_id( - project_name: str, - activity_id: str, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, -) -> Optional[Dict[str, Any]]: - """Get activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - reference_types: Optional[Iterable[ActivityReferenceType]]: Filter - by reference types. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. - - Returns: - Optional[Dict[str, Any]]: Activity data or None if activity is not - found. - - """ - con = get_server_api_connection() - return con.get_activity_by_id( - project_name=project_name, - activity_id=activity_id, - reference_types=reference_types, - fields=fields, - ) - - -def create_activity( - project_name: str, - entity_id: str, - entity_type: str, - activity_type: "ActivityType", - activity_id: Optional[str] = None, - body: Optional[str] = None, - file_ids: Optional[List[str]] = None, - timestamp: Optional[str] = None, - data: Optional[Dict[str, Any]] = None, -) -> str: - """Create activity on a project. - - Args: - project_name (str): Project on which activity happened. - entity_id (str): Entity id. - entity_type (str): Entity type. - activity_type (ActivityType): Activity type. - activity_id (Optional[str]): Activity id. - body (Optional[str]): Activity body. - file_ids (Optional[List[str]]): List of file ids attached - to activity. - timestamp (Optional[str]): Activity timestamp. - data (Optional[Dict[str, Any]]): Additional data. - - Returns: - str: Activity id. - - """ - con = get_server_api_connection() - return con.create_activity( - project_name=project_name, - entity_id=entity_id, - entity_type=entity_type, - activity_type=activity_type, - activity_id=activity_id, - body=body, - file_ids=file_ids, - timestamp=timestamp, - data=data, - ) - - -def update_activity( - project_name: str, - activity_id: str, - body: Optional[str] = None, - file_ids: Optional[List[str]] = None, - append_file_ids: Optional[bool] = False, - data: Optional[Dict[str, Any]] = None, -): - """Update activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - body (str): Activity body. - file_ids (Optional[List[str]]): List of file ids attached - to activity. - append_file_ids (Optional[bool]): Append file ids to existing - list of file ids. - data (Optional[Dict[str, Any]]): Update data in activity. - - """ - con = get_server_api_connection() - return con.update_activity( - project_name=project_name, - activity_id=activity_id, - body=body, - file_ids=file_ids, - append_file_ids=append_file_ids, - data=data, - ) - - -def delete_activity( - project_name: str, - activity_id: str, -): - """Delete activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id to remove. - - """ - con = get_server_api_connection() - return con.delete_activity( - project_name=project_name, - activity_id=activity_id, - ) - - def download_file_to_stream( endpoint: str, stream: "StreamType", @@ -4862,46 +4682,6 @@ def send_batch_operations( ) -def send_activities_batch_operations( - project_name: str, - operations: List[Dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True, -) -> List[Dict[str, Any]]: - """Post multiple CRUD activities operations to server. - - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. - - Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. - - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. - - Returns: - list[dict[str, Any]]: Operations result with process details. - - """ - con = get_server_api_connection() - return con.send_activities_batch_operations( - project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, - ) - - def get_actions( project_name: Optional[str] = None, entity_type: Optional["ActionEntityTypes"] = None, @@ -5122,6 +4902,226 @@ def abort_action( ) +def get_activities( + project_name: str, + activity_ids: Optional[Iterable[str]] = None, + activity_types: Optional[Iterable["ActivityType"]] = None, + entity_ids: Optional[Iterable[str]] = None, + entity_names: Optional[Iterable[str]] = None, + entity_type: Optional[str] = None, + changed_after: Optional[str] = None, + changed_before: Optional[str] = None, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + fields: Optional[Iterable[str]] = None, + limit: Optional[int] = None, + order: Optional[SortOrder] = None, +) -> Generator[dict[str, Any], None, None]: + """Get activities from server with filtering options. + + Args: + project_name (str): Project on which activities happened. + activity_ids (Optional[Iterable[str]]): Activity ids. + activity_types (Optional[Iterable[ActivityType]]): Activity types. + entity_ids (Optional[Iterable[str]]): Entity ids. + entity_names (Optional[Iterable[str]]): Entity names. + entity_type (Optional[str]): Entity type. + changed_after (Optional[str]): Return only activities changed + after given iso datetime string. + changed_before (Optional[str]): Return only activities changed + before given iso datetime string. + reference_types (Optional[Iterable[ActivityReferenceType]]): + Reference types filter. Defaults to `['origin']`. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. + limit (Optional[int]): Limit number of activities to be fetched. + order (Optional[SortOrder]): Order activities in ascending + or descending order. It is recommended to set 'limit' + when used descending. + + Returns: + Generator[dict[str, Any]]: Available activities matching filters. + + """ + con = get_server_api_connection() + return con.get_activities( + project_name=project_name, + activity_ids=activity_ids, + activity_types=activity_types, + entity_ids=entity_ids, + entity_names=entity_names, + entity_type=entity_type, + changed_after=changed_after, + changed_before=changed_before, + reference_types=reference_types, + fields=fields, + limit=limit, + order=order, + ) + + +def get_activity_by_id( + project_name: str, + activity_id: str, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + fields: Optional[Iterable[str]] = None, +) -> Optional[dict[str, Any]]: + """Get activity by id. + + Args: + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + reference_types: Optional[Iterable[ActivityReferenceType]]: Filter + by reference types. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. + + Returns: + Optional[dict[str, Any]]: Activity data or None if activity is not + found. + + """ + con = get_server_api_connection() + return con.get_activity_by_id( + project_name=project_name, + activity_id=activity_id, + reference_types=reference_types, + fields=fields, + ) + + +def create_activity( + project_name: str, + entity_id: str, + entity_type: str, + activity_type: "ActivityType", + activity_id: Optional[str] = None, + body: Optional[str] = None, + file_ids: Optional[list[str]] = None, + timestamp: Optional[str] = None, + data: Optional[dict[str, Any]] = None, +) -> str: + """Create activity on a project. + + Args: + project_name (str): Project on which activity happened. + entity_id (str): Entity id. + entity_type (str): Entity type. + activity_type (ActivityType): Activity type. + activity_id (Optional[str]): Activity id. + body (Optional[str]): Activity body. + file_ids (Optional[list[str]]): List of file ids attached + to activity. + timestamp (Optional[str]): Activity timestamp. + data (Optional[dict[str, Any]]): Additional data. + + Returns: + str: Activity id. + + """ + con = get_server_api_connection() + return con.create_activity( + project_name=project_name, + entity_id=entity_id, + entity_type=entity_type, + activity_type=activity_type, + activity_id=activity_id, + body=body, + file_ids=file_ids, + timestamp=timestamp, + data=data, + ) + + +def update_activity( + project_name: str, + activity_id: str, + body: Optional[str] = None, + file_ids: Optional[list[str]] = None, + append_file_ids: Optional[bool] = False, + data: Optional[dict[str, Any]] = None, +): + """Update activity by id. + + Args: + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + body (str): Activity body. + file_ids (Optional[list[str]]): List of file ids attached + to activity. + append_file_ids (Optional[bool]): Append file ids to existing + list of file ids. + data (Optional[dict[str, Any]]): Update data in activity. + + """ + con = get_server_api_connection() + return con.update_activity( + project_name=project_name, + activity_id=activity_id, + body=body, + file_ids=file_ids, + append_file_ids=append_file_ids, + data=data, + ) + + +def delete_activity( + project_name: str, + activity_id: str, +): + """Delete activity by id. + + Args: + project_name (str): Project on which activity happened. + activity_id (str): Activity id to remove. + + """ + con = get_server_api_connection() + return con.delete_activity( + project_name=project_name, + activity_id=activity_id, + ) + + +def send_activities_batch_operations( + project_name: str, + operations: list, + can_fail: bool = False, + raise_on_fail: bool = True, +) -> list: + """Post multiple CRUD activities operations to server. + + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. + + Args: + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. + + Returns: + list[dict[str, Any]]: Operations result with process details. + + """ + con = get_server_api_connection() + return con.send_activities_batch_operations( + project_name=project_name, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, + ) + + def get_addon_endpoint( addon_name: str, addon_version: str, diff --git a/ayon_api/_base.py b/ayon_api/_base.py index faf8c81bf..a742289c2 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -1,7 +1,7 @@ from __future__ import annotations import typing -from typing import Optional +from typing import Optional, Any import requests @@ -12,6 +12,9 @@ class _BaseServerAPI: + def get_server_version(self) -> str: + raise NotImplementedError() + def get_server_version_tuple(self) -> "ServerVersion": raise NotImplementedError() @@ -94,3 +97,12 @@ def _prepare_fields( def _convert_entity_data(self, entity: "AnyEntityDict"): raise NotImplementedError() + + def _send_batch_operations( + self, + uri: str, + operations: list[dict[str, Any]], + can_fail: bool, + raise_on_fail: bool + ) -> list[dict[str, Any]]: + raise NotImplementedError() diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 9dff80799..ca6413822 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -96,13 +96,13 @@ get_default_site_id, NOT_SET, get_media_mime_type, - SortOrder, get_machine_name, fill_own_attribs, prepare_list_filters, PatternType, ) from ._actions import _ActionsAPI +from ._activities import _ActivitiesAPI from ._addons import _AddonsAPI from ._events import _EventsAPI from ._folders import _FoldersAPI @@ -114,8 +114,6 @@ from typing import Union from .typing import ( ServerVersion, - ActivityType, - ActivityReferenceType, AttributeScope, AttributeSchemaDataDict, AttributeSchemaDict, @@ -128,13 +126,11 @@ SecretDict, AnyEntityDict, - FolderDict, TaskDict, ProductDict, VersionDict, RepresentationDict, WorkfileInfoDict, - FlatFolderDict, ProjectHierarchyDict, ProductTypeDict, @@ -369,6 +365,7 @@ def as_user(self, username): class ServerAPI( _ActionsAPI, + _ActivitiesAPI, _AddonsAPI, _EventsAPI, _FoldersAPI, From f876275ebe54b34298dde2a74a0df04ec24b3845 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:19:24 +0200 Subject: [PATCH 21/53] move few things to utils --- ayon_api/exceptions.py | 11 ++++ ayon_api/server_api.py | 129 +---------------------------------------- ayon_api/utils.py | 124 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 129 deletions(-) diff --git a/ayon_api/exceptions.py b/ayon_api/exceptions.py index 32c786834..55343b1e8 100644 --- a/ayon_api/exceptions.py +++ b/ayon_api/exceptions.py @@ -1,5 +1,16 @@ import copy +try: + # This should be used if 'requests' have it available + from requests.exceptions import JSONDecodeError as RequestsJSONDecodeError +except ImportError: + # Older versions of 'requests' don't have custom exception for json + # decode error + try: + from simplejson import JSONDecodeError as RequestsJSONDecodeError + except ImportError: + from json import JSONDecodeError as RequestsJSONDecodeError + class UrlError(Exception): """Url cannot be parsed as url. diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index ca6413822..52312d798 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -137,8 +137,6 @@ StreamType, ) -JSONDecodeError = getattr(json, "JSONDecodeError", ValueError) - _PLACEHOLDER = object() VERSION_REGEX = re.compile( @@ -150,116 +148,6 @@ ) -def _get_description(response): - if HTTPStatus is None: - return str(response.orig_response) - return HTTPStatus(response.status).description - - -class RestApiResponse(object): - """API Response.""" - - def __init__(self, response, data=None): - if response is None: - status_code = 500 - else: - status_code = response.status_code - self._response = response - self.status = status_code - self._data = data - - @property - def text(self): - if self._response is None: - return self.detail - return self._response.text - - @property - def orig_response(self): - return self._response - - @property - def headers(self): - if self._response is None: - return {} - return self._response.headers - - @property - def data(self): - if self._data is None: - try: - self._data = self.orig_response.json() - except RequestsJSONDecodeError: - self._data = {} - return self._data - - @property - def content(self): - if self._response is None: - return b"" - return self._response.content - - @property - def content_type(self) -> Optional[str]: - return self.headers.get("Content-Type") - - @property - def detail(self): - detail = self.get("detail") - if detail: - return detail - return _get_description(self) - - @property - def status_code(self) -> int: - return self.status - - @property - def ok(self) -> bool: - if self._response is not None: - return self._response.ok - return False - - def raise_for_status(self, message=None): - if self._response is None: - if self._data and self._data.get("detail"): - raise ServerError(self._data["detail"]) - raise ValueError("Response is not available.") - - if self.status_code == 401: - raise UnauthorizedError("Missing or invalid authentication token") - try: - self._response.raise_for_status() - except requests.exceptions.HTTPError as exc: - if message is None: - message = str(exc) - raise HTTPRequestError(message, exc.response) - - def __enter__(self, *args, **kwargs): - return self._response.__enter__(*args, **kwargs) - - def __contains__(self, key): - return key in self.data - - def __repr__(self): - return f"<{self.__class__.__name__} [{self.status}]>" - - def __len__(self): - return int(200 <= self.status < 400) - - def __bool__(self): - return 200 <= self.status < 400 - - def __getitem__(self, key): - return self.data[key] - - def get(self, key, default=None): - data = self.data - if isinstance(data, dict): - return self.data.get(key, default) - return default - - class GraphQlResponse: """GraphQl response.""" @@ -1400,22 +1288,7 @@ def _do_rest_request(self, function, url, **kwargs): if new_response is not None: return new_response - content_type = response.headers.get("Content-Type") - if content_type == "application/json": - try: - new_response = RestApiResponse(response) - except JSONDecodeError: - new_response = RestApiResponse( - None, - { - "detail": "The response is not a JSON: {}".format( - response.text) - } - ) - - else: - new_response = RestApiResponse(response) - + new_response = RestApiResponse(response) self.log.debug(f"Response {str(new_response)}") return new_response diff --git a/ayon_api/utils.py b/ayon_api/utils.py index dc5349ef7..7f04d64cd 100644 --- a/ayon_api/utils.py +++ b/ayon_api/utils.py @@ -23,7 +23,19 @@ DEFAULT_VARIANT_ENV_KEY, SITE_ID_ENV_KEY, ) -from .exceptions import UrlError +from .exceptions import ( + UrlError, + ServerError, + UnauthorizedError, + HTTPRequestError, + RequestsJSONDecodeError, +) + +try: + from http import HTTPStatus +except ImportError: + HTTPStatus = None + if typing.TYPE_CHECKING: from typing import Union @@ -84,6 +96,116 @@ class RequestTypes: delete = RequestType("DELETE") +def _get_description(response): + if HTTPStatus is None: + return str(response.orig_response) + return HTTPStatus(response.status).description + + +class RestApiResponse(object): + """API Response.""" + + def __init__(self, response, data=None): + if response is None: + status_code = 500 + else: + status_code = response.status_code + self._response = response + self.status = status_code + self._data = data + + @property + def text(self): + if self._response is None: + return self.detail + return self._response.text + + @property + def orig_response(self): + return self._response + + @property + def headers(self): + if self._response is None: + return {} + return self._response.headers + + @property + def data(self): + if self._data is None: + try: + self._data = self.orig_response.json() + except RequestsJSONDecodeError: + self._data = {} + return self._data + + @property + def content(self): + if self._response is None: + return b"" + return self._response.content + + @property + def content_type(self) -> Optional[str]: + return self.headers.get("Content-Type") + + @property + def detail(self): + detail = self.get("detail") + if detail: + return detail + return _get_description(self) + + @property + def status_code(self) -> int: + return self.status + + @property + def ok(self) -> bool: + if self._response is not None: + return self._response.ok + return False + + def raise_for_status(self, message=None): + if self._response is None: + if self._data and self._data.get("detail"): + raise ServerError(self._data["detail"]) + raise ValueError("Response is not available.") + + if self.status_code == 401: + raise UnauthorizedError("Missing or invalid authentication token") + try: + self._response.raise_for_status() + except requests.exceptions.HTTPError as exc: + if message is None: + message = str(exc) + raise HTTPRequestError(message, exc.response) + + def __enter__(self, *args, **kwargs): + return self._response.__enter__(*args, **kwargs) + + def __contains__(self, key): + return key in self.data + + def __repr__(self): + return f"<{self.__class__.__name__} [{self.status}]>" + + def __len__(self): + return int(200 <= self.status < 400) + + def __bool__(self): + return 200 <= self.status < 400 + + def __getitem__(self, key): + return self.data[key] + + def get(self, key, default=None): + data = self.data + if isinstance(data, dict): + return self.data.get(key, default) + return default + + def fill_own_attribs(entity: "AnyEntityDict") -> None: """Fill own attributes. From 462f9e2480a5d0d039adf0dfae07f8f174c4885b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:19:46 +0200 Subject: [PATCH 22/53] move thumbnails api to separate file --- automated_api.py | 2 + ayon_api/__init__.py | 32 +-- ayon_api/_api.py | 468 ++++++++++++++++++++-------------------- ayon_api/_thumbnails.py | 304 ++++++++++++++++++++++++++ ayon_api/server_api.py | 315 +-------------------------- 5 files changed, 559 insertions(+), 562 deletions(-) create mode 100644 ayon_api/_thumbnails.py diff --git a/automated_api.py b/automated_api.py index 1fff22de0..e47434efd 100644 --- a/automated_api.py +++ b/automated_api.py @@ -343,6 +343,7 @@ def prepare_api_functions(api_globals): _LinksAPI, _ListsAPI, _ProjectsAPI, + _ThumbnailsAPI, ) functions = [] @@ -355,6 +356,7 @@ def prepare_api_functions(api_globals): _items.extend(_LinksAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) _items.extend(_ProjectsAPI.__dict__.items()) + _items.extend(_ThumbnailsAPI.__dict__.items()) processed = set() for attr_name, attr in _items: diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 9b4a956bb..b6272a464 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -176,14 +176,6 @@ get_workfiles_info, get_workfile_info, get_workfile_info_by_id, - get_thumbnail_by_id, - get_thumbnail, - get_folder_thumbnail, - get_task_thumbnail, - get_version_thumbnail, - get_workfile_thumbnail, - create_thumbnail, - update_thumbnail, send_batch_operations, get_actions, trigger_action, @@ -261,6 +253,14 @@ create_project, update_project, delete_project, + get_thumbnail_by_id, + get_thumbnail, + get_folder_thumbnail, + get_task_thumbnail, + get_version_thumbnail, + get_workfile_thumbnail, + create_thumbnail, + update_thumbnail, ) @@ -440,14 +440,6 @@ "get_workfiles_info", "get_workfile_info", "get_workfile_info_by_id", - "get_thumbnail_by_id", - "get_thumbnail", - "get_folder_thumbnail", - "get_task_thumbnail", - "get_version_thumbnail", - "get_workfile_thumbnail", - "create_thumbnail", - "update_thumbnail", "send_batch_operations", "get_actions", "trigger_action", @@ -525,4 +517,12 @@ "create_project", "update_project", "delete_project", + "get_thumbnail_by_id", + "get_thumbnail", + "get_folder_thumbnail", + "get_task_thumbnail", + "get_version_thumbnail", + "get_workfile_thumbnail", + "create_thumbnail", + "update_thumbnail", ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 70bcda06d..678720202 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -4408,240 +4408,6 @@ def get_workfile_info_by_id( ) -def get_thumbnail_by_id( - project_name: str, - thumbnail_id: str, -) -> ThumbnailContent: - """Get thumbnail from server by id. - - Warnings: - Please keep in mind that used endpoint is allowed only for admins - and managers. Use 'get_thumbnail' with entity type and id - to allow access for artists. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. - - Args: - project_name (str): Project under which the entity is located. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - con = get_server_api_connection() - return con.get_thumbnail_by_id( - project_name=project_name, - thumbnail_id=thumbnail_id, - ) - - -def get_thumbnail( - project_name: str, - entity_type: str, - entity_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Get thumbnail from server. - - Permissions of thumbnails are related to entities so thumbnails must - be queried per entity. So an entity type and entity id is required - to be passed. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. - - Args: - project_name (str): Project under which the entity is located. - entity_type (str): Entity type which passed entity id represents. - entity_id (str): Entity id for which thumbnail should be returned. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - con = get_server_api_connection() - return con.get_thumbnail( - project_name=project_name, - entity_type=entity_type, - entity_id=entity_id, - thumbnail_id=thumbnail_id, - ) - - -def get_folder_thumbnail( - project_name: str, - folder_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for folder entity. - - Args: - project_name (str): Project under which the entity is located. - folder_id (str): Folder id for which thumbnail should be returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - con = get_server_api_connection() - return con.get_folder_thumbnail( - project_name=project_name, - folder_id=folder_id, - thumbnail_id=thumbnail_id, - ) - - -def get_task_thumbnail( - project_name: str, - task_id: str, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for task entity. - - Args: - project_name (str): Project under which the entity is located. - task_id (str): Folder id for which thumbnail should be returned. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - con = get_server_api_connection() - return con.get_task_thumbnail( - project_name=project_name, - task_id=task_id, - ) - - -def get_version_thumbnail( - project_name: str, - version_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for version entity. - - Args: - project_name (str): Project under which the entity is located. - version_id (str): Version id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - con = get_server_api_connection() - return con.get_version_thumbnail( - project_name=project_name, - version_id=version_id, - thumbnail_id=thumbnail_id, - ) - - -def get_workfile_thumbnail( - project_name: str, - workfile_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for workfile entity. - - Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Worfile id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - con = get_server_api_connection() - return con.get_workfile_thumbnail( - project_name=project_name, - workfile_id=workfile_id, - thumbnail_id=thumbnail_id, - ) - - -def create_thumbnail( - project_name: str, - src_filepath: str, - thumbnail_id: Optional[str] = None, -) -> str: - """Create new thumbnail on server from passed path. - - Args: - project_name (str): Project where the thumbnail will be created - and can be used. - src_filepath (str): Filepath to thumbnail which should be uploaded. - thumbnail_id (Optional[str]): Prepared if of thumbnail. - - Returns: - str: Created thumbnail id. - - Raises: - ValueError: When thumbnail source cannot be processed. - - """ - con = get_server_api_connection() - return con.create_thumbnail( - project_name=project_name, - src_filepath=src_filepath, - thumbnail_id=thumbnail_id, - ) - - -def update_thumbnail( - project_name: str, - thumbnail_id: str, - src_filepath: str, -): - """Change thumbnail content by id. - - Update can be also used to create new thumbnail. - - Args: - project_name (str): Project where the thumbnail will be created - and can be used. - thumbnail_id (str): Thumbnail id to update. - src_filepath (str): Filepath to thumbnail which should be uploaded. - - Raises: - ValueError: When thumbnail source cannot be processed. - - """ - con = get_server_api_connection() - return con.update_thumbnail( - project_name=project_name, - thumbnail_id=thumbnail_id, - src_filepath=src_filepath, - ) - - def send_batch_operations( project_name: str, operations: List[Dict[str, Any]], @@ -7317,3 +7083,237 @@ def delete_project( return con.delete_project( project_name=project_name, ) + + +def get_thumbnail_by_id( + project_name: str, + thumbnail_id: str, +) -> ThumbnailContent: + """Get thumbnail from server by id. + + Warnings: + Please keep in mind that used endpoint is allowed only for admins + and managers. Use 'get_thumbnail' with entity type and id + to allow access for artists. + + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. + + Args: + project_name (str): Project under which the entity is located. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + con = get_server_api_connection() + return con.get_thumbnail_by_id( + project_name=project_name, + thumbnail_id=thumbnail_id, + ) + + +def get_thumbnail( + project_name: str, + entity_type: str, + entity_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Get thumbnail from server. + + Permissions of thumbnails are related to entities so thumbnails must + be queried per entity. So an entity type and entity id is required + to be passed. + + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. + + Args: + project_name (str): Project under which the entity is located. + entity_type (str): Entity type which passed entity id represents. + entity_id (str): Entity id for which thumbnail should be returned. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + con = get_server_api_connection() + return con.get_thumbnail( + project_name=project_name, + entity_type=entity_type, + entity_id=entity_id, + thumbnail_id=thumbnail_id, + ) + + +def get_folder_thumbnail( + project_name: str, + folder_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for folder entity. + + Args: + project_name (str): Project under which the entity is located. + folder_id (str): Folder id for which thumbnail should be returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + con = get_server_api_connection() + return con.get_folder_thumbnail( + project_name=project_name, + folder_id=folder_id, + thumbnail_id=thumbnail_id, + ) + + +def get_task_thumbnail( + project_name: str, + task_id: str, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for task entity. + + Args: + project_name (str): Project under which the entity is located. + task_id (str): Folder id for which thumbnail should be returned. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + con = get_server_api_connection() + return con.get_task_thumbnail( + project_name=project_name, + task_id=task_id, + ) + + +def get_version_thumbnail( + project_name: str, + version_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for version entity. + + Args: + project_name (str): Project under which the entity is located. + version_id (str): Version id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + con = get_server_api_connection() + return con.get_version_thumbnail( + project_name=project_name, + version_id=version_id, + thumbnail_id=thumbnail_id, + ) + + +def get_workfile_thumbnail( + project_name: str, + workfile_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for workfile entity. + + Args: + project_name (str): Project under which the entity is located. + workfile_id (str): Worfile id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + con = get_server_api_connection() + return con.get_workfile_thumbnail( + project_name=project_name, + workfile_id=workfile_id, + thumbnail_id=thumbnail_id, + ) + + +def create_thumbnail( + project_name: str, + src_filepath: str, + thumbnail_id: Optional[str] = None, +) -> str: + """Create new thumbnail on server from passed path. + + Args: + project_name (str): Project where the thumbnail will be created + and can be used. + src_filepath (str): Filepath to thumbnail which should be uploaded. + thumbnail_id (Optional[str]): Prepared if of thumbnail. + + Returns: + str: Created thumbnail id. + + Raises: + ValueError: When thumbnail source cannot be processed. + + """ + con = get_server_api_connection() + return con.create_thumbnail( + project_name=project_name, + src_filepath=src_filepath, + thumbnail_id=thumbnail_id, + ) + + +def update_thumbnail( + project_name: str, + thumbnail_id: str, + src_filepath: str, +): + """Change thumbnail content by id. + + Update can be also used to create new thumbnail. + + Args: + project_name (str): Project where the thumbnail will be created + and can be used. + thumbnail_id (str): Thumbnail id to update. + src_filepath (str): Filepath to thumbnail which should be uploaded. + + Raises: + ValueError: When thumbnail source cannot be processed. + + """ + con = get_server_api_connection() + return con.update_thumbnail( + project_name=project_name, + thumbnail_id=thumbnail_id, + src_filepath=src_filepath, + ) diff --git a/ayon_api/_thumbnails.py b/ayon_api/_thumbnails.py new file mode 100644 index 000000000..39fb86602 --- /dev/null +++ b/ayon_api/_thumbnails.py @@ -0,0 +1,304 @@ +import os +import warnings +from typing import Optional + +from ._base import _BaseServerAPI +from .utils import ( + get_media_mime_type, + ThumbnailContent, + RequestTypes, + RestApiResponse, +) + + +class _ThumbnailsAPI(_BaseServerAPI): + def get_thumbnail_by_id( + self, project_name: str, thumbnail_id: str + ) -> ThumbnailContent: + """Get thumbnail from server by id. + + Warnings: + Please keep in mind that used endpoint is allowed only for admins + and managers. Use 'get_thumbnail' with entity type and id + to allow access for artists. + + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. + + Args: + project_name (str): Project under which the entity is located. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + response = self.raw_get( + f"projects/{project_name}/thumbnails/{thumbnail_id}" + ) + return self._prepare_thumbnail_content(project_name, response) + + def get_thumbnail( + self, + project_name: str, + entity_type: str, + entity_id: str, + thumbnail_id: Optional[str] = None, + ) -> ThumbnailContent: + """Get thumbnail from server. + + Permissions of thumbnails are related to entities so thumbnails must + be queried per entity. So an entity type and entity id is required + to be passed. + + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. + + Args: + project_name (str): Project under which the entity is located. + entity_type (str): Entity type which passed entity id represents. + entity_id (str): Entity id for which thumbnail should be returned. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + if thumbnail_id: + warnings.warn( + ( + "Function 'get_thumbnail' got 'thumbnail_id' which" + " is deprecated and will be removed in future version." + ), + DeprecationWarning + ) + + if entity_type in ( + "folder", + "task", + "version", + "workfile", + ): + entity_type += "s" + + response = self.raw_get( + f"projects/{project_name}/{entity_type}/{entity_id}/thumbnail" + ) + return self._prepare_thumbnail_content(project_name, response) + + def get_folder_thumbnail( + self, + project_name: str, + folder_id: str, + thumbnail_id: Optional[str] = None, + ) -> ThumbnailContent: + """Prepared method to receive thumbnail for folder entity. + + Args: + project_name (str): Project under which the entity is located. + folder_id (str): Folder id for which thumbnail should be returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + if thumbnail_id: + warnings.warn( + ( + "Function 'get_folder_thumbnail' got 'thumbnail_id' which" + " is deprecated and will be removed in future version." + ), + DeprecationWarning + ) + return self.get_thumbnail( + project_name, "folder", folder_id + ) + + def get_task_thumbnail( + self, + project_name: str, + task_id: str, + ) -> ThumbnailContent: + """Prepared method to receive thumbnail for task entity. + + Args: + project_name (str): Project under which the entity is located. + task_id (str): Folder id for which thumbnail should be returned. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + return self.get_thumbnail(project_name, "task", task_id) + + def get_version_thumbnail( + self, + project_name: str, + version_id: str, + thumbnail_id: Optional[str] = None, + ) -> ThumbnailContent: + """Prepared method to receive thumbnail for version entity. + + Args: + project_name (str): Project under which the entity is located. + version_id (str): Version id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + if thumbnail_id: + warnings.warn( + ( + "Function 'get_version_thumbnail' got 'thumbnail_id' which" + " is deprecated and will be removed in future version." + ), + DeprecationWarning + ) + return self.get_thumbnail( + project_name, "version", version_id + ) + + def get_workfile_thumbnail( + self, + project_name: str, + workfile_id: str, + thumbnail_id: Optional[str] = None, + ) -> ThumbnailContent: + """Prepared method to receive thumbnail for workfile entity. + + Args: + project_name (str): Project under which the entity is located. + workfile_id (str): Worfile id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. + + """ + if thumbnail_id: + warnings.warn( + ( + "Function 'get_workfile_thumbnail' got 'thumbnail_id'" + " which is deprecated and will be removed in future" + " version." + ), + DeprecationWarning + ) + return self.get_thumbnail( + project_name, "workfile", workfile_id + ) + + def create_thumbnail( + self, + project_name: str, + src_filepath: str, + thumbnail_id: Optional[str] = None, + ) -> str: + """Create new thumbnail on server from passed path. + + Args: + project_name (str): Project where the thumbnail will be created + and can be used. + src_filepath (str): Filepath to thumbnail which should be uploaded. + thumbnail_id (Optional[str]): Prepared if of thumbnail. + + Returns: + str: Created thumbnail id. + + Raises: + ValueError: When thumbnail source cannot be processed. + + """ + if not os.path.exists(src_filepath): + raise ValueError("Entered filepath does not exist.") + + if thumbnail_id: + self.update_thumbnail( + project_name, + thumbnail_id, + src_filepath + ) + return thumbnail_id + + mime_type = get_media_mime_type(src_filepath) + response = self.upload_file( + f"projects/{project_name}/thumbnails", + src_filepath, + request_type=RequestTypes.post, + headers={"Content-Type": mime_type}, + ) + response.raise_for_status() + return response.json()["id"] + + def update_thumbnail( + self, project_name: str, thumbnail_id: str, src_filepath: str + ): + """Change thumbnail content by id. + + Update can be also used to create new thumbnail. + + Args: + project_name (str): Project where the thumbnail will be created + and can be used. + thumbnail_id (str): Thumbnail id to update. + src_filepath (str): Filepath to thumbnail which should be uploaded. + + Raises: + ValueError: When thumbnail source cannot be processed. + + """ + if not os.path.exists(src_filepath): + raise ValueError("Entered filepath does not exist.") + + mime_type = get_media_mime_type(src_filepath) + response = self.upload_file( + f"projects/{project_name}/thumbnails/{thumbnail_id}", + src_filepath, + request_type=RequestTypes.put, + headers={"Content-Type": mime_type}, + ) + response.raise_for_status() + + def _prepare_thumbnail_content( + self, + project_name: str, + response: RestApiResponse, + ) -> ThumbnailContent: + content = None + content_type = response.content_type + + # It is expected the response contains thumbnail id otherwise the + # content cannot be cached and filepath returned + thumbnail_id = response.headers.get("X-Thumbnail-Id") + if thumbnail_id is not None: + content = response.content + + return ThumbnailContent( + project_name, thumbnail_id, content, content_type + ) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 52312d798..96cfe4891 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -16,29 +16,12 @@ import copy import uuid import warnings -import itertools from contextlib import contextmanager import typing from typing import Optional, Iterable, Tuple, Generator, Dict, List, Set, Any -try: - from http import HTTPStatus -except ImportError: - HTTPStatus = None - import requests -try: - # This should be used if 'requests' have it available - from requests.exceptions import JSONDecodeError as RequestsJSONDecodeError -except ImportError: - # Older versions of 'requests' don't have custom exception for json - # decode error - try: - from simplejson import JSONDecodeError as RequestsJSONDecodeError - except ImportError: - from json import JSONDecodeError as RequestsJSONDecodeError - from .constants import ( SERVER_RETRIES_ENV_KEY, DEFAULT_FOLDER_TYPE_FIELDS, @@ -76,12 +59,11 @@ UnauthorizedError, AuthenticationError, ServerNotReached, - ServerError, - HTTPRequestError, ) from .utils import ( RequestType, RequestTypes, + RestApiResponse, RepresentationParents, RepresentationHierarchy, prepare_query_string, @@ -90,7 +72,6 @@ entity_data_json_default, failed_json_default, TransferProgress, - ThumbnailContent, get_default_timeout, get_default_settings_variant, get_default_site_id, @@ -109,6 +90,7 @@ from ._links import _LinksAPI from ._lists import _ListsAPI from ._projects import _ProjectsAPI +from ._thumbnails import _ThumbnailsAPI if typing.TYPE_CHECKING: from typing import Union @@ -132,7 +114,6 @@ RepresentationDict, WorkfileInfoDict, - ProjectHierarchyDict, ProductTypeDict, StreamType, ) @@ -260,6 +241,7 @@ class ServerAPI( _LinksAPI, _ListsAPI, _ProjectsAPI, + _ThumbnailsAPI, ): """Base handler of connection to server. @@ -5926,297 +5908,6 @@ def get_workfile_info_by_id( return workfile_info return None - def _prepare_thumbnail_content( - self, - project_name: str, - response: RestApiResponse, - ) -> ThumbnailContent: - content = None - content_type = response.content_type - - # It is expected the response contains thumbnail id otherwise the - # content cannot be cached and filepath returned - thumbnail_id = response.headers.get("X-Thumbnail-Id") - if thumbnail_id is not None: - content = response.content - - return ThumbnailContent( - project_name, thumbnail_id, content, content_type - ) - - def get_thumbnail_by_id( - self, project_name: str, thumbnail_id: str - ) -> ThumbnailContent: - """Get thumbnail from server by id. - - Warnings: - Please keep in mind that used endpoint is allowed only for admins - and managers. Use 'get_thumbnail' with entity type and id - to allow access for artists. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. - - Args: - project_name (str): Project under which the entity is located. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - response = self.raw_get( - f"projects/{project_name}/thumbnails/{thumbnail_id}" - ) - return self._prepare_thumbnail_content(project_name, response) - - def get_thumbnail( - self, - project_name: str, - entity_type: str, - entity_id: str, - thumbnail_id: Optional[str] = None, - ) -> ThumbnailContent: - """Get thumbnail from server. - - Permissions of thumbnails are related to entities so thumbnails must - be queried per entity. So an entity type and entity id is required - to be passed. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. - - Args: - project_name (str): Project under which the entity is located. - entity_type (str): Entity type which passed entity id represents. - entity_id (str): Entity id for which thumbnail should be returned. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - if thumbnail_id: - warnings.warn( - ( - "Function 'get_thumbnail' got 'thumbnail_id' which" - " is deprecated and will be removed in future version." - ), - DeprecationWarning - ) - - if entity_type in ( - "folder", - "task", - "version", - "workfile", - ): - entity_type += "s" - - response = self.raw_get( - f"projects/{project_name}/{entity_type}/{entity_id}/thumbnail" - ) - return self._prepare_thumbnail_content(project_name, response) - - def get_folder_thumbnail( - self, - project_name: str, - folder_id: str, - thumbnail_id: Optional[str] = None, - ) -> ThumbnailContent: - """Prepared method to receive thumbnail for folder entity. - - Args: - project_name (str): Project under which the entity is located. - folder_id (str): Folder id for which thumbnail should be returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - if thumbnail_id: - warnings.warn( - ( - "Function 'get_folder_thumbnail' got 'thumbnail_id' which" - " is deprecated and will be removed in future version." - ), - DeprecationWarning - ) - return self.get_thumbnail( - project_name, "folder", folder_id - ) - - def get_task_thumbnail( - self, - project_name: str, - task_id: str, - ) -> ThumbnailContent: - """Prepared method to receive thumbnail for task entity. - - Args: - project_name (str): Project under which the entity is located. - task_id (str): Folder id for which thumbnail should be returned. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - return self.get_thumbnail(project_name, "task", task_id) - - def get_version_thumbnail( - self, - project_name: str, - version_id: str, - thumbnail_id: Optional[str] = None, - ) -> ThumbnailContent: - """Prepared method to receive thumbnail for version entity. - - Args: - project_name (str): Project under which the entity is located. - version_id (str): Version id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - if thumbnail_id: - warnings.warn( - ( - "Function 'get_version_thumbnail' got 'thumbnail_id' which" - " is deprecated and will be removed in future version." - ), - DeprecationWarning - ) - return self.get_thumbnail( - project_name, "version", version_id - ) - - def get_workfile_thumbnail( - self, - project_name: str, - workfile_id: str, - thumbnail_id: Optional[str] = None, - ) -> ThumbnailContent: - """Prepared method to receive thumbnail for workfile entity. - - Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Worfile id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. - - Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. - - """ - if thumbnail_id: - warnings.warn( - ( - "Function 'get_workfile_thumbnail' got 'thumbnail_id'" - " which is deprecated and will be removed in future" - " version." - ), - DeprecationWarning - ) - return self.get_thumbnail( - project_name, "workfile", workfile_id - ) - - def create_thumbnail( - self, - project_name: str, - src_filepath: str, - thumbnail_id: Optional[str] = None, - ) -> str: - """Create new thumbnail on server from passed path. - - Args: - project_name (str): Project where the thumbnail will be created - and can be used. - src_filepath (str): Filepath to thumbnail which should be uploaded. - thumbnail_id (Optional[str]): Prepared if of thumbnail. - - Returns: - str: Created thumbnail id. - - Raises: - ValueError: When thumbnail source cannot be processed. - - """ - if not os.path.exists(src_filepath): - raise ValueError("Entered filepath does not exist.") - - if thumbnail_id: - self.update_thumbnail( - project_name, - thumbnail_id, - src_filepath - ) - return thumbnail_id - - mime_type = get_media_mime_type(src_filepath) - response = self.upload_file( - f"projects/{project_name}/thumbnails", - src_filepath, - request_type=RequestTypes.post, - headers={"Content-Type": mime_type}, - ) - response.raise_for_status() - return response.json()["id"] - - def update_thumbnail( - self, project_name: str, thumbnail_id: str, src_filepath: str - ): - """Change thumbnail content by id. - - Update can be also used to create new thumbnail. - - Args: - project_name (str): Project where the thumbnail will be created - and can be used. - thumbnail_id (str): Thumbnail id to update. - src_filepath (str): Filepath to thumbnail which should be uploaded. - - Raises: - ValueError: When thumbnail source cannot be processed. - - """ - if not os.path.exists(src_filepath): - raise ValueError("Entered filepath does not exist.") - - mime_type = get_media_mime_type(src_filepath) - response = self.upload_file( - f"projects/{project_name}/thumbnails/{thumbnail_id}", - src_filepath, - request_type=RequestTypes.put, - headers={"Content-Type": mime_type}, - ) - response.raise_for_status() - # --- Batch operations processing --- def send_batch_operations( self, From f00f4e6effa4f7f33d22c944b272ca871c3cd4a6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:25:49 +0200 Subject: [PATCH 23/53] moved workfiles info into separate file --- automated_api.py | 2 + ayon_api/__init__.py | 12 +-- ayon_api/_api.py | 230 ++++++++++++++++++++--------------------- ayon_api/_base.py | 2 + ayon_api/_workfiles.py | 183 ++++++++++++++++++++++++++++++++ ayon_api/server_api.py | 177 +------------------------------ 6 files changed, 311 insertions(+), 295 deletions(-) create mode 100644 ayon_api/_workfiles.py diff --git a/automated_api.py b/automated_api.py index e47434efd..bdadfd6e6 100644 --- a/automated_api.py +++ b/automated_api.py @@ -344,6 +344,7 @@ def prepare_api_functions(api_globals): _ListsAPI, _ProjectsAPI, _ThumbnailsAPI, + _WorkfilesAPI, ) functions = [] @@ -357,6 +358,7 @@ def prepare_api_functions(api_globals): _items.extend(_ListsAPI.__dict__.items()) _items.extend(_ProjectsAPI.__dict__.items()) _items.extend(_ThumbnailsAPI.__dict__.items()) + _items.extend(_WorkfilesAPI.__dict__.items()) processed = set() for attr_name, attr in _items: diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index b6272a464..73f45e13f 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -173,9 +173,6 @@ create_representation, update_representation, delete_representation, - get_workfiles_info, - get_workfile_info, - get_workfile_info_by_id, send_batch_operations, get_actions, trigger_action, @@ -261,6 +258,9 @@ get_workfile_thumbnail, create_thumbnail, update_thumbnail, + get_workfiles_info, + get_workfile_info, + get_workfile_info_by_id, ) @@ -437,9 +437,6 @@ "create_representation", "update_representation", "delete_representation", - "get_workfiles_info", - "get_workfile_info", - "get_workfile_info_by_id", "send_batch_operations", "get_actions", "trigger_action", @@ -525,4 +522,7 @@ "get_workfile_thumbnail", "create_thumbnail", "update_thumbnail", + "get_workfiles_info", + "get_workfile_info", + "get_workfile_info_by_id", ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 678720202..4abf5beaa 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -4293,121 +4293,6 @@ def delete_representation( ) -def get_workfiles_info( - project_name: str, - workfile_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] = None, - paths: Optional[Iterable[str]] = None, - path_regex: Optional[str] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - has_links: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["WorkfileInfoDict", None, None]: - """Workfile info entities by passed filters. - - Args: - project_name (str): Project under which the entity is located. - workfile_ids (Optional[Iterable[str]]): Workfile ids. - task_ids (Optional[Iterable[str]]): Task ids. - paths (Optional[Iterable[str]]): Rootless workfiles paths. - path_regex (Optional[str]): Regex filter for workfile path. - statuses (Optional[Iterable[str]]): Workfile info statuses used - for filtering. - tags (Optional[Iterable[str]]): Workfile info tags used - for filtering. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Generator[WorkfileInfoDict, None, None]: Queried workfile info - entites. - - """ - con = get_server_api_connection() - return con.get_workfiles_info( - project_name=project_name, - workfile_ids=workfile_ids, - task_ids=task_ids, - paths=paths, - path_regex=path_regex, - statuses=statuses, - tags=tags, - has_links=has_links, - fields=fields, - own_attributes=own_attributes, - ) - - -def get_workfile_info( - project_name: str, - task_id: str, - path: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by task id and workfile path. - - Args: - project_name (str): Project under which the entity is located. - task_id (str): Task id. - path (str): Rootless workfile path. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. - - """ - con = get_server_api_connection() - return con.get_workfile_info( - project_name=project_name, - task_id=task_id, - path=path, - fields=fields, - own_attributes=own_attributes, - ) - - -def get_workfile_info_by_id( - project_name: str, - workfile_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by id. - - Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Workfile info id. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. - - """ - con = get_server_api_connection() - return con.get_workfile_info_by_id( - project_name=project_name, - workfile_id=workfile_id, - fields=fields, - own_attributes=own_attributes, - ) - - def send_batch_operations( project_name: str, operations: List[Dict[str, Any]], @@ -7317,3 +7202,118 @@ def update_thumbnail( thumbnail_id=thumbnail_id, src_filepath=src_filepath, ) + + +def get_workfiles_info( + project_name: str, + workfile_ids: Optional[Iterable[str]] = None, + task_ids: Optional[Iterable[str]] = None, + paths: Optional[Iterable[str]] = None, + path_regex: Optional[str] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + has_links: Optional[str] = None, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> "Generator[WorkfileInfoDict, None, None]": + """Workfile info entities by passed filters. + + Args: + project_name (str): Project under which the entity is located. + workfile_ids (Optional[Iterable[str]]): Workfile ids. + task_ids (Optional[Iterable[str]]): Task ids. + paths (Optional[Iterable[str]]): Rootless workfiles paths. + path_regex (Optional[str]): Regex filter for workfile path. + statuses (Optional[Iterable[str]]): Workfile info statuses used + for filtering. + tags (Optional[Iterable[str]]): Workfile info tags used + for filtering. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. + + Returns: + Generator[WorkfileInfoDict, None, None]: Queried workfile info + entites. + + """ + con = get_server_api_connection() + return con.get_workfiles_info( + project_name=project_name, + workfile_ids=workfile_ids, + task_ids=task_ids, + paths=paths, + path_regex=path_regex, + statuses=statuses, + tags=tags, + has_links=has_links, + fields=fields, + own_attributes=own_attributes, + ) + + +def get_workfile_info( + project_name: str, + task_id: str, + path: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by task id and workfile path. + + Args: + project_name (str): Project under which the entity is located. + task_id (str): Task id. + path (str): Rootless workfile path. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. + + Returns: + Optional[WorkfileInfoDict]: Workfile info entity or None. + + """ + con = get_server_api_connection() + return con.get_workfile_info( + project_name=project_name, + task_id=task_id, + path=path, + fields=fields, + own_attributes=own_attributes, + ) + + +def get_workfile_info_by_id( + project_name: str, + workfile_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by id. + + Args: + project_name (str): Project under which the entity is located. + workfile_id (str): Workfile info id. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. + + Returns: + Optional[WorkfileInfoDict]: Workfile info entity or None. + + """ + con = get_server_api_connection() + return con.get_workfile_info_by_id( + project_name=project_name, + workfile_id=workfile_id, + fields=fields, + own_attributes=own_attributes, + ) diff --git a/ayon_api/_base.py b/ayon_api/_base.py index a742289c2..d98869df0 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -10,6 +10,8 @@ if typing.TYPE_CHECKING: from .typing import AnyEntityDict, ServerVersion +_PLACEHOLDER = object() + class _BaseServerAPI: def get_server_version(self) -> str: diff --git a/ayon_api/_workfiles.py b/ayon_api/_workfiles.py new file mode 100644 index 000000000..2a624f607 --- /dev/null +++ b/ayon_api/_workfiles.py @@ -0,0 +1,183 @@ +import warnings +import typing +from typing import Optional, Iterable, Generator + +from ._base import _BaseServerAPI, _PLACEHOLDER +from .graphql_queries import workfiles_info_graphql_query + +if typing.TYPE_CHECKING: + from .typing import WorkfileInfoDict + + +class _WorkfilesAPI(_BaseServerAPI): + def get_workfiles_info( + self, + project_name: str, + workfile_ids: Optional[Iterable[str]] = None, + task_ids: Optional[Iterable[str]] =None, + paths: Optional[Iterable[str]] =None, + path_regex: Optional[str] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + has_links: Optional[str]=None, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Generator["WorkfileInfoDict", None, None]: + """Workfile info entities by passed filters. + + Args: + project_name (str): Project under which the entity is located. + workfile_ids (Optional[Iterable[str]]): Workfile ids. + task_ids (Optional[Iterable[str]]): Task ids. + paths (Optional[Iterable[str]]): Rootless workfiles paths. + path_regex (Optional[str]): Regex filter for workfile path. + statuses (Optional[Iterable[str]]): Workfile info statuses used + for filtering. + tags (Optional[Iterable[str]]): Workfile info tags used + for filtering. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. + + Returns: + Generator[WorkfileInfoDict, None, None]: Queried workfile info + entites. + + """ + filters = {"projectName": project_name} + if task_ids is not None: + task_ids = set(task_ids) + if not task_ids: + return + filters["taskIds"] = list(task_ids) + + if paths is not None: + paths = set(paths) + if not paths: + return + filters["paths"] = list(paths) + + if path_regex is not None: + filters["workfilePathRegex"] = path_regex + + if workfile_ids is not None: + workfile_ids = set(workfile_ids) + if not workfile_ids: + return + filters["workfileIds"] = list(workfile_ids) + + if statuses is not None: + statuses = set(statuses) + if not statuses: + return + filters["workfileStatuses"] = list(statuses) + + if tags is not None: + tags = set(tags) + if not tags: + return + filters["workfileTags"] = list(tags) + + if has_links is not None: + filters["workfilehasLinks"] = has_links.upper() + + if not fields: + fields = self.get_default_fields_for_type("workfile") + else: + fields = set(fields) + self._prepare_fields("workfile", fields) + + if own_attributes is not _PLACEHOLDER: + warnings.warn( + ( + "'own_attributes' is not supported for workfiles. The" + " argument will be removed form function signature in" + " future (apx. version 1.0.10 or 1.1.0)." + ), + DeprecationWarning + ) + + query = workfiles_info_graphql_query(fields) + + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + for parsed_data in query.continuous_query(self): + for workfile_info in parsed_data["project"]["workfiles"]: + self._convert_entity_data(workfile_info) + yield workfile_info + + def get_workfile_info( + self, + project_name: str, + task_id: str, + path: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by task id and workfile path. + + Args: + project_name (str): Project under which the entity is located. + task_id (str): Task id. + path (str): Rootless workfile path. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. + + Returns: + Optional[WorkfileInfoDict]: Workfile info entity or None. + + """ + if not task_id or not path: + return None + + for workfile_info in self.get_workfiles_info( + project_name, + task_ids=[task_id], + paths=[path], + fields=fields, + own_attributes=own_attributes + ): + return workfile_info + return None + + def get_workfile_info_by_id( + self, + project_name: str, + workfile_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by id. + + Args: + project_name (str): Project under which the entity is located. + workfile_id (str): Workfile info id. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. + + Returns: + Optional[WorkfileInfoDict]: Workfile info entity or None. + + """ + if not workfile_id: + return None + + for workfile_info in self.get_workfiles_info( + project_name, + workfile_ids=[workfile_id], + fields=fields, + own_attributes=own_attributes + ): + return workfile_info + return None diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 96cfe4891..b2d58d47f 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -82,6 +82,7 @@ prepare_list_filters, PatternType, ) +from ._base import _PLACEHOLDER from ._actions import _ActionsAPI from ._activities import _ActivitiesAPI from ._addons import _AddonsAPI @@ -91,6 +92,7 @@ from ._lists import _ListsAPI from ._projects import _ProjectsAPI from ._thumbnails import _ThumbnailsAPI +from ._workfiles import _WorkfilesAPI if typing.TYPE_CHECKING: from typing import Union @@ -118,8 +120,6 @@ StreamType, ) -_PLACEHOLDER = object() - VERSION_REGEX = re.compile( r"(?P0|[1-9]\d*)" r"\.(?P0|[1-9]\d*)" @@ -242,6 +242,7 @@ class ServerAPI( _ListsAPI, _ProjectsAPI, _ThumbnailsAPI, + _WorkfilesAPI, ): """Base handler of connection to server. @@ -5736,178 +5737,6 @@ def delete_representation( ) response.raise_for_status() - def get_workfiles_info( - self, - project_name: str, - workfile_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] =None, - paths: Optional[Iterable[str]] =None, - path_regex: Optional[str] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - has_links: Optional[str]=None, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Generator["WorkfileInfoDict", None, None]: - """Workfile info entities by passed filters. - - Args: - project_name (str): Project under which the entity is located. - workfile_ids (Optional[Iterable[str]]): Workfile ids. - task_ids (Optional[Iterable[str]]): Task ids. - paths (Optional[Iterable[str]]): Rootless workfiles paths. - path_regex (Optional[str]): Regex filter for workfile path. - statuses (Optional[Iterable[str]]): Workfile info statuses used - for filtering. - tags (Optional[Iterable[str]]): Workfile info tags used - for filtering. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Generator[WorkfileInfoDict, None, None]: Queried workfile info - entites. - - """ - filters = {"projectName": project_name} - if task_ids is not None: - task_ids = set(task_ids) - if not task_ids: - return - filters["taskIds"] = list(task_ids) - - if paths is not None: - paths = set(paths) - if not paths: - return - filters["paths"] = list(paths) - - if path_regex is not None: - filters["workfilePathRegex"] = path_regex - - if workfile_ids is not None: - workfile_ids = set(workfile_ids) - if not workfile_ids: - return - filters["workfileIds"] = list(workfile_ids) - - if statuses is not None: - statuses = set(statuses) - if not statuses: - return - filters["workfileStatuses"] = list(statuses) - - if tags is not None: - tags = set(tags) - if not tags: - return - filters["workfileTags"] = list(tags) - - if has_links is not None: - filters["workfilehasLinks"] = has_links.upper() - - if not fields: - fields = self.get_default_fields_for_type("workfile") - else: - fields = set(fields) - self._prepare_fields("workfile", fields) - - if own_attributes is not _PLACEHOLDER: - warnings.warn( - ( - "'own_attributes' is not supported for workfiles. The" - " argument will be removed form function signature in" - " future (apx. version 1.0.10 or 1.1.0)." - ), - DeprecationWarning - ) - - query = workfiles_info_graphql_query(fields) - - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - for parsed_data in query.continuous_query(self): - for workfile_info in parsed_data["project"]["workfiles"]: - self._convert_entity_data(workfile_info) - yield workfile_info - - def get_workfile_info( - self, - project_name: str, - task_id: str, - path: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by task id and workfile path. - - Args: - project_name (str): Project under which the entity is located. - task_id (str): Task id. - path (str): Rootless workfile path. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. - - """ - if not task_id or not path: - return None - - for workfile_info in self.get_workfiles_info( - project_name, - task_ids=[task_id], - paths=[path], - fields=fields, - own_attributes=own_attributes - ): - return workfile_info - return None - - def get_workfile_info_by_id( - self, - project_name: str, - workfile_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by id. - - Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Workfile info id. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. - - """ - if not workfile_id: - return None - - for workfile_info in self.get_workfiles_info( - project_name, - workfile_ids=[workfile_id], - fields=fields, - own_attributes=own_attributes - ): - return workfile_info - return None - # --- Batch operations processing --- def send_batch_operations( self, From 14b56b766075d12f254b2881f182526287f7e026 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:36:06 +0200 Subject: [PATCH 24/53] moved representations api to separate file --- automated_api.py | 2 + ayon_api/__init__.py | 48 +- ayon_api/_api.py | 2744 +++++++++++++++++----------------- ayon_api/_base.py | 16 +- ayon_api/_representations.py | 746 +++++++++ ayon_api/server_api.py | 727 +-------- 6 files changed, 2163 insertions(+), 2120 deletions(-) create mode 100644 ayon_api/_representations.py diff --git a/automated_api.py b/automated_api.py index bdadfd6e6..a581dde58 100644 --- a/automated_api.py +++ b/automated_api.py @@ -345,6 +345,7 @@ def prepare_api_functions(api_globals): _ProjectsAPI, _ThumbnailsAPI, _WorkfilesAPI, + _RepresentationsAPI, ) functions = [] @@ -359,6 +360,7 @@ def prepare_api_functions(api_globals): _items.extend(_ProjectsAPI.__dict__.items()) _items.extend(_ThumbnailsAPI.__dict__.items()) _items.extend(_WorkfilesAPI.__dict__.items()) + _items.extend(_RepresentationsAPI.__dict__.items()) processed = set() for attr_name, attr in _items: diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 73f45e13f..dd7b1a053 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -130,7 +130,6 @@ get_rest_task, get_rest_product, get_rest_version, - get_rest_representation, get_tasks, get_task_by_name, get_task_by_id, @@ -162,17 +161,6 @@ create_version, update_version, delete_version, - get_representations, - get_representation_by_id, - get_representation_by_name, - get_representations_hierarchy, - get_representation_hierarchy, - get_representations_parents, - get_representation_parents, - get_repre_ids_by_context_filters, - create_representation, - update_representation, - delete_representation, send_batch_operations, get_actions, trigger_action, @@ -261,6 +249,18 @@ get_workfiles_info, get_workfile_info, get_workfile_info_by_id, + get_rest_representation, + get_representations, + get_representation_by_id, + get_representation_by_name, + get_representations_hierarchy, + get_representation_hierarchy, + get_representations_parents, + get_representation_parents, + get_repre_ids_by_context_filters, + create_representation, + update_representation, + delete_representation, ) @@ -394,7 +394,6 @@ "get_rest_task", "get_rest_product", "get_rest_version", - "get_rest_representation", "get_tasks", "get_task_by_name", "get_task_by_id", @@ -426,17 +425,6 @@ "create_version", "update_version", "delete_version", - "get_representations", - "get_representation_by_id", - "get_representation_by_name", - "get_representations_hierarchy", - "get_representation_hierarchy", - "get_representations_parents", - "get_representation_parents", - "get_repre_ids_by_context_filters", - "create_representation", - "update_representation", - "delete_representation", "send_batch_operations", "get_actions", "trigger_action", @@ -525,4 +513,16 @@ "get_workfiles_info", "get_workfile_info", "get_workfile_info_by_id", + "get_rest_representation", + "get_representations", + "get_representation_by_id", + "get_representation_by_name", + "get_representations_hierarchy", + "get_representation_hierarchy", + "get_representations_parents", + "get_representation_parents", + "get_repre_ids_by_context_filters", + "create_representation", + "update_representation", + "delete_representation", ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 4abf5beaa..6df3bebb3 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -2613,17 +2613,6 @@ def get_rest_version( ) -def get_rest_representation( - project_name: str, - representation_id: str, -) -> Optional["RepresentationDict"]: - con = get_server_api_connection() - return con.get_rest_representation( - project_name=project_name, - representation_id=representation_id, - ) - - def get_tasks( project_name: str, task_ids: Optional[Iterable[str]] = None, @@ -3830,625 +3819,162 @@ def delete_version( ) -def get_representations( +def send_batch_operations( project_name: str, - representation_ids: Optional[Iterable[str]] = None, - representation_names: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - names_by_version_ids: Optional[Dict[str, Iterable[str]]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - has_links: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["RepresentationDict", None, None]: - """Get representation entities based on passed filters from server. - - .. todo:: + operations: List[Dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True, +) -> List[Dict[str, Any]]: + """Post multiple CRUD operations to server. - Add separated function for 'names_by_version_ids' filtering. - Because can't be combined with others. + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. Args: - project_name (str): Name of project where to look for versions. - representation_ids (Optional[Iterable[str]]): Representation ids - used for representation filtering. - representation_names (Optional[Iterable[str]]): Representation - names used for representation filtering. - version_ids (Optional[Iterable[str]]): Version ids used for - representation filtering. Versions are parents of - representations. - names_by_version_ids (Optional[Dict[str, Iterable[str]]]): Find - representations by names and version ids. This filter - discards all other filters. - statuses (Optional[Iterable[str]]): Representation statuses used - for filtering. - tags (Optional[Iterable[str]]): Representation tags used - for filtering. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. Returns: - Generator[RepresentationDict, None, None]: Queried - representation entities. + list[dict[str, Any]]: Operations result with process details. """ con = get_server_api_connection() - return con.get_representations( + return con.send_batch_operations( project_name=project_name, - representation_ids=representation_ids, - representation_names=representation_names, - version_ids=version_ids, - names_by_version_ids=names_by_version_ids, - statuses=statuses, - tags=tags, - active=active, - has_links=has_links, - fields=fields, - own_attributes=own_attributes, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) -def get_representation_by_id( - project_name: str, - representation_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["RepresentationDict"]: - """Query representation entity from server based on id filter. +def get_actions( + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, + mode: Optional["ActionModeType"] = None, +) -> List["ActionManifestDict"]: + """Get actions for a context. Args: - project_name (str): Project where to look for representation. - representation_id (str): Id of representation. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + mode (Optional[ActionModeType]): Action modes. Returns: - Optional[RepresentationDict]: Queried representation - entity or None. + List[ActionManifestDict]: List of action manifests. """ con = get_server_api_connection() - return con.get_representation_by_id( + return con.get_actions( project_name=project_name, - representation_id=representation_id, - fields=fields, - own_attributes=own_attributes, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + mode=mode, ) -def get_representation_by_name( - project_name: str, - representation_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["RepresentationDict"]: - """Query representation entity by name and version id. +def trigger_action( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionTriggerResponse": + """Trigger action. Args: - project_name (str): Project where to look for representation. - representation_name (str): Representation name. - version_id (str): Version id. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. - - Returns: - Optional[RepresentationDict]: Queried representation entity - or None. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. """ con = get_server_api_connection() - return con.get_representation_by_name( + return con.trigger_action( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, project_name=project_name, - representation_name=representation_name, - version_id=version_id, - fields=fields, - own_attributes=own_attributes, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def get_representations_hierarchy( - project_name: str, - representation_ids: Iterable[str], - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - task_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - representation_fields: Optional[Iterable[str]] = None, -) -> Dict[str, RepresentationHierarchy]: - """Find representation with parents by representation id. - - Representation entity with parent entities up to project. - - Default fields are used when any fields are set to `None`. But it is - possible to pass in empty iterable (list, set, tuple) to skip - entity. +def get_action_config( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Get action configuration. Args: - project_name (str): Project where to look for entities. - representation_ids (Iterable[str]): Representation ids. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - task_fields (Optional[Iterable[str]]): Task fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - representation_fields (Optional[Iterable[str]]): Representation - fields. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. Returns: - dict[str, RepresentationHierarchy]: Parent entities by - representation id. - - """ - con = get_server_api_connection() - return con.get_representations_hierarchy( - project_name=project_name, - representation_ids=representation_ids, - project_fields=project_fields, - folder_fields=folder_fields, - task_fields=task_fields, - product_fields=product_fields, - version_fields=version_fields, - representation_fields=representation_fields, - ) - - -def get_representation_hierarchy( - project_name: str, - representation_id: str, - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - task_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - representation_fields: Optional[Iterable[str]] = None, -) -> Optional[RepresentationHierarchy]: - """Find representation parents by representation id. - - Representation parent entities up to project. - - Args: - project_name (str): Project where to look for entities. - representation_id (str): Representation id. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - task_fields (Optional[Iterable[str]]): Task fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - representation_fields (Optional[Iterable[str]]): Representation - fields. - - Returns: - RepresentationHierarchy: Representation hierarchy entities. - - """ - con = get_server_api_connection() - return con.get_representation_hierarchy( - project_name=project_name, - representation_id=representation_id, - project_fields=project_fields, - folder_fields=folder_fields, - task_fields=task_fields, - product_fields=product_fields, - version_fields=version_fields, - representation_fields=representation_fields, - ) - - -def get_representations_parents( - project_name: str, - representation_ids: Iterable[str], - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, -) -> Dict[str, RepresentationParents]: - """Find representations parents by representation id. - - Representation parent entities up to project. - - Args: - project_name (str): Project where to look for entities. - representation_ids (Iterable[str]): Representation ids. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - - Returns: - dict[str, RepresentationParents]: Parent entities by - representation id. - - """ - con = get_server_api_connection() - return con.get_representations_parents( - project_name=project_name, - representation_ids=representation_ids, - project_fields=project_fields, - folder_fields=folder_fields, - product_fields=product_fields, - version_fields=version_fields, - ) - - -def get_representation_parents( - project_name: str, - representation_id: str, - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, -) -> Optional["RepresentationParents"]: - """Find representation parents by representation id. - - Representation parent entities up to project. - - Args: - project_name (str): Project where to look for entities. - representation_id (str): Representation id. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - - Returns: - RepresentationParents: Representation parent entities. - - """ - con = get_server_api_connection() - return con.get_representation_parents( - project_name=project_name, - representation_id=representation_id, - project_fields=project_fields, - folder_fields=folder_fields, - product_fields=product_fields, - version_fields=version_fields, - ) - - -def get_repre_ids_by_context_filters( - project_name: str, - context_filters: Optional[Dict[str, Iterable[str]]], - representation_names: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, -) -> List[str]: - """Find representation ids which match passed context filters. - - Each representation has context integrated on representation entity in - database. The context may contain project, folder, task name or - product name, product type and many more. This implementation gives - option to quickly filter representation based on representation data - in database. - - Context filters have defined structure. To define filter of nested - subfield use dot '.' as delimiter (For example 'task.name'). - Filter values can be regex filters. String or ``re.Pattern`` can - be used. - - Args: - project_name (str): Project where to look for representations. - context_filters (dict[str, list[str]]): Filters of context fields. - representation_names (Optional[Iterable[str]]): Representation - names, can be used as additional filter for representations - by their names. - version_ids (Optional[Iterable[str]]): Version ids, can be used - as additional filter for representations by their parent ids. - - Returns: - list[str]: Representation ids that match passed filters. - - Example: - The function returns just representation ids so if entities are - required for funtionality they must be queried afterwards by - their ids. - >>> project_name = "testProject" - >>> filters = { - ... "task.name": ["[aA]nimation"], - ... "product": [".*[Mm]ain"] - ... } - >>> repre_ids = get_repre_ids_by_context_filters( - ... project_name, filters) - >>> repres = get_representations(project_name, repre_ids) - - """ - con = get_server_api_connection() - return con.get_repre_ids_by_context_filters( - project_name=project_name, - context_filters=context_filters, - representation_names=representation_names, - version_ids=version_ids, - ) - - -def create_representation( - project_name: str, - name: str, - version_id: str, - files: Optional[List[Dict[str, Any]]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - traits: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - representation_id: Optional[str] = None, -) -> str: - """Create new representation. - - Args: - project_name (str): Project name. - name (str): Representation name. - version_id (str): Parent version id. - files (Optional[list[dict]]): Representation files information. - attrib (Optional[dict[str, Any]]): Representation attributes. - data (Optional[dict[str, Any]]): Representation data. - traits (Optional[dict[str, Any]]): Representation traits - serialized data as dict. - tags (Optional[Iterable[str]]): Representation tags. - status (Optional[str]): Representation status. - active (Optional[bool]): Representation active state. - representation_id (Optional[str]): Representation id. If not - passed new id is generated. - - Returns: - str: Representation id. - - """ - con = get_server_api_connection() - return con.create_representation( - project_name=project_name, - name=name, - version_id=version_id, - files=files, - attrib=attrib, - data=data, - traits=traits, - tags=tags, - status=status, - active=active, - representation_id=representation_id, - ) - - -def update_representation( - project_name: str, - representation_id: str, - name: Optional[str] = None, - version_id: Optional[str] = None, - files: Optional[List[Dict[str, Any]]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - traits: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, -): - """Update representation entity on server. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. - - Args: - project_name (str): Project name. - representation_id (str): Representation id. - name (Optional[str]): New name. - version_id (Optional[str]): New version id. - files (Optional[list[dict]]): New files - information. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - traits (Optional[dict[str, Any]]): New traits. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - - """ - con = get_server_api_connection() - return con.update_representation( - project_name=project_name, - representation_id=representation_id, - name=name, - version_id=version_id, - files=files, - attrib=attrib, - data=data, - traits=traits, - tags=tags, - status=status, - active=active, - ) - - -def delete_representation( - project_name: str, - representation_id: str, -): - """Delete representation. - - Args: - project_name (str): Project name. - representation_id (str): Representation id to delete. - - """ - con = get_server_api_connection() - return con.delete_representation( - project_name=project_name, - representation_id=representation_id, - ) - - -def send_batch_operations( - project_name: str, - operations: List[Dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True, -) -> List[Dict[str, Any]]: - """Post multiple CRUD operations to server. - - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. - - Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. - - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. - - Returns: - list[dict[str, Any]]: Operations result with process details. - - """ - con = get_server_api_connection() - return con.send_batch_operations( - project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, - ) - - -def get_actions( - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, - mode: Optional["ActionModeType"] = None, -) -> List["ActionManifestDict"]: - """Get actions for a context. - - Args: - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. - mode (Optional[ActionModeType]): Action modes. - - Returns: - List[ActionManifestDict]: List of action manifests. - - """ - con = get_server_api_connection() - return con.get_actions( - project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, - mode=mode, - ) - - -def trigger_action( - identifier: str, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionTriggerResponse": - """Trigger action. - - Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. - - """ - con = get_server_api_connection() - return con.trigger_action( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, - project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, - ) - - -def get_action_config( - identifier: str, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Get action configuration. - - Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. - - Returns: - ActionConfigResponse: Action configuration data. + ActionConfigResponse: Action configuration data. """ con = get_server_api_connection() @@ -6078,1242 +5604,1718 @@ def get_entities_links( ) -def get_folders_links( +def get_folders_links( + project_name: str, + folder_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query folders links from server. + + Args: + project_name (str): Project where links are. + folder_ids (Optional[Iterable[str]]): Ids of folders for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by folder ids. + + """ + con = get_server_api_connection() + return con.get_folders_links( + project_name=project_name, + folder_ids=folder_ids, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_folder_links( + project_name: str, + folder_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query folder links from server. + + Args: + project_name (str): Project where links are. + folder_id (str): Folder id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of folder. + + """ + con = get_server_api_connection() + return con.get_folder_links( + project_name=project_name, + folder_id=folder_id, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_tasks_links( + project_name: str, + task_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query tasks links from server. + + Args: + project_name (str): Project where links are. + task_ids (Optional[Iterable[str]]): Ids of tasks for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by task ids. + + """ + con = get_server_api_connection() + return con.get_tasks_links( + project_name=project_name, + task_ids=task_ids, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_task_links( + project_name: str, + task_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query task links from server. + + Args: + project_name (str): Project where links are. + task_id (str): Task id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of task. + + """ + con = get_server_api_connection() + return con.get_task_links( + project_name=project_name, + task_id=task_id, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_products_links( + project_name: str, + product_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query products links from server. + + Args: + project_name (str): Project where links are. + product_ids (Optional[Iterable[str]]): Ids of products for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by product ids. + + """ + con = get_server_api_connection() + return con.get_products_links( + project_name=project_name, + product_ids=product_ids, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_product_links( + project_name: str, + product_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query product links from server. + + Args: + project_name (str): Project where links are. + product_id (str): Product id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of product. + + """ + con = get_server_api_connection() + return con.get_product_links( + project_name=project_name, + product_id=product_id, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_versions_links( + project_name: str, + version_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query versions links from server. + + Args: + project_name (str): Project where links are. + version_ids (Optional[Iterable[str]]): Ids of versions for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by version ids. + + """ + con = get_server_api_connection() + return con.get_versions_links( + project_name=project_name, + version_ids=version_ids, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_version_links( + project_name: str, + version_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query version links from server. + + Args: + project_name (str): Project where links are. + version_id (str): Version id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of version. + + """ + con = get_server_api_connection() + return con.get_version_links( + project_name=project_name, + version_id=version_id, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_representations_links( project_name: str, - folder_ids: Optional[Iterable[str]] = None, + representation_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, link_direction: Optional["LinkDirection"] = None, ) -> dict[str, list[dict[str, Any]]]: - """Query folders links from server. + """Query representations links from server. Args: project_name (str): Project where links are. - folder_ids (Optional[Iterable[str]]): Ids of folders for which - links should be received. + representation_ids (Optional[Iterable[str]]): Ids of + representations for which links should be received. link_types (Optional[Iterable[str]]): Link type filters. link_direction (Optional[Literal["in", "out"]]): Link direction filter. Returns: - dict[str, list[dict[str, Any]]]: Link info by folder ids. + dict[str, list[dict[str, Any]]]: Link info by representation ids. """ con = get_server_api_connection() - return con.get_folders_links( + return con.get_representations_links( project_name=project_name, - folder_ids=folder_ids, + representation_ids=representation_ids, link_types=link_types, link_direction=link_direction, ) -def get_folder_links( +def get_representation_links( project_name: str, - folder_id: str, + representation_id: str, link_types: Optional[Iterable[str]] = None, link_direction: Optional["LinkDirection"] = None, ) -> list[dict[str, Any]]: - """Query folder links from server. + """Query representation links from server. Args: project_name (str): Project where links are. - folder_id (str): Folder id for which links should be received. + representation_id (str): Representation id for which links + should be received. link_types (Optional[Iterable[str]]): Link type filters. link_direction (Optional[Literal["in", "out"]]): Link direction filter. Returns: - list[dict[str, Any]]: Link info of folder. + list[dict[str, Any]]: Link info of representation. """ con = get_server_api_connection() - return con.get_folder_links( + return con.get_representation_links( project_name=project_name, - folder_id=folder_id, + representation_id=representation_id, link_types=link_types, link_direction=link_direction, ) -def get_tasks_links( +def get_entity_lists( project_name: str, - task_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query tasks links from server. + *, + list_ids: Optional[Iterable[str]] = None, + active: Optional[bool] = None, + fields: Optional[Iterable[str]] = None, +) -> Generator[Dict[str, Any], None, None]: + """Fetch entity lists from server. Args: - project_name (str): Project where links are. - task_ids (Optional[Iterable[str]]): Ids of tasks for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Project name where entity lists are. + list_ids (Optional[Iterable[str]]): List of entity list ids to + fetch. + active (Optional[bool]): Filter by active state of entity lists. + fields (Optional[Iterable[str]]): Fields to fetch from server. Returns: - dict[str, list[dict[str, Any]]]: Link info by task ids. + Generator[Dict[str, Any], None, None]: Entity list entities + matching defined filters. """ con = get_server_api_connection() - return con.get_tasks_links( + return con.get_entity_lists( project_name=project_name, - task_ids=task_ids, - link_types=link_types, - link_direction=link_direction, + list_ids=list_ids, + active=active, + fields=fields, ) -def get_task_links( +def get_entity_list_rest( project_name: str, - task_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query task links from server. + list_id: str, +) -> Optional[Dict[str, Any]]: + """Get entity list by id using REST API. Args: - project_name (str): Project where links are. - task_id (str): Task id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Project name. + list_id (str): Entity list id. Returns: - list[dict[str, Any]]: Link info of task. + Optional[Dict[str, Any]]: Entity list data or None if not found. """ con = get_server_api_connection() - return con.get_task_links( + return con.get_entity_list_rest( project_name=project_name, - task_id=task_id, - link_types=link_types, - link_direction=link_direction, + list_id=list_id, ) -def get_products_links( +def get_entity_list_by_id( project_name: str, - product_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query products links from server. + list_id: str, + fields: Optional[Iterable[str]] = None, +) -> Optional[Dict[str, Any]]: + """Get entity list by id using GraphQl. + + Args: + project_name (str): Project name. + list_id (str): Entity list id. + fields (Optional[Iterable[str]]): Fields to fetch from server. + + Returns: + Optional[Dict[str, Any]]: Entity list data or None if not found. + + """ + con = get_server_api_connection() + return con.get_entity_list_by_id( + project_name=project_name, + list_id=list_id, + fields=fields, + ) + + +def create_entity_list( + project_name: str, + entity_type: "EntityListEntityType", + label: str, + *, + list_type: Optional[str] = None, + access: Optional[Dict[str, Any]] = None, + attrib: Optional[List[Dict[str, Any]]] = None, + data: Optional[List[Dict[str, Any]]] = None, + tags: Optional[List[str]] = None, + template: Optional[Dict[str, Any]] = None, + owner: Optional[str] = None, + active: Optional[bool] = None, + items: Optional[List[Dict[str, Any]]] = None, + list_id: Optional[str] = None, +) -> str: + """Create entity list. + + Args: + project_name (str): Project name where entity list lives. + entity_type (EntityListEntityType): Which entity types can be + used in list. + label (str): Entity list label. + list_type (Optional[str]): Entity list type. + access (Optional[dict[str, Any]]): Access control for entity list. + attrib (Optional[dict[str, Any]]): Attribute values of + entity list. + data (Optional[dict[str, Any]]): Custom data of entity list. + tags (Optional[list[str]]): Entity list tags. + template (Optional[dict[str, Any]]): Dynamic list template. + owner (Optional[str]): New owner of the list. + active (Optional[bool]): Change active state of entity list. + items (Optional[list[dict[str, Any]]]): Initial items in + entity list. + list_id (Optional[str]): Entity list id. + + """ + con = get_server_api_connection() + return con.create_entity_list( + project_name=project_name, + entity_type=entity_type, + label=label, + list_type=list_type, + access=access, + attrib=attrib, + data=data, + tags=tags, + template=template, + owner=owner, + active=active, + items=items, + list_id=list_id, + ) + + +def update_entity_list( + project_name: str, + list_id: str, + *, + label: Optional[str] = None, + access: Optional[Dict[str, Any]] = None, + attrib: Optional[List[Dict[str, Any]]] = None, + data: Optional[List[Dict[str, Any]]] = None, + tags: Optional[List[str]] = None, + owner: Optional[str] = None, + active: Optional[bool] = None, +) -> None: + """Update entity list. + + Args: + project_name (str): Project name where entity list lives. + list_id (str): Entity list id that will be updated. + label (Optional[str]): New label of entity list. + access (Optional[dict[str, Any]]): Access control for entity list. + attrib (Optional[dict[str, Any]]): Attribute values of + entity list. + data (Optional[dict[str, Any]]): Custom data of entity list. + tags (Optional[list[str]]): Entity list tags. + owner (Optional[str]): New owner of the list. + active (Optional[bool]): Change active state of entity list. + + """ + con = get_server_api_connection() + return con.update_entity_list( + project_name=project_name, + list_id=list_id, + label=label, + access=access, + attrib=attrib, + data=data, + tags=tags, + owner=owner, + active=active, + ) + + +def delete_entity_list( + project_name: str, + list_id: str, +) -> None: + """Delete entity list from project. + + Args: + project_name (str): Project name. + list_id (str): Entity list id that will be removed. + + """ + con = get_server_api_connection() + return con.delete_entity_list( + project_name=project_name, + list_id=list_id, + ) + + +def get_entity_list_attribute_definitions( + project_name: str, + list_id: str, +) -> List["EntityListAttributeDefinitionDict"]: + """Get attribute definitioins on entity list. + + Args: + project_name (str): Project name. + list_id (str): Entity list id. + + Returns: + List[EntityListAttributeDefinitionDict]: List of attribute + definitions. + + """ + con = get_server_api_connection() + return con.get_entity_list_attribute_definitions( + project_name=project_name, + list_id=list_id, + ) + - Args: - project_name (str): Project where links are. - product_ids (Optional[Iterable[str]]): Ids of products for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. +def set_entity_list_attribute_definitions( + project_name: str, + list_id: str, + attribute_definitions: List["EntityListAttributeDefinitionDict"], +) -> None: + """Set attribute definitioins on entity list. - Returns: - dict[str, list[dict[str, Any]]]: Link info by product ids. + Args: + project_name (str): Project name. + list_id (str): Entity list id. + attribute_definitions (List[EntityListAttributeDefinitionDict]): + List of attribute definitions. """ con = get_server_api_connection() - return con.get_products_links( + return con.set_entity_list_attribute_definitions( project_name=project_name, - product_ids=product_ids, - link_types=link_types, - link_direction=link_direction, + list_id=list_id, + attribute_definitions=attribute_definitions, ) -def get_product_links( +def create_entity_list_item( project_name: str, - product_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query product links from server. + list_id: str, + *, + position: Optional[int] = None, + label: Optional[str] = None, + attrib: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + tags: Optional[List[str]] = None, + item_id: Optional[str] = None, +) -> str: + """Create entity list item. Args: - project_name (str): Project where links are. - product_id (str): Product id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Project name where entity list lives. + list_id (str): Entity list id where item will be added. + position (Optional[int]): Position of item in entity list. + label (Optional[str]): Label of item in entity list. + attrib (Optional[dict[str, Any]]): Item attribute values. + data (Optional[dict[str, Any]]): Item data. + tags (Optional[list[str]]): Tags of item in entity list. + item_id (Optional[str]): Id of item that will be created. Returns: - list[dict[str, Any]]: Link info of product. + str: Item id. """ con = get_server_api_connection() - return con.get_product_links( + return con.create_entity_list_item( project_name=project_name, - product_id=product_id, - link_types=link_types, - link_direction=link_direction, + list_id=list_id, + position=position, + label=label, + attrib=attrib, + data=data, + tags=tags, + item_id=item_id, ) -def get_versions_links( +def update_entity_list_items( project_name: str, - version_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query versions links from server. + list_id: str, + items: List[Dict[str, Any]], + mode: "EntityListItemMode", +) -> None: + """Update items in entity list. Args: - project_name (str): Project where links are. - version_ids (Optional[Iterable[str]]): Ids of versions for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by version ids. + project_name (str): Project name where entity list live. + list_id (str): Entity list id. + items (List[Dict[str, Any]]): Entity list items. + mode (EntityListItemMode): Mode of items update. """ con = get_server_api_connection() - return con.get_versions_links( + return con.update_entity_list_items( project_name=project_name, - version_ids=version_ids, - link_types=link_types, - link_direction=link_direction, + list_id=list_id, + items=items, + mode=mode, ) -def get_version_links( +def update_entity_list_item( project_name: str, - version_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query version links from server. + list_id: str, + item_id: str, + *, + new_list_id: Optional[str], + position: Optional[int] = None, + label: Optional[str] = None, + attrib: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + tags: Optional[List[str]] = None, +) -> None: + """Update item in entity list. Args: - project_name (str): Project where links are. - version_id (str): Version id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of version. + project_name (str): Project name where entity list live. + list_id (str): Entity list id where item lives. + item_id (str): Item id that will be removed from entity list. + new_list_id (Optional[str]): New entity list id where item will be + added. + position (Optional[int]): Position of item in entity list. + label (Optional[str]): Label of item in entity list. + attrib (Optional[dict[str, Any]]): Attributes of item in entity + list. + data (Optional[dict[str, Any]]): Custom data of item in + entity list. + tags (Optional[list[str]]): Tags of item in entity list. """ con = get_server_api_connection() - return con.get_version_links( + return con.update_entity_list_item( project_name=project_name, - version_id=version_id, - link_types=link_types, - link_direction=link_direction, + list_id=list_id, + item_id=item_id, + new_list_id=new_list_id, + position=position, + label=label, + attrib=attrib, + data=data, + tags=tags, ) -def get_representations_links( +def delete_entity_list_item( project_name: str, - representation_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query representations links from server. + list_id: str, + item_id: str, +) -> None: + """Delete item from entity list. Args: - project_name (str): Project where links are. - representation_ids (Optional[Iterable[str]]): Ids of - representations for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by representation ids. + project_name (str): Project name where entity list live. + list_id (str): Entity list id from which item will be removed. + item_id (str): Item id that will be removed from entity list. """ con = get_server_api_connection() - return con.get_representations_links( + return con.delete_entity_list_item( project_name=project_name, - representation_ids=representation_ids, - link_types=link_types, - link_direction=link_direction, + list_id=list_id, + item_id=item_id, ) -def get_representation_links( +def get_rest_project( project_name: str, - representation_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query representation links from server. +) -> Optional["ProjectDict"]: + """Query project by name. + + This call returns project with anatomy data. Args: - project_name (str): Project where links are. - representation_id (str): Representation id for which links - should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project. Returns: - list[dict[str, Any]]: Link info of representation. + Optional[ProjectDict]: Project entity data or 'None' if + project was not found. """ con = get_server_api_connection() - return con.get_representation_links( + return con.get_rest_project( project_name=project_name, - representation_id=representation_id, - link_types=link_types, - link_direction=link_direction, ) -def get_entity_lists( - project_name: str, - *, - list_ids: Optional[Iterable[str]] = None, - active: Optional[bool] = None, - fields: Optional[Iterable[str]] = None, -) -> Generator[Dict[str, Any], None, None]: - """Fetch entity lists from server. +def get_rest_projects( + active: Optional[bool] = True, + library: Optional[bool] = None, +) -> Generator["ProjectDict", None, None]: + """Query available project entities. + + User must be logged in. Args: - project_name (str): Project name where entity lists are. - list_ids (Optional[Iterable[str]]): List of entity list ids to - fetch. - active (Optional[bool]): Filter by active state of entity lists. - fields (Optional[Iterable[str]]): Fields to fetch from server. + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. Returns: - Generator[Dict[str, Any], None, None]: Entity list entities - matching defined filters. + Generator[ProjectDict, None, None]: Available projects. """ con = get_server_api_connection() - return con.get_entity_lists( - project_name=project_name, - list_ids=list_ids, + return con.get_rest_projects( active=active, - fields=fields, + library=library, ) -def get_entity_list_rest( - project_name: str, - list_id: str, -) -> Optional[Dict[str, Any]]: - """Get entity list by id using REST API. +def get_project_names( + active: Optional[bool] = True, + library: Optional[bool] = None, +) -> list[str]: + """Receive available project names. + + User must be logged in. Args: - project_name (str): Project name. - list_id (str): Entity list id. + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. Returns: - Optional[Dict[str, Any]]: Entity list data or None if not found. + list[str]: List of available project names. """ con = get_server_api_connection() - return con.get_entity_list_rest( - project_name=project_name, - list_id=list_id, + return con.get_project_names( + active=active, + library=library, ) -def get_entity_list_by_id( - project_name: str, - list_id: str, +def get_projects( + active: Optional[bool] = True, + library: Optional[bool] = None, fields: Optional[Iterable[str]] = None, -) -> Optional[Dict[str, Any]]: - """Get entity list by id using GraphQl. + own_attributes: bool = False, +) -> Generator["ProjectDict", None, None]: + """Get projects. Args: - project_name (str): Project name. - list_id (str): Entity list id. - fields (Optional[Iterable[str]]): Fields to fetch from server. + active (Optional[bool]): Filter active or inactive projects. + Filter is disabled when 'None' is passed. + library (Optional[bool]): Filter library projects. Filter is + disabled when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - Optional[Dict[str, Any]]: Entity list data or None if not found. + Generator[ProjectDict, None, None]: Queried projects. """ con = get_server_api_connection() - return con.get_entity_list_by_id( - project_name=project_name, - list_id=list_id, + return con.get_projects( + active=active, + library=library, fields=fields, + own_attributes=own_attributes, ) -def create_entity_list( +def get_project( project_name: str, - entity_type: "EntityListEntityType", - label: str, - *, - list_type: Optional[str] = None, - access: Optional[Dict[str, Any]] = None, - attrib: Optional[List[Dict[str, Any]]] = None, - data: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[str]] = None, - template: Optional[Dict[str, Any]] = None, - owner: Optional[str] = None, - active: Optional[bool] = None, - items: Optional[List[Dict[str, Any]]] = None, - list_id: Optional[str] = None, -) -> str: - """Create entity list. + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["ProjectDict"]: + """Get project. Args: - project_name (str): Project name where entity list lives. - entity_type (EntityListEntityType): Which entity types can be - used in list. - label (str): Entity list label. - list_type (Optional[str]): Entity list type. - access (Optional[dict[str, Any]]): Access control for entity list. - attrib (Optional[dict[str, Any]]): Attribute values of - entity list. - data (Optional[dict[str, Any]]): Custom data of entity list. - tags (Optional[list[str]]): Entity list tags. - template (Optional[dict[str, Any]]): Dynamic list template. - owner (Optional[str]): New owner of the list. - active (Optional[bool]): Change active state of entity list. - items (Optional[list[dict[str, Any]]]): Initial items in - entity list. - list_id (Optional[str]): Entity list id. + project_name (str): Name of project. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[ProjectDict]: Project entity data or None + if project was not found. """ con = get_server_api_connection() - return con.create_entity_list( + return con.get_project( project_name=project_name, - entity_type=entity_type, - label=label, - list_type=list_type, - access=access, - attrib=attrib, - data=data, - tags=tags, - template=template, - owner=owner, - active=active, - items=items, - list_id=list_id, + fields=fields, + own_attributes=own_attributes, ) -def update_entity_list( +def create_project( project_name: str, - list_id: str, - *, - label: Optional[str] = None, - access: Optional[Dict[str, Any]] = None, - attrib: Optional[List[Dict[str, Any]]] = None, - data: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[str]] = None, - owner: Optional[str] = None, - active: Optional[bool] = None, -) -> None: - """Update entity list. - - Args: - project_name (str): Project name where entity list lives. - list_id (str): Entity list id that will be updated. - label (Optional[str]): New label of entity list. - access (Optional[dict[str, Any]]): Access control for entity list. - attrib (Optional[dict[str, Any]]): Attribute values of - entity list. - data (Optional[dict[str, Any]]): Custom data of entity list. - tags (Optional[list[str]]): Entity list tags. - owner (Optional[str]): New owner of the list. - active (Optional[bool]): Change active state of entity list. + project_code: str, + library_project: bool = False, + preset_name: Optional[str] = None, +) -> "ProjectDict": + """Create project using AYON settings. - """ - con = get_server_api_connection() - return con.update_entity_list( - project_name=project_name, - list_id=list_id, - label=label, - access=access, - attrib=attrib, - data=data, - tags=tags, - owner=owner, - active=active, - ) + This project creation function is not validating project entity on + creation. It is because project entity is created blindly with only + minimum required information about project which is name and code. + Entered project name must be unique and project must not exist yet. -def delete_entity_list( - project_name: str, - list_id: str, -) -> None: - """Delete entity list from project. + Note: + This function is here to be OP v4 ready but in v3 has more logic + to do. That's why inner imports are in the body. Args: - project_name (str): Project name. - list_id (str): Entity list id that will be removed. + project_name (str): New project name. Should be unique. + project_code (str): Project's code should be unique too. + library_project (Optional[bool]): Project is library project. + preset_name (Optional[str]): Name of anatomy preset. Default is + used if not passed. + + Raises: + ValueError: When project name already exists. + + Returns: + ProjectDict: Created project entity. """ con = get_server_api_connection() - return con.delete_entity_list( + return con.create_project( project_name=project_name, - list_id=list_id, + project_code=project_code, + library_project=library_project, + preset_name=preset_name, ) -def get_entity_list_attribute_definitions( +def update_project( project_name: str, - list_id: str, -) -> List["EntityListAttributeDefinitionDict"]: - """Get attribute definitioins on entity list. + library: Optional[bool] = None, + folder_types: Optional[list[dict[str, Any]]] = None, + task_types: Optional[list[dict[str, Any]]] = None, + link_types: Optional[list[dict[str, Any]]] = None, + statuses: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[dict[str, Any]]] = None, + config: Optional[dict[str, Any]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + active: Optional[bool] = None, + project_code: Optional[str] = None, + **changes, +): + """Update project entity on server. Args: - project_name (str): Project name. - list_id (str): Entity list id. - - Returns: - List[EntityListAttributeDefinitionDict]: List of attribute + 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[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. """ con = get_server_api_connection() - return con.get_entity_list_attribute_definitions( + return con.update_project( project_name=project_name, - list_id=list_id, + library=library, + folder_types=folder_types, + task_types=task_types, + link_types=link_types, + statuses=statuses, + tags=tags, + config=config, + attrib=attrib, + data=data, + active=active, + project_code=project_code, + **changes, ) -def set_entity_list_attribute_definitions( +def delete_project( project_name: str, - list_id: str, - attribute_definitions: List["EntityListAttributeDefinitionDict"], -) -> None: - """Set attribute definitioins on entity list. +): + """Delete project from server. + + This will completely remove project from server without any step back. Args: - project_name (str): Project name. - list_id (str): Entity list id. - attribute_definitions (List[EntityListAttributeDefinitionDict]): - List of attribute definitions. + project_name (str): Project name that will be removed. """ con = get_server_api_connection() - return con.set_entity_list_attribute_definitions( + return con.delete_project( project_name=project_name, - list_id=list_id, - attribute_definitions=attribute_definitions, ) -def create_entity_list_item( +def get_thumbnail_by_id( project_name: str, - list_id: str, - *, - position: Optional[int] = None, - label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - item_id: Optional[str] = None, -) -> str: - """Create entity list item. + thumbnail_id: str, +) -> ThumbnailContent: + """Get thumbnail from server by id. + + Warnings: + Please keep in mind that used endpoint is allowed only for admins + and managers. Use 'get_thumbnail' with entity type and id + to allow access for artists. + + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. Args: - project_name (str): Project name where entity list lives. - list_id (str): Entity list id where item will be added. - position (Optional[int]): Position of item in entity list. - label (Optional[str]): Label of item in entity list. - attrib (Optional[dict[str, Any]]): Item attribute values. - data (Optional[dict[str, Any]]): Item data. - tags (Optional[list[str]]): Tags of item in entity list. - item_id (Optional[str]): Id of item that will be created. + project_name (str): Project under which the entity is located. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. Returns: - str: Item id. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.create_entity_list_item( + return con.get_thumbnail_by_id( project_name=project_name, - list_id=list_id, - position=position, - label=label, - attrib=attrib, - data=data, - tags=tags, - item_id=item_id, + thumbnail_id=thumbnail_id, ) -def update_entity_list_items( +def get_thumbnail( project_name: str, - list_id: str, - items: List[Dict[str, Any]], - mode: "EntityListItemMode", -) -> None: - """Update items in entity list. + entity_type: str, + entity_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Get thumbnail from server. + + Permissions of thumbnails are related to entities so thumbnails must + be queried per entity. So an entity type and entity id is required + to be passed. + + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id. - items (List[Dict[str, Any]]): Entity list items. - mode (EntityListItemMode): Mode of items update. + project_name (str): Project under which the entity is located. + entity_type (str): Entity type which passed entity id represents. + entity_id (str): Entity id for which thumbnail should be returned. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.update_entity_list_items( + return con.get_thumbnail( project_name=project_name, - list_id=list_id, - items=items, - mode=mode, + entity_type=entity_type, + entity_id=entity_id, + thumbnail_id=thumbnail_id, ) -def update_entity_list_item( +def get_folder_thumbnail( project_name: str, - list_id: str, - item_id: str, - *, - new_list_id: Optional[str], - position: Optional[int] = None, - label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, -) -> None: - """Update item in entity list. + folder_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for folder entity. Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id where item lives. - item_id (str): Item id that will be removed from entity list. - new_list_id (Optional[str]): New entity list id where item will be - added. - position (Optional[int]): Position of item in entity list. - label (Optional[str]): Label of item in entity list. - attrib (Optional[dict[str, Any]]): Attributes of item in entity - list. - data (Optional[dict[str, Any]]): Custom data of item in - entity list. - tags (Optional[list[str]]): Tags of item in entity list. + project_name (str): Project under which the entity is located. + folder_id (str): Folder id for which thumbnail should be returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.update_entity_list_item( + return con.get_folder_thumbnail( project_name=project_name, - list_id=list_id, - item_id=item_id, - new_list_id=new_list_id, - position=position, - label=label, - attrib=attrib, - data=data, - tags=tags, + folder_id=folder_id, + thumbnail_id=thumbnail_id, ) -def delete_entity_list_item( +def get_task_thumbnail( project_name: str, - list_id: str, - item_id: str, -) -> None: - """Delete item from entity list. + task_id: str, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for task entity. Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id from which item will be removed. - item_id (str): Item id that will be removed from entity list. + project_name (str): Project under which the entity is located. + task_id (str): Folder id for which thumbnail should be returned. + + Returns: + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.delete_entity_list_item( + return con.get_task_thumbnail( project_name=project_name, - list_id=list_id, - item_id=item_id, + task_id=task_id, ) -def get_rest_project( +def get_version_thumbnail( project_name: str, -) -> Optional["ProjectDict"]: - """Query project by name. - - This call returns project with anatomy data. + version_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for version entity. Args: - project_name (str): Name of project. + project_name (str): Project under which the entity is located. + version_id (str): Version id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. Returns: - Optional[ProjectDict]: Project entity data or 'None' if - project was not found. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_rest_project( + return con.get_version_thumbnail( project_name=project_name, + version_id=version_id, + thumbnail_id=thumbnail_id, ) -def get_rest_projects( - active: Optional[bool] = True, - library: Optional[bool] = None, -) -> Generator["ProjectDict", None, None]: - """Query available project entities. - - User must be logged in. +def get_workfile_thumbnail( + project_name: str, + workfile_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for workfile entity. Args: - active (Optional[bool]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Optional[bool]): Filter standard/library projects. Both - are returned if 'None' is passed. + project_name (str): Project under which the entity is located. + workfile_id (str): Worfile id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. Returns: - Generator[ProjectDict, None, None]: Available projects. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_rest_projects( - active=active, - library=library, + return con.get_workfile_thumbnail( + project_name=project_name, + workfile_id=workfile_id, + thumbnail_id=thumbnail_id, ) -def get_project_names( - active: Optional[bool] = True, - library: Optional[bool] = None, -) -> list[str]: - """Receive available project names. - - User must be logged in. +def create_thumbnail( + project_name: str, + src_filepath: str, + thumbnail_id: Optional[str] = None, +) -> str: + """Create new thumbnail on server from passed path. Args: - active (Optional[bool]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Optional[bool]): Filter standard/library projects. Both - are returned if 'None' is passed. + project_name (str): Project where the thumbnail will be created + and can be used. + src_filepath (str): Filepath to thumbnail which should be uploaded. + thumbnail_id (Optional[str]): Prepared if of thumbnail. Returns: - list[str]: List of available project names. + str: Created thumbnail id. + + Raises: + ValueError: When thumbnail source cannot be processed. """ con = get_server_api_connection() - return con.get_project_names( - active=active, - library=library, + return con.create_thumbnail( + project_name=project_name, + src_filepath=src_filepath, + thumbnail_id=thumbnail_id, ) -def get_projects( - active: Optional[bool] = True, - library: Optional[bool] = None, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Generator["ProjectDict", None, None]: - """Get projects. +def update_thumbnail( + project_name: str, + thumbnail_id: str, + src_filepath: str, +): + """Change thumbnail content by id. + + Update can be also used to create new thumbnail. Args: - active (Optional[bool]): Filter active or inactive projects. - Filter is disabled when 'None' is passed. - library (Optional[bool]): Filter library projects. Filter is - disabled when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where the thumbnail will be created + and can be used. + thumbnail_id (str): Thumbnail id to update. + src_filepath (str): Filepath to thumbnail which should be uploaded. - Returns: - Generator[ProjectDict, None, None]: Queried projects. + Raises: + ValueError: When thumbnail source cannot be processed. """ con = get_server_api_connection() - return con.get_projects( - active=active, - library=library, - fields=fields, - own_attributes=own_attributes, + return con.update_thumbnail( + project_name=project_name, + thumbnail_id=thumbnail_id, + src_filepath=src_filepath, ) -def get_project( +def get_workfiles_info( project_name: str, + workfile_ids: Optional[Iterable[str]] = None, + task_ids: Optional[Iterable[str]] = None, + paths: Optional[Iterable[str]] = None, + path_regex: Optional[str] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + has_links: Optional[str] = None, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["ProjectDict"]: - """Get project. + own_attributes=_PLACEHOLDER, +) -> "Generator[WorkfileInfoDict, None, None]": + """Workfile info entities by passed filters. Args: - project_name (str): Name of project. - fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project under which the entity is located. + workfile_ids (Optional[Iterable[str]]): Workfile ids. + task_ids (Optional[Iterable[str]]): Task ids. + paths (Optional[Iterable[str]]): Rootless workfiles paths. + path_regex (Optional[str]): Regex filter for workfile path. + statuses (Optional[Iterable[str]]): Workfile info statuses used + for filtering. + tags (Optional[Iterable[str]]): Workfile info tags used + for filtering. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. Returns: - Optional[ProjectDict]: Project entity data or None - if project was not found. + Generator[WorkfileInfoDict, None, None]: Queried workfile info + entites. """ con = get_server_api_connection() - return con.get_project( + return con.get_workfiles_info( project_name=project_name, + workfile_ids=workfile_ids, + task_ids=task_ids, + paths=paths, + path_regex=path_regex, + statuses=statuses, + tags=tags, + has_links=has_links, fields=fields, own_attributes=own_attributes, ) -def create_project( +def get_workfile_info( project_name: str, - project_code: str, - library_project: bool = False, - preset_name: Optional[str] = None, -) -> "ProjectDict": - """Create project using AYON settings. - - This project creation function is not validating project entity on - creation. It is because project entity is created blindly with only - minimum required information about project which is name and code. - - Entered project name must be unique and project must not exist yet. - - Note: - This function is here to be OP v4 ready but in v3 has more logic - to do. That's why inner imports are in the body. + task_id: str, + path: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by task id and workfile path. Args: - project_name (str): New project name. Should be unique. - project_code (str): Project's code should be unique too. - library_project (Optional[bool]): Project is library project. - preset_name (Optional[str]): Name of anatomy preset. Default is - used if not passed. - - Raises: - ValueError: When project name already exists. + project_name (str): Project under which the entity is located. + task_id (str): Task id. + path (str): Rootless workfile path. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. Returns: - ProjectDict: Created project entity. + Optional[WorkfileInfoDict]: Workfile info entity or None. """ con = get_server_api_connection() - return con.create_project( + return con.get_workfile_info( project_name=project_name, - project_code=project_code, - library_project=library_project, - preset_name=preset_name, + task_id=task_id, + path=path, + fields=fields, + own_attributes=own_attributes, ) -def update_project( +def get_workfile_info_by_id( project_name: str, - library: Optional[bool] = None, - folder_types: Optional[list[dict[str, Any]]] = None, - task_types: Optional[list[dict[str, Any]]] = None, - link_types: Optional[list[dict[str, Any]]] = None, - statuses: Optional[list[dict[str, Any]]] = None, - tags: Optional[list[dict[str, Any]]] = None, - config: Optional[dict[str, Any]] = None, - attrib: Optional[dict[str, Any]] = None, - data: Optional[dict[str, Any]] = None, - active: Optional[bool] = None, - project_code: Optional[str] = None, - **changes, -): - """Update project entity on server. + workfile_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by id. 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[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. + project_name (str): Project under which the entity is located. + workfile_id (str): Workfile info id. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. + + Returns: + Optional[WorkfileInfoDict]: Workfile info entity or None. """ con = get_server_api_connection() - return con.update_project( + return con.get_workfile_info_by_id( project_name=project_name, - library=library, - folder_types=folder_types, - task_types=task_types, - link_types=link_types, - statuses=statuses, - tags=tags, - config=config, - attrib=attrib, - data=data, - active=active, - project_code=project_code, - **changes, + workfile_id=workfile_id, + fields=fields, + own_attributes=own_attributes, ) -def delete_project( +def get_rest_representation( project_name: str, -): - """Delete project from server. - - This will completely remove project from server without any step back. - - Args: - project_name (str): Project name that will be removed. - - """ + representation_id: str, +) -> Optional["RepresentationDict"]: con = get_server_api_connection() - return con.delete_project( + return con.get_rest_representation( project_name=project_name, + representation_id=representation_id, ) -def get_thumbnail_by_id( +def get_representations( project_name: str, - thumbnail_id: str, -) -> ThumbnailContent: - """Get thumbnail from server by id. - - Warnings: - Please keep in mind that used endpoint is allowed only for admins - and managers. Use 'get_thumbnail' with entity type and id - to allow access for artists. + representation_ids: Optional[Iterable[str]] = None, + representation_names: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, + names_by_version_ids: Optional[dict[str, Iterable[str]]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + has_links: Optional[str] = None, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Generator["RepresentationDict", None, None]: + """Get representation entities based on passed filters from server. - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. + .. todo:: + + Add separated function for 'names_by_version_ids' filtering. + Because can't be combined with others. Args: - project_name (str): Project under which the entity is located. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. + project_name (str): Name of project where to look for versions. + representation_ids (Optional[Iterable[str]]): Representation ids + used for representation filtering. + representation_names (Optional[Iterable[str]]): Representation + names used for representation filtering. + version_ids (Optional[Iterable[str]]): Version ids used for + representation filtering. Versions are parents of + representations. + names_by_version_ids (Optional[dict[str, Iterable[str]]]): Find + representations by names and version ids. This filter + discards all other filters. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + Generator[RepresentationDict, None, None]: Queried + representation entities. """ con = get_server_api_connection() - return con.get_thumbnail_by_id( + return con.get_representations( project_name=project_name, - thumbnail_id=thumbnail_id, + representation_ids=representation_ids, + representation_names=representation_names, + version_ids=version_ids, + names_by_version_ids=names_by_version_ids, + statuses=statuses, + tags=tags, + active=active, + has_links=has_links, + fields=fields, + own_attributes=own_attributes, ) -def get_thumbnail( +def get_representation_by_id( project_name: str, - entity_type: str, - entity_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Get thumbnail from server. - - Permissions of thumbnails are related to entities so thumbnails must - be queried per entity. So an entity type and entity id is required - to be passed. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. + representation_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["RepresentationDict"]: + """Query representation entity from server based on id filter. Args: - project_name (str): Project under which the entity is located. - entity_type (str): Entity type which passed entity id represents. - entity_id (str): Entity id for which thumbnail should be returned. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. + project_name (str): Project where to look for representation. + representation_id (str): Id of representation. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + Optional[RepresentationDict]: Queried representation + entity or None. """ con = get_server_api_connection() - return con.get_thumbnail( + return con.get_representation_by_id( project_name=project_name, - entity_type=entity_type, - entity_id=entity_id, - thumbnail_id=thumbnail_id, + representation_id=representation_id, + fields=fields, + own_attributes=own_attributes, ) -def get_folder_thumbnail( +def get_representation_by_name( project_name: str, - folder_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for folder entity. + representation_name: str, + version_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["RepresentationDict"]: + """Query representation entity by name and version id. Args: - project_name (str): Project under which the entity is located. - folder_id (str): Folder id for which thumbnail should be returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. + project_name (str): Project where to look for representation. + representation_name (str): Representation name. + version_id (str): Version id. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + Optional[RepresentationDict]: Queried representation entity + or None. """ con = get_server_api_connection() - return con.get_folder_thumbnail( + return con.get_representation_by_name( project_name=project_name, - folder_id=folder_id, - thumbnail_id=thumbnail_id, + representation_name=representation_name, + version_id=version_id, + fields=fields, + own_attributes=own_attributes, ) -def get_task_thumbnail( +def get_representations_hierarchy( project_name: str, - task_id: str, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for task entity. + representation_ids: Iterable[str], + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + task_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + representation_fields: Optional[Iterable[str]] = None, +) -> dict[str, RepresentationHierarchy]: + """Find representation with parents by representation id. + + Representation entity with parent entities up to project. + + Default fields are used when any fields are set to `None`. But it is + possible to pass in empty iterable (list, set, tuple) to skip + entity. Args: - project_name (str): Project under which the entity is located. - task_id (str): Folder id for which thumbnail should be returned. + project_name (str): Project where to look for entities. + representation_ids (Iterable[str]): Representation ids. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + task_fields (Optional[Iterable[str]]): Task fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + representation_fields (Optional[Iterable[str]]): Representation + fields. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + dict[str, RepresentationHierarchy]: Parent entities by + representation id. """ con = get_server_api_connection() - return con.get_task_thumbnail( + return con.get_representations_hierarchy( project_name=project_name, - task_id=task_id, + representation_ids=representation_ids, + project_fields=project_fields, + folder_fields=folder_fields, + task_fields=task_fields, + product_fields=product_fields, + version_fields=version_fields, + representation_fields=representation_fields, ) -def get_version_thumbnail( +def get_representation_hierarchy( project_name: str, - version_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for version entity. + representation_id: str, + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + task_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + representation_fields: Optional[Iterable[str]] = None, +) -> Optional[RepresentationHierarchy]: + """Find representation parents by representation id. + + Representation parent entities up to project. Args: - project_name (str): Project under which the entity is located. - version_id (str): Version id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. + project_name (str): Project where to look for entities. + representation_id (str): Representation id. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + task_fields (Optional[Iterable[str]]): Task fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + representation_fields (Optional[Iterable[str]]): Representation + fields. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + RepresentationHierarchy: Representation hierarchy entities. """ con = get_server_api_connection() - return con.get_version_thumbnail( + return con.get_representation_hierarchy( project_name=project_name, - version_id=version_id, - thumbnail_id=thumbnail_id, + representation_id=representation_id, + project_fields=project_fields, + folder_fields=folder_fields, + task_fields=task_fields, + product_fields=product_fields, + version_fields=version_fields, + representation_fields=representation_fields, ) -def get_workfile_thumbnail( +def get_representations_parents( project_name: str, - workfile_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for workfile entity. + representation_ids: Iterable[str], + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, +) -> dict[str, RepresentationParents]: + """Find representations parents by representation id. + + Representation parent entities up to project. Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Worfile id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. + project_name (str): Project where to look for entities. + representation_ids (Iterable[str]): Representation ids. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + dict[str, RepresentationParents]: Parent entities by + representation id. """ con = get_server_api_connection() - return con.get_workfile_thumbnail( + return con.get_representations_parents( project_name=project_name, - workfile_id=workfile_id, - thumbnail_id=thumbnail_id, + representation_ids=representation_ids, + project_fields=project_fields, + folder_fields=folder_fields, + product_fields=product_fields, + version_fields=version_fields, ) -def create_thumbnail( +def get_representation_parents( project_name: str, - src_filepath: str, - thumbnail_id: Optional[str] = None, -) -> str: - """Create new thumbnail on server from passed path. + representation_id: str, + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, +) -> Optional["RepresentationParents"]: + """Find representation parents by representation id. + + Representation parent entities up to project. Args: - project_name (str): Project where the thumbnail will be created - and can be used. - src_filepath (str): Filepath to thumbnail which should be uploaded. - thumbnail_id (Optional[str]): Prepared if of thumbnail. + project_name (str): Project where to look for entities. + representation_id (str): Representation id. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. Returns: - str: Created thumbnail id. - - Raises: - ValueError: When thumbnail source cannot be processed. + RepresentationParents: Representation parent entities. """ con = get_server_api_connection() - return con.create_thumbnail( + return con.get_representation_parents( project_name=project_name, - src_filepath=src_filepath, - thumbnail_id=thumbnail_id, + representation_id=representation_id, + project_fields=project_fields, + folder_fields=folder_fields, + product_fields=product_fields, + version_fields=version_fields, ) -def update_thumbnail( +def get_repre_ids_by_context_filters( project_name: str, - thumbnail_id: str, - src_filepath: str, -): - """Change thumbnail content by id. + context_filters: Optional[dict[str, Iterable[str]]], + representation_names: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, +) -> list[str]: + """Find representation ids which match passed context filters. - Update can be also used to create new thumbnail. + Each representation has context integrated on representation entity in + database. The context may contain project, folder, task name or + product name, product type and many more. This implementation gives + option to quickly filter representation based on representation data + in database. + + Context filters have defined structure. To define filter of nested + subfield use dot '.' as delimiter (For example 'task.name'). + Filter values can be regex filters. String or ``re.Pattern`` can + be used. Args: - project_name (str): Project where the thumbnail will be created - and can be used. - thumbnail_id (str): Thumbnail id to update. - src_filepath (str): Filepath to thumbnail which should be uploaded. + project_name (str): Project where to look for representations. + context_filters (dict[str, list[str]]): Filters of context fields. + representation_names (Optional[Iterable[str]]): Representation + names, can be used as additional filter for representations + by their names. + version_ids (Optional[Iterable[str]]): Version ids, can be used + as additional filter for representations by their parent ids. - Raises: - ValueError: When thumbnail source cannot be processed. + Returns: + list[str]: Representation ids that match passed filters. + + Example: + The function returns just representation ids so if entities are + required for funtionality they must be queried afterwards by + their ids. + >>> from ayon_api import get_repre_ids_by_context_filters + >>> from ayon_api import get_representations + >>> project_name = "testProject" + >>> filters = { + ... "task.name": ["[aA]nimation"], + ... "product": [".*[Mm]ain"] + ... } + >>> repre_ids = get_repre_ids_by_context_filters( + ... project_name, filters) + >>> repres = get_representations(project_name, repre_ids) """ con = get_server_api_connection() - return con.update_thumbnail( + return con.get_repre_ids_by_context_filters( project_name=project_name, - thumbnail_id=thumbnail_id, - src_filepath=src_filepath, + context_filters=context_filters, + representation_names=representation_names, + version_ids=version_ids, ) -def get_workfiles_info( +def create_representation( project_name: str, - workfile_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] = None, - paths: Optional[Iterable[str]] = None, - path_regex: Optional[str] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - has_links: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> "Generator[WorkfileInfoDict, None, None]": - """Workfile info entities by passed filters. + name: str, + version_id: str, + files: Optional[list[dict[str, Any]]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + traits: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + representation_id: Optional[str] = None, +) -> str: + """Create new representation. Args: - project_name (str): Project under which the entity is located. - workfile_ids (Optional[Iterable[str]]): Workfile ids. - task_ids (Optional[Iterable[str]]): Task ids. - paths (Optional[Iterable[str]]): Rootless workfiles paths. - path_regex (Optional[str]): Regex filter for workfile path. - statuses (Optional[Iterable[str]]): Workfile info statuses used - for filtering. - tags (Optional[Iterable[str]]): Workfile info tags used - for filtering. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. + project_name (str): Project name. + name (str): Representation name. + version_id (str): Parent version id. + files (Optional[list[dict]]): Representation files information. + attrib (Optional[dict[str, Any]]): Representation attributes. + data (Optional[dict[str, Any]]): Representation data. + traits (Optional[dict[str, Any]]): Representation traits + serialized data as dict. + tags (Optional[Iterable[str]]): Representation tags. + status (Optional[str]): Representation status. + active (Optional[bool]): Representation active state. + representation_id (Optional[str]): Representation id. If not + passed new id is generated. Returns: - Generator[WorkfileInfoDict, None, None]: Queried workfile info - entites. + str: Representation id. """ con = get_server_api_connection() - return con.get_workfiles_info( + return con.create_representation( project_name=project_name, - workfile_ids=workfile_ids, - task_ids=task_ids, - paths=paths, - path_regex=path_regex, - statuses=statuses, + name=name, + version_id=version_id, + files=files, + attrib=attrib, + data=data, + traits=traits, tags=tags, - has_links=has_links, - fields=fields, - own_attributes=own_attributes, + status=status, + active=active, + representation_id=representation_id, ) -def get_workfile_info( +def update_representation( project_name: str, - task_id: str, - path: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by task id and workfile path. + representation_id: str, + name: Optional[str] = None, + version_id: Optional[str] = None, + files: Optional[list[dict[str, Any]]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + traits: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, +): + """Update representation entity on server. - Args: - project_name (str): Project under which the entity is located. - task_id (str): Task id. - path (str): Rootless workfile path. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. + Update of ``data`` will override existing value on folder entity. - Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. + + Args: + project_name (str): Project name. + representation_id (str): Representation id. + name (Optional[str]): New name. + version_id (Optional[str]): New version id. + files (Optional[list[dict]]): New files + information. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + traits (Optional[dict[str, Any]]): New traits. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. """ con = get_server_api_connection() - return con.get_workfile_info( + return con.update_representation( project_name=project_name, - task_id=task_id, - path=path, - fields=fields, - own_attributes=own_attributes, + representation_id=representation_id, + name=name, + version_id=version_id, + files=files, + attrib=attrib, + data=data, + traits=traits, + tags=tags, + status=status, + active=active, ) -def get_workfile_info_by_id( +def delete_representation( project_name: str, - workfile_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by id. + representation_id: str, +): + """Delete representation. Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Workfile info id. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. + project_name (str): Project name. + representation_id (str): Representation id to delete. """ con = get_server_api_connection() - return con.get_workfile_info_by_id( + return con.delete_representation( project_name=project_name, - workfile_id=workfile_id, - fields=fields, - own_attributes=own_attributes, + representation_id=representation_id, ) diff --git a/ayon_api/_base.py b/ayon_api/_base.py index d98869df0..95021aec7 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -1,14 +1,18 @@ from __future__ import annotations import typing -from typing import Optional, Any +from typing import Optional, Any, Iterable import requests from .utils import TransferProgress, RequestType if typing.TYPE_CHECKING: - from .typing import AnyEntityDict, ServerVersion + from .typing import ( + AnyEntityDict, + ServerVersion, + ProjectDict, + ) _PLACEHOLDER = object() @@ -89,6 +93,14 @@ def get_rest_entity_by_id( ) -> Optional["AnyEntityDict"]: raise NotImplementedError() + def get_project( + self, + project_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, + ) -> Optional["ProjectDict"]: + raise NotImplementedError() + def _prepare_fields( self, entity_type: str, diff --git a/ayon_api/_representations.py b/ayon_api/_representations.py new file mode 100644 index 000000000..a1d7d2ad1 --- /dev/null +++ b/ayon_api/_representations.py @@ -0,0 +1,746 @@ +from __future__ import annotations + +import json +import warnings +import typing +from typing import Optional, Iterable, Generator, Any + +from ._base import _BaseServerAPI, _PLACEHOLDER +from .constants import REPRESENTATION_FILES_FIELDS +from .utils import ( + RepresentationHierarchy, + RepresentationParents, + PatternType, + create_entity_id, +) +from .graphql_queries import ( + representations_graphql_query, + representations_hierarchy_qraphql_query, +) + +if typing.TYPE_CHECKING: + from .typing import RepresentationDict + + +class _RepresentationsAPI(_BaseServerAPI): + def get_rest_representation( + self, project_name: str, representation_id: str + ) -> Optional["RepresentationDict"]: + return self.get_rest_entity_by_id( + project_name, "representation", representation_id + ) + + def get_representations( + self, + project_name: str, + representation_ids: Optional[Iterable[str]] = None, + representation_names: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, + names_by_version_ids: Optional[dict[str, Iterable[str]]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + has_links: Optional[str] = None, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Generator["RepresentationDict", None, None]: + """Get representation entities based on passed filters from server. + + .. todo:: + + Add separated function for 'names_by_version_ids' filtering. + Because can't be combined with others. + + Args: + project_name (str): Name of project where to look for versions. + representation_ids (Optional[Iterable[str]]): Representation ids + used for representation filtering. + representation_names (Optional[Iterable[str]]): Representation + names used for representation filtering. + version_ids (Optional[Iterable[str]]): Version ids used for + representation filtering. Versions are parents of + representations. + names_by_version_ids (Optional[dict[str, Iterable[str]]]): Find + representations by names and version ids. This filter + discards all other filters. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. + + Returns: + Generator[RepresentationDict, None, None]: Queried + representation entities. + + """ + if not fields: + fields = self.get_default_fields_for_type("representation") + else: + fields = set(fields) + self._prepare_fields("representation", fields) + + if active is not None: + fields.add("active") + + if own_attributes is not _PLACEHOLDER: + warnings.warn( + ( + "'own_attributes' is not supported for representations. " + "The argument will be removed form function signature in " + "future (apx. version 1.0.10 or 1.1.0)." + ), + DeprecationWarning + ) + + if "files" in fields: + fields.discard("files") + fields |= REPRESENTATION_FILES_FIELDS + + filters = { + "projectName": project_name + } + + if representation_ids is not None: + representation_ids = set(representation_ids) + if not representation_ids: + return + filters["representationIds"] = list(representation_ids) + + version_ids_filter = None + representation_names_filter = None + if names_by_version_ids is not None: + version_ids_filter = set() + representation_names_filter = set() + for version_id, names in names_by_version_ids.items(): + version_ids_filter.add(version_id) + representation_names_filter |= set(names) + + if not version_ids_filter or not representation_names_filter: + return + + else: + if representation_names is not None: + representation_names_filter = set(representation_names) + if not representation_names_filter: + return + + if version_ids is not None: + version_ids_filter = set(version_ids) + if not version_ids_filter: + return + + if version_ids_filter: + filters["versionIds"] = list(version_ids_filter) + + if representation_names_filter: + filters["representationNames"] = list(representation_names_filter) + + if statuses is not None: + statuses = set(statuses) + if not statuses: + return + filters["representationStatuses"] = list(statuses) + + if tags is not None: + tags = set(tags) + if not tags: + return + filters["representationTags"] = list(tags) + + if has_links is not None: + filters["representationHasLinks"] = has_links.upper() + + query = representations_graphql_query(fields) + + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + for parsed_data in query.continuous_query(self): + for repre in parsed_data["project"]["representations"]: + if active is not None and active is not repre["active"]: + continue + + self._convert_entity_data(repre) + + self._representation_conversion(repre) + + yield repre + + def get_representation_by_id( + self, + project_name: str, + representation_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Optional["RepresentationDict"]: + """Query representation entity from server based on id filter. + + Args: + project_name (str): Project where to look for representation. + representation_id (str): Id of representation. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. + + Returns: + Optional[RepresentationDict]: Queried representation + entity or None. + + """ + representations = self.get_representations( + project_name, + representation_ids=[representation_id], + active=None, + fields=fields, + own_attributes=own_attributes + ) + for representation in representations: + return representation + return None + + def get_representation_by_name( + self, + project_name: str, + representation_name: str, + version_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Optional["RepresentationDict"]: + """Query representation entity by name and version id. + + Args: + project_name (str): Project where to look for representation. + representation_name (str): Representation name. + version_id (str): Version id. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. + + Returns: + Optional[RepresentationDict]: Queried representation entity + or None. + + """ + representations = self.get_representations( + project_name, + representation_names=[representation_name], + version_ids=[version_id], + active=None, + fields=fields, + own_attributes=own_attributes + ) + for representation in representations: + return representation + return None + + def get_representations_hierarchy( + self, + project_name: str, + representation_ids: Iterable[str], + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + task_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + representation_fields: Optional[Iterable[str]] = None, + ) -> dict[str, RepresentationHierarchy]: + """Find representation with parents by representation id. + + Representation entity with parent entities up to project. + + Default fields are used when any fields are set to `None`. But it is + possible to pass in empty iterable (list, set, tuple) to skip + entity. + + Args: + project_name (str): Project where to look for entities. + representation_ids (Iterable[str]): Representation ids. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + task_fields (Optional[Iterable[str]]): Task fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + representation_fields (Optional[Iterable[str]]): Representation + fields. + + Returns: + dict[str, RepresentationHierarchy]: Parent entities by + representation id. + + """ + if not representation_ids: + return {} + + if project_fields is not None: + project_fields = set(project_fields) + self._prepare_fields("project", project_fields) + + project = {} + if project_fields is None: + project = self.get_project(project_name) + + elif project_fields: + # Keep project as empty dictionary if does not have + # filled any fields + project = self.get_project( + project_name, fields=project_fields + ) + + repre_ids = set(representation_ids) + output = { + repre_id: RepresentationHierarchy( + project, None, None, None, None, None + ) + for repre_id in representation_ids + } + + if folder_fields is None: + folder_fields = self.get_default_fields_for_type("folder") + else: + folder_fields = set(folder_fields) + + if task_fields is None: + task_fields = self.get_default_fields_for_type("task") + else: + task_fields = set(task_fields) + + if product_fields is None: + product_fields = self.get_default_fields_for_type("product") + else: + product_fields = set(product_fields) + + if version_fields is None: + version_fields = self.get_default_fields_for_type("version") + else: + version_fields = set(version_fields) + + if representation_fields is None: + representation_fields = self.get_default_fields_for_type( + "representation" + ) + else: + representation_fields = set(representation_fields) + + for (entity_type, fields) in ( + ("folder", folder_fields), + ("task", task_fields), + ("product", product_fields), + ("version", version_fields), + ("representation", representation_fields), + ): + self._prepare_fields(entity_type, fields) + + representation_fields.add("id") + + query = representations_hierarchy_qraphql_query( + folder_fields, + task_fields, + product_fields, + version_fields, + representation_fields, + ) + query.set_variable_value("projectName", project_name) + query.set_variable_value("representationIds", list(repre_ids)) + + parsed_data = query.query(self) + for repre in parsed_data["project"]["representations"]: + repre_id = repre["id"] + version = repre.pop("version", {}) + product = version.pop("product", {}) + task = version.pop("task", None) + folder = product.pop("folder", {}) + self._convert_entity_data(repre) + self._representation_conversion(repre) + self._convert_entity_data(version) + self._convert_entity_data(product) + self._convert_entity_data(folder) + if task: + self._convert_entity_data(task) + + output[repre_id] = RepresentationHierarchy( + project, folder, task, product, version, repre + ) + + return output + + def get_representation_hierarchy( + self, + project_name: str, + representation_id: str, + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + task_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + representation_fields: Optional[Iterable[str]] = None, + ) -> Optional[RepresentationHierarchy]: + """Find representation parents by representation id. + + Representation parent entities up to project. + + Args: + project_name (str): Project where to look for entities. + representation_id (str): Representation id. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + task_fields (Optional[Iterable[str]]): Task fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + representation_fields (Optional[Iterable[str]]): Representation + fields. + + Returns: + RepresentationHierarchy: Representation hierarchy entities. + + """ + if not representation_id: + return None + + parents_by_repre_id = self.get_representations_hierarchy( + project_name, + [representation_id], + project_fields=project_fields, + folder_fields=folder_fields, + task_fields=task_fields, + product_fields=product_fields, + version_fields=version_fields, + representation_fields=representation_fields, + ) + return parents_by_repre_id[representation_id] + + def get_representations_parents( + self, + project_name: str, + representation_ids: Iterable[str], + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + ) -> dict[str, RepresentationParents]: + """Find representations parents by representation id. + + Representation parent entities up to project. + + Args: + project_name (str): Project where to look for entities. + representation_ids (Iterable[str]): Representation ids. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + + Returns: + dict[str, RepresentationParents]: Parent entities by + representation id. + + """ + hierarchy_by_repre_id = self.get_representations_hierarchy( + project_name, + representation_ids, + project_fields=project_fields, + folder_fields=folder_fields, + task_fields=set(), + product_fields=product_fields, + version_fields=version_fields, + representation_fields={"id"}, + ) + return { + repre_id: RepresentationParents( + hierarchy.version, + hierarchy.product, + hierarchy.folder, + hierarchy.project, + ) + for repre_id, hierarchy in hierarchy_by_repre_id.items() + } + + def get_representation_parents( + self, + project_name: str, + representation_id: str, + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + ) -> Optional["RepresentationParents"]: + """Find representation parents by representation id. + + Representation parent entities up to project. + + Args: + project_name (str): Project where to look for entities. + representation_id (str): Representation id. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + + Returns: + RepresentationParents: Representation parent entities. + + """ + if not representation_id: + return None + + parents_by_repre_id = self.get_representations_parents( + project_name, + [representation_id], + project_fields=project_fields, + folder_fields=folder_fields, + product_fields=product_fields, + version_fields=version_fields, + ) + return parents_by_repre_id[representation_id] + + def get_repre_ids_by_context_filters( + self, + project_name: str, + context_filters: Optional[dict[str, Iterable[str]]], + representation_names: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, + ) -> list[str]: + """Find representation ids which match passed context filters. + + Each representation has context integrated on representation entity in + database. The context may contain project, folder, task name or + product name, product type and many more. This implementation gives + option to quickly filter representation based on representation data + in database. + + Context filters have defined structure. To define filter of nested + subfield use dot '.' as delimiter (For example 'task.name'). + Filter values can be regex filters. String or ``re.Pattern`` can + be used. + + Args: + project_name (str): Project where to look for representations. + context_filters (dict[str, list[str]]): Filters of context fields. + representation_names (Optional[Iterable[str]]): Representation + names, can be used as additional filter for representations + by their names. + version_ids (Optional[Iterable[str]]): Version ids, can be used + as additional filter for representations by their parent ids. + + Returns: + list[str]: Representation ids that match passed filters. + + Example: + The function returns just representation ids so if entities are + required for funtionality they must be queried afterwards by + their ids. + >>> from ayon_api import get_repre_ids_by_context_filters + >>> from ayon_api import get_representations + >>> project_name = "testProject" + >>> filters = { + ... "task.name": ["[aA]nimation"], + ... "product": [".*[Mm]ain"] + ... } + >>> repre_ids = get_repre_ids_by_context_filters( + ... project_name, filters) + >>> repres = get_representations(project_name, repre_ids) + + """ + if not isinstance(context_filters, dict): + raise TypeError( + f"Expected 'dict' got {str(type(context_filters))}" + ) + + filter_body = {} + if representation_names is not None: + if not representation_names: + return [] + filter_body["names"] = list(set(representation_names)) + + if version_ids is not None: + if not version_ids: + return [] + filter_body["versionIds"] = list(set(version_ids)) + + body_context_filters = [] + for key, filters in context_filters.items(): + if not isinstance(filters, (set, list, tuple)): + raise TypeError( + "Expected 'set', 'list', 'tuple' got {}".format( + str(type(filters)))) + + new_filters = set() + for filter_value in filters: + if isinstance(filter_value, PatternType): + filter_value = filter_value.pattern + new_filters.add(filter_value) + + body_context_filters.append({ + "key": key, + "values": list(new_filters) + }) + + response = self.post( + f"projects/{project_name}/repreContextFilter", + context=body_context_filters, + **filter_body + ) + response.raise_for_status() + return response.data["ids"] + + def create_representation( + self, + project_name: str, + name: str, + version_id: str, + files: Optional[list[dict[str, Any]]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + traits: Optional[dict[str, Any]] = None, + tags: Optional[list[str]]=None, + status: Optional[str] = None, + active: Optional[bool] = None, + representation_id: Optional[str] = None, + ) -> str: + """Create new representation. + + Args: + project_name (str): Project name. + name (str): Representation name. + version_id (str): Parent version id. + files (Optional[list[dict]]): Representation files information. + attrib (Optional[dict[str, Any]]): Representation attributes. + data (Optional[dict[str, Any]]): Representation data. + traits (Optional[dict[str, Any]]): Representation traits + serialized data as dict. + tags (Optional[Iterable[str]]): Representation tags. + status (Optional[str]): Representation status. + active (Optional[bool]): Representation active state. + representation_id (Optional[str]): Representation id. If not + passed new id is generated. + + Returns: + str: Representation id. + + """ + if not representation_id: + representation_id = create_entity_id() + create_data = { + "id": representation_id, + "name": name, + "versionId": version_id, + } + for key, value in ( + ("files", files), + ("attrib", attrib), + ("data", data), + ("traits", traits), + ("tags", tags), + ("status", status), + ("active", active), + ): + if value is not None: + create_data[key] = value + + response = self.post( + f"projects/{project_name}/representations", + **create_data + ) + response.raise_for_status() + return representation_id + + def update_representation( + self, + project_name: str, + representation_id: str, + name: Optional[str] = None, + version_id: Optional[str] = None, + files: Optional[list[dict[str, Any]]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + traits: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + ): + """Update representation entity on server. + + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. + + Args: + project_name (str): Project name. + representation_id (str): Representation id. + name (Optional[str]): New name. + version_id (Optional[str]): New version id. + files (Optional[list[dict]]): New files + information. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + traits (Optional[dict[str, Any]]): New traits. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + + """ + update_data = {} + for key, value in ( + ("name", name), + ("versionId", version_id), + ("files", files), + ("attrib", attrib), + ("data", data), + ("traits", traits), + ("tags", tags), + ("status", status), + ("active", active), + ): + if value is not None: + update_data[key] = value + + response = self.patch( + f"projects/{project_name}/representations/{representation_id}", + **update_data + ) + response.raise_for_status() + + def delete_representation( + self, project_name: str, representation_id: str + ): + """Delete representation. + + Args: + project_name (str): Project name. + representation_id (str): Representation id to delete. + + """ + response = self.delete( + f"projects/{project_name}/representations/{representation_id}" + ) + response.raise_for_status() + + def _representation_conversion( + self, representation: "RepresentationDict" + ): + if "context" in representation: + orig_context = representation["context"] + context = {} + if orig_context and orig_context != "null": + context = json.loads(orig_context) + representation["context"] = context + + repre_files = representation.get("files") + if not repre_files: + return + + for repre_file in repre_files: + repre_file_size = repre_file.get("size") + if repre_file_size is not None: + repre_file["size"] = int(repre_file["size"]) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index b2d58d47f..4a2767255 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -80,7 +80,6 @@ get_machine_name, fill_own_attribs, prepare_list_filters, - PatternType, ) from ._base import _PLACEHOLDER from ._actions import _ActionsAPI @@ -93,6 +92,7 @@ from ._projects import _ProjectsAPI from ._thumbnails import _ThumbnailsAPI from ._workfiles import _WorkfilesAPI +from ._representations import _RepresentationsAPI if typing.TYPE_CHECKING: from typing import Union @@ -237,12 +237,13 @@ class ServerAPI( _ActivitiesAPI, _AddonsAPI, _EventsAPI, + _ProjectsAPI, _FoldersAPI, + _RepresentationsAPI, + _WorkfilesAPI, _LinksAPI, _ListsAPI, - _ProjectsAPI, _ThumbnailsAPI, - _WorkfilesAPI, ): """Base handler of connection to server. @@ -3439,13 +3440,6 @@ def get_rest_version( ) -> Optional["VersionDict"]: return self.get_rest_entity_by_id(project_name, "version", version_id) - def get_rest_representation( - self, project_name: str, representation_id: str - ) -> Optional["RepresentationDict"]: - return self.get_rest_entity_by_id( - project_name, "representation", representation_id - ) - def get_tasks( self, project_name: str, @@ -5024,719 +5018,6 @@ def delete_version(self, project_name: str, version_id: str): ) response.raise_for_status() - def _representation_conversion( - self, representation: "RepresentationDict" - ): - if "context" in representation: - orig_context = representation["context"] - context = {} - if orig_context and orig_context != "null": - context = json.loads(orig_context) - representation["context"] = context - - repre_files = representation.get("files") - if not repre_files: - return - - for repre_file in repre_files: - repre_file_size = repre_file.get("size") - if repre_file_size is not None: - repre_file["size"] = int(repre_file["size"]) - - def get_representations( - self, - project_name: str, - representation_ids: Optional[Iterable[str]] = None, - representation_names: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - names_by_version_ids: Optional[Dict[str, Iterable[str]]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - has_links: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Generator["RepresentationDict", None, None]: - """Get representation entities based on passed filters from server. - - .. todo:: - - Add separated function for 'names_by_version_ids' filtering. - Because can't be combined with others. - - Args: - project_name (str): Name of project where to look for versions. - representation_ids (Optional[Iterable[str]]): Representation ids - used for representation filtering. - representation_names (Optional[Iterable[str]]): Representation - names used for representation filtering. - version_ids (Optional[Iterable[str]]): Version ids used for - representation filtering. Versions are parents of - representations. - names_by_version_ids (Optional[Dict[str, Iterable[str]]]): Find - representations by names and version ids. This filter - discards all other filters. - statuses (Optional[Iterable[str]]): Representation statuses used - for filtering. - tags (Optional[Iterable[str]]): Representation tags used - for filtering. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. - - Returns: - Generator[RepresentationDict, None, None]: Queried - representation entities. - - """ - if not fields: - fields = self.get_default_fields_for_type("representation") - else: - fields = set(fields) - self._prepare_fields("representation", fields) - - if active is not None: - fields.add("active") - - if own_attributes is not _PLACEHOLDER: - warnings.warn( - ( - "'own_attributes' is not supported for representations. " - "The argument will be removed form function signature in " - "future (apx. version 1.0.10 or 1.1.0)." - ), - DeprecationWarning - ) - - if "files" in fields: - fields.discard("files") - fields |= REPRESENTATION_FILES_FIELDS - - filters = { - "projectName": project_name - } - - if representation_ids is not None: - representation_ids = set(representation_ids) - if not representation_ids: - return - filters["representationIds"] = list(representation_ids) - - version_ids_filter = None - representation_names_filter = None - if names_by_version_ids is not None: - version_ids_filter = set() - representation_names_filter = set() - for version_id, names in names_by_version_ids.items(): - version_ids_filter.add(version_id) - representation_names_filter |= set(names) - - if not version_ids_filter or not representation_names_filter: - return - - else: - if representation_names is not None: - representation_names_filter = set(representation_names) - if not representation_names_filter: - return - - if version_ids is not None: - version_ids_filter = set(version_ids) - if not version_ids_filter: - return - - if version_ids_filter: - filters["versionIds"] = list(version_ids_filter) - - if representation_names_filter: - filters["representationNames"] = list(representation_names_filter) - - if statuses is not None: - statuses = set(statuses) - if not statuses: - return - filters["representationStatuses"] = list(statuses) - - if tags is not None: - tags = set(tags) - if not tags: - return - filters["representationTags"] = list(tags) - - if has_links is not None: - filters["representationHasLinks"] = has_links.upper() - - query = representations_graphql_query(fields) - - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - for parsed_data in query.continuous_query(self): - for repre in parsed_data["project"]["representations"]: - if active is not None and active is not repre["active"]: - continue - - self._convert_entity_data(repre) - - self._representation_conversion(repre) - - yield repre - - def get_representation_by_id( - self, - project_name: str, - representation_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Optional["RepresentationDict"]: - """Query representation entity from server based on id filter. - - Args: - project_name (str): Project where to look for representation. - representation_id (str): Id of representation. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. - - Returns: - Optional[RepresentationDict]: Queried representation - entity or None. - - """ - representations = self.get_representations( - project_name, - representation_ids=[representation_id], - active=None, - fields=fields, - own_attributes=own_attributes - ) - for representation in representations: - return representation - return None - - def get_representation_by_name( - self, - project_name: str, - representation_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Optional["RepresentationDict"]: - """Query representation entity by name and version id. - - Args: - project_name (str): Project where to look for representation. - representation_name (str): Representation name. - version_id (str): Version id. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. - - Returns: - Optional[RepresentationDict]: Queried representation entity - or None. - - """ - representations = self.get_representations( - project_name, - representation_names=[representation_name], - version_ids=[version_id], - active=None, - fields=fields, - own_attributes=own_attributes - ) - for representation in representations: - return representation - return None - - def get_representations_hierarchy( - self, - project_name: str, - representation_ids: Iterable[str], - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - task_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - representation_fields: Optional[Iterable[str]] = None, - ) -> Dict[str, RepresentationHierarchy]: - """Find representation with parents by representation id. - - Representation entity with parent entities up to project. - - Default fields are used when any fields are set to `None`. But it is - possible to pass in empty iterable (list, set, tuple) to skip - entity. - - Args: - project_name (str): Project where to look for entities. - representation_ids (Iterable[str]): Representation ids. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - task_fields (Optional[Iterable[str]]): Task fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - representation_fields (Optional[Iterable[str]]): Representation - fields. - - Returns: - dict[str, RepresentationHierarchy]: Parent entities by - representation id. - - """ - if not representation_ids: - return {} - - if project_fields is not None: - project_fields = set(project_fields) - self._prepare_fields("project", project_fields) - - project = {} - if project_fields is None: - project = self.get_project(project_name) - - elif project_fields: - # Keep project as empty dictionary if does not have - # filled any fields - project = self.get_project( - project_name, fields=project_fields - ) - - repre_ids = set(representation_ids) - output = { - repre_id: RepresentationHierarchy( - project, None, None, None, None, None - ) - for repre_id in representation_ids - } - - if folder_fields is None: - folder_fields = self.get_default_fields_for_type("folder") - else: - folder_fields = set(folder_fields) - - if task_fields is None: - task_fields = self.get_default_fields_for_type("task") - else: - task_fields = set(task_fields) - - if product_fields is None: - product_fields = self.get_default_fields_for_type("product") - else: - product_fields = set(product_fields) - - if version_fields is None: - version_fields = self.get_default_fields_for_type("version") - else: - version_fields = set(version_fields) - - if representation_fields is None: - representation_fields = self.get_default_fields_for_type( - "representation" - ) - else: - representation_fields = set(representation_fields) - - for (entity_type, fields) in ( - ("folder", folder_fields), - ("task", task_fields), - ("product", product_fields), - ("version", version_fields), - ("representation", representation_fields), - ): - self._prepare_fields(entity_type, fields) - - representation_fields.add("id") - - query = representations_hierarchy_qraphql_query( - folder_fields, - task_fields, - product_fields, - version_fields, - representation_fields, - ) - query.set_variable_value("projectName", project_name) - query.set_variable_value("representationIds", list(repre_ids)) - - parsed_data = query.query(self) - for repre in parsed_data["project"]["representations"]: - repre_id = repre["id"] - version = repre.pop("version", {}) - product = version.pop("product", {}) - task = version.pop("task", None) - folder = product.pop("folder", {}) - self._convert_entity_data(repre) - self._representation_conversion(repre) - self._convert_entity_data(version) - self._convert_entity_data(product) - self._convert_entity_data(folder) - if task: - self._convert_entity_data(task) - - output[repre_id] = RepresentationHierarchy( - project, folder, task, product, version, repre - ) - - return output - - def get_representation_hierarchy( - self, - project_name: str, - representation_id: str, - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - task_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - representation_fields: Optional[Iterable[str]] = None, - ) -> Optional[RepresentationHierarchy]: - """Find representation parents by representation id. - - Representation parent entities up to project. - - Args: - project_name (str): Project where to look for entities. - representation_id (str): Representation id. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - task_fields (Optional[Iterable[str]]): Task fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - representation_fields (Optional[Iterable[str]]): Representation - fields. - - Returns: - RepresentationHierarchy: Representation hierarchy entities. - - """ - if not representation_id: - return None - - parents_by_repre_id = self.get_representations_hierarchy( - project_name, - [representation_id], - project_fields=project_fields, - folder_fields=folder_fields, - task_fields=task_fields, - product_fields=product_fields, - version_fields=version_fields, - representation_fields=representation_fields, - ) - return parents_by_repre_id[representation_id] - - def get_representations_parents( - self, - project_name: str, - representation_ids: Iterable[str], - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - ) -> Dict[str, RepresentationParents]: - """Find representations parents by representation id. - - Representation parent entities up to project. - - Args: - project_name (str): Project where to look for entities. - representation_ids (Iterable[str]): Representation ids. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - - Returns: - dict[str, RepresentationParents]: Parent entities by - representation id. - - """ - hierarchy_by_repre_id = self.get_representations_hierarchy( - project_name, - representation_ids, - project_fields=project_fields, - folder_fields=folder_fields, - task_fields=set(), - product_fields=product_fields, - version_fields=version_fields, - representation_fields={"id"}, - ) - return { - repre_id: RepresentationParents( - hierarchy.version, - hierarchy.product, - hierarchy.folder, - hierarchy.project, - ) - for repre_id, hierarchy in hierarchy_by_repre_id.items() - } - - def get_representation_parents( - self, - project_name: str, - representation_id: str, - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - ) -> Optional["RepresentationParents"]: - """Find representation parents by representation id. - - Representation parent entities up to project. - - Args: - project_name (str): Project where to look for entities. - representation_id (str): Representation id. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - - Returns: - RepresentationParents: Representation parent entities. - - """ - if not representation_id: - return None - - parents_by_repre_id = self.get_representations_parents( - project_name, - [representation_id], - project_fields=project_fields, - folder_fields=folder_fields, - product_fields=product_fields, - version_fields=version_fields, - ) - return parents_by_repre_id[representation_id] - - def get_repre_ids_by_context_filters( - self, - project_name: str, - context_filters: Optional[Dict[str, Iterable[str]]], - representation_names: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - ) -> List[str]: - """Find representation ids which match passed context filters. - - Each representation has context integrated on representation entity in - database. The context may contain project, folder, task name or - product name, product type and many more. This implementation gives - option to quickly filter representation based on representation data - in database. - - Context filters have defined structure. To define filter of nested - subfield use dot '.' as delimiter (For example 'task.name'). - Filter values can be regex filters. String or ``re.Pattern`` can - be used. - - Args: - project_name (str): Project where to look for representations. - context_filters (dict[str, list[str]]): Filters of context fields. - representation_names (Optional[Iterable[str]]): Representation - names, can be used as additional filter for representations - by their names. - version_ids (Optional[Iterable[str]]): Version ids, can be used - as additional filter for representations by their parent ids. - - Returns: - list[str]: Representation ids that match passed filters. - - Example: - The function returns just representation ids so if entities are - required for funtionality they must be queried afterwards by - their ids. - >>> project_name = "testProject" - >>> filters = { - ... "task.name": ["[aA]nimation"], - ... "product": [".*[Mm]ain"] - ... } - >>> repre_ids = get_repre_ids_by_context_filters( - ... project_name, filters) - >>> repres = get_representations(project_name, repre_ids) - - """ - if not isinstance(context_filters, dict): - raise TypeError( - f"Expected 'dict' got {str(type(context_filters))}" - ) - - filter_body = {} - if representation_names is not None: - if not representation_names: - return [] - filter_body["names"] = list(set(representation_names)) - - if version_ids is not None: - if not version_ids: - return [] - filter_body["versionIds"] = list(set(version_ids)) - - body_context_filters = [] - for key, filters in context_filters.items(): - if not isinstance(filters, (set, list, tuple)): - raise TypeError( - "Expected 'set', 'list', 'tuple' got {}".format( - str(type(filters)))) - - new_filters = set() - for filter_value in filters: - if isinstance(filter_value, PatternType): - filter_value = filter_value.pattern - new_filters.add(filter_value) - - body_context_filters.append({ - "key": key, - "values": list(new_filters) - }) - - response = self.post( - f"projects/{project_name}/repreContextFilter", - context=body_context_filters, - **filter_body - ) - response.raise_for_status() - return response.data["ids"] - - def create_representation( - self, - project_name: str, - name: str, - version_id: str, - files: Optional[List[Dict[str, Any]]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - traits: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]]=None, - status: Optional[str] = None, - active: Optional[bool] = None, - representation_id: Optional[str] = None, - ) -> str: - """Create new representation. - - Args: - project_name (str): Project name. - name (str): Representation name. - version_id (str): Parent version id. - files (Optional[list[dict]]): Representation files information. - attrib (Optional[dict[str, Any]]): Representation attributes. - data (Optional[dict[str, Any]]): Representation data. - traits (Optional[dict[str, Any]]): Representation traits - serialized data as dict. - tags (Optional[Iterable[str]]): Representation tags. - status (Optional[str]): Representation status. - active (Optional[bool]): Representation active state. - representation_id (Optional[str]): Representation id. If not - passed new id is generated. - - Returns: - str: Representation id. - - """ - if not representation_id: - representation_id = create_entity_id() - create_data = { - "id": representation_id, - "name": name, - "versionId": version_id, - } - for key, value in ( - ("files", files), - ("attrib", attrib), - ("data", data), - ("traits", traits), - ("tags", tags), - ("status", status), - ("active", active), - ): - if value is not None: - create_data[key] = value - - response = self.post( - f"projects/{project_name}/representations", - **create_data - ) - response.raise_for_status() - return representation_id - - def update_representation( - self, - project_name: str, - representation_id: str, - name: Optional[str] = None, - version_id: Optional[str] = None, - files: Optional[List[Dict[str, Any]]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - traits: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - ): - """Update representation entity on server. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. - - Args: - project_name (str): Project name. - representation_id (str): Representation id. - name (Optional[str]): New name. - version_id (Optional[str]): New version id. - files (Optional[list[dict]]): New files - information. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - traits (Optional[dict[str, Any]]): New traits. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - - """ - update_data = {} - for key, value in ( - ("name", name), - ("versionId", version_id), - ("files", files), - ("attrib", attrib), - ("data", data), - ("traits", traits), - ("tags", tags), - ("status", status), - ("active", active), - ): - if value is not None: - update_data[key] = value - - response = self.patch( - f"projects/{project_name}/representations/{representation_id}", - **update_data - ) - response.raise_for_status() - - def delete_representation( - self, project_name: str, representation_id: str - ): - """Delete representation. - - Args: - project_name (str): Project name. - representation_id (str): Representation id to delete. - - """ - response = self.delete( - f"projects/{project_name}/representations/{representation_id}" - ) - response.raise_for_status() - # --- Batch operations processing --- def send_batch_operations( self, From 11ba7d881e9b436eb5e240cb67523d6338e6acd8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 19:51:36 +0200 Subject: [PATCH 25/53] moved tasks, products and versions --- automated_api.py | 8 +- ayon_api/__init__.py | 184 +- ayon_api/_api.py | 4842 ++++++++++++++++++++-------------------- ayon_api/_products.py | 501 +++++ ayon_api/_tasks.py | 514 +++++ ayon_api/_versions.py | 639 ++++++ ayon_api/server_api.py | 1620 +------------- 7 files changed, 4184 insertions(+), 4124 deletions(-) create mode 100644 ayon_api/_products.py create mode 100644 ayon_api/_tasks.py create mode 100644 ayon_api/_versions.py diff --git a/automated_api.py b/automated_api.py index a581dde58..e9804adfd 100644 --- a/automated_api.py +++ b/automated_api.py @@ -340,6 +340,9 @@ def prepare_api_functions(api_globals): _AddonsAPI, _EventsAPI, _FoldersAPI, + _TasksAPI, + _ProductsAPI, + _VersionsAPI, _LinksAPI, _ListsAPI, _ProjectsAPI, @@ -354,10 +357,13 @@ def prepare_api_functions(api_globals): _items.extend(_ActivitiesAPI.__dict__.items()) _items.extend(_AddonsAPI.__dict__.items()) _items.extend(_EventsAPI.__dict__.items()) - _items.extend(_FoldersAPI.__dict__.items()) _items.extend(_LinksAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) _items.extend(_ProjectsAPI.__dict__.items()) + _items.extend(_FoldersAPI.__dict__.items()) + _items.extend(_TasksAPI.__dict__.items()) + _items.extend(_ProductsAPI.__dict__.items()) + _items.extend(_VersionsAPI.__dict__.items()) _items.extend(_ThumbnailsAPI.__dict__.items()) _items.extend(_WorkfilesAPI.__dict__.items()) _items.extend(_RepresentationsAPI.__dict__.items()) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index dd7b1a053..a12e27624 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -127,40 +127,6 @@ save_secret, delete_secret, get_rest_entity_by_id, - get_rest_task, - get_rest_product, - get_rest_version, - get_tasks, - get_task_by_name, - get_task_by_id, - get_tasks_by_folder_paths, - get_tasks_by_folder_path, - get_task_by_folder_path, - create_task, - update_task, - delete_task, - get_products, - get_product_by_id, - get_product_by_name, - get_product_types, - get_project_product_types, - get_product_type_names, - create_product, - update_product, - delete_product, - get_versions, - get_version_by_id, - get_version_by_name, - get_hero_version_by_id, - get_hero_version_by_product_id, - get_hero_versions, - get_last_versions, - get_last_version_by_product_id, - get_last_version_by_product_name, - version_is_latest, - create_version, - update_version, - delete_version, send_batch_operations, get_actions, trigger_action, @@ -187,18 +153,6 @@ dispatch_event, delete_event, enroll_event_job, - get_rest_folder, - get_rest_folders, - get_folders_hierarchy, - get_folders_rest, - get_folders, - get_folder_by_id, - get_folder_by_path, - get_folder_by_name, - get_folder_ids_with_products, - create_folder, - update_folder, - delete_folder, get_full_link_type_name, get_link_types, get_link_type, @@ -238,6 +192,52 @@ create_project, update_project, delete_project, + get_rest_folder, + get_rest_folders, + get_folders_hierarchy, + get_folders_rest, + get_folders, + get_folder_by_id, + get_folder_by_path, + get_folder_by_name, + get_folder_ids_with_products, + create_folder, + update_folder, + delete_folder, + get_rest_task, + get_tasks, + get_task_by_name, + get_task_by_id, + get_tasks_by_folder_paths, + get_tasks_by_folder_path, + get_task_by_folder_path, + create_task, + update_task, + delete_task, + get_rest_product, + get_products, + get_product_by_id, + get_product_by_name, + get_product_types, + get_project_product_types, + get_product_type_names, + create_product, + update_product, + delete_product, + get_rest_version, + get_versions, + get_version_by_id, + get_version_by_name, + get_hero_version_by_id, + get_hero_version_by_product_id, + get_hero_versions, + get_last_versions, + get_last_version_by_product_id, + get_last_version_by_product_name, + version_is_latest, + create_version, + update_version, + delete_version, get_thumbnail_by_id, get_thumbnail, get_folder_thumbnail, @@ -391,40 +391,6 @@ "save_secret", "delete_secret", "get_rest_entity_by_id", - "get_rest_task", - "get_rest_product", - "get_rest_version", - "get_tasks", - "get_task_by_name", - "get_task_by_id", - "get_tasks_by_folder_paths", - "get_tasks_by_folder_path", - "get_task_by_folder_path", - "create_task", - "update_task", - "delete_task", - "get_products", - "get_product_by_id", - "get_product_by_name", - "get_product_types", - "get_project_product_types", - "get_product_type_names", - "create_product", - "update_product", - "delete_product", - "get_versions", - "get_version_by_id", - "get_version_by_name", - "get_hero_version_by_id", - "get_hero_version_by_product_id", - "get_hero_versions", - "get_last_versions", - "get_last_version_by_product_id", - "get_last_version_by_product_name", - "version_is_latest", - "create_version", - "update_version", - "delete_version", "send_batch_operations", "get_actions", "trigger_action", @@ -451,18 +417,6 @@ "dispatch_event", "delete_event", "enroll_event_job", - "get_rest_folder", - "get_rest_folders", - "get_folders_hierarchy", - "get_folders_rest", - "get_folders", - "get_folder_by_id", - "get_folder_by_path", - "get_folder_by_name", - "get_folder_ids_with_products", - "create_folder", - "update_folder", - "delete_folder", "get_full_link_type_name", "get_link_types", "get_link_type", @@ -502,6 +456,52 @@ "create_project", "update_project", "delete_project", + "get_rest_folder", + "get_rest_folders", + "get_folders_hierarchy", + "get_folders_rest", + "get_folders", + "get_folder_by_id", + "get_folder_by_path", + "get_folder_by_name", + "get_folder_ids_with_products", + "create_folder", + "update_folder", + "delete_folder", + "get_rest_task", + "get_tasks", + "get_task_by_name", + "get_task_by_id", + "get_tasks_by_folder_paths", + "get_tasks_by_folder_path", + "get_task_by_folder_path", + "create_task", + "update_task", + "delete_task", + "get_rest_product", + "get_products", + "get_product_by_id", + "get_product_by_name", + "get_product_types", + "get_project_product_types", + "get_product_type_names", + "create_product", + "update_product", + "delete_product", + "get_rest_version", + "get_versions", + "get_version_by_id", + "get_version_by_name", + "get_hero_version_by_id", + "get_hero_version_by_product_id", + "get_hero_versions", + "get_last_versions", + "get_last_version_by_product_id", + "get_last_version_by_product_name", + "version_is_latest", + "create_version", + "update_version", + "delete_version", "get_thumbnail_by_id", "get_thumbnail", "get_folder_thumbnail", diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 6df3bebb3..8ae32db4d 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -2580,2208 +2580,2169 @@ def get_rest_entity_by_id( ) -def get_rest_task( +def send_batch_operations( project_name: str, - task_id: str, -) -> Optional["TaskDict"]: + operations: List[Dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True, +) -> List[Dict[str, Any]]: + """Post multiple CRUD operations to server. + + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. + + Args: + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. + + Returns: + list[dict[str, Any]]: Operations result with process details. + + """ con = get_server_api_connection() - return con.get_rest_task( + return con.send_batch_operations( project_name=project_name, - task_id=task_id, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) -def get_rest_product( - project_name: str, - product_id: str, -) -> Optional["ProductDict"]: +def get_actions( + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, + mode: Optional["ActionModeType"] = None, +) -> List["ActionManifestDict"]: + """Get actions for a context. + + Args: + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + mode (Optional[ActionModeType]): Action modes. + + Returns: + List[ActionManifestDict]: List of action manifests. + + """ con = get_server_api_connection() - return con.get_rest_product( + return con.get_actions( project_name=project_name, - product_id=product_id, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + mode=mode, ) -def get_rest_version( - project_name: str, - version_id: str, -) -> Optional["VersionDict"]: +def trigger_action( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionTriggerResponse": + """Trigger action. + + Args: + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + + """ con = get_server_api_connection() - return con.get_rest_version( + return con.trigger_action( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, project_name=project_name, - version_id=version_id, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def get_tasks( +def get_action_config( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Get action configuration. + + Args: + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + + Returns: + ActionConfigResponse: Action configuration data. + + """ + con = get_server_api_connection() + return con.get_action_config( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, + project_name=project_name, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + ) + + +def set_action_config( + identifier: str, + addon_name: str, + addon_version: str, + value: Dict[str, Any], + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[List[str]] = None, + entity_subtypes: Optional[List[str]] = None, + form_data: Optional[Dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Set action configuration. + + Args: + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + value (Optional[Dict[str, Any]]): Value of the action + configuration. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[List[str]]): List of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[List[str]]): List of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[Dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + + Returns: + ActionConfigResponse: New action configuration data. + + """ + con = get_server_api_connection() + return con.set_action_config( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, + value=value, + project_name=project_name, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + ) + + +def take_action( + action_token: str, +) -> "ActionTakeResponse": + """Take action metadata using an action token. + + Args: + action_token (str): AYON launcher action token. + + Returns: + ActionTakeResponse: Action metadata describing how to launch + action. + + """ + con = get_server_api_connection() + return con.take_action( + action_token=action_token, + ) + + +def abort_action( + action_token: str, + message: Optional[str] = None, +) -> None: + """Abort action using an action token. + + Args: + action_token (str): AYON launcher action token. + message (Optional[str]): Message to display in the UI. + + """ + con = get_server_api_connection() + return con.abort_action( + action_token=action_token, + message=message, + ) + + +def get_activities( project_name: str, - task_ids: Optional[Iterable[str]] = None, - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - folder_ids: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, + activity_ids: Optional[Iterable[str]] = None, + activity_types: Optional[Iterable["ActivityType"]] = None, + entity_ids: Optional[Iterable[str]] = None, + entity_names: Optional[Iterable[str]] = None, + entity_type: Optional[str] = None, + changed_after: Optional[str] = None, + changed_before: Optional[str] = None, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Generator["TaskDict", None, None]: - """Query task entities from server. + limit: Optional[int] = None, + order: Optional[SortOrder] = None, +) -> Generator[dict[str, Any], None, None]: + """Get activities from server with filtering options. Args: - project_name (str): Name of project. - task_ids (Iterable[str]): Task ids to filter. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - folder_ids (Iterable[str]): Ids of task parents. Use 'None' - if folder is direct child of project. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project on which activities happened. + activity_ids (Optional[Iterable[str]]): Activity ids. + activity_types (Optional[Iterable[ActivityType]]): Activity types. + entity_ids (Optional[Iterable[str]]): Entity ids. + entity_names (Optional[Iterable[str]]): Entity names. + entity_type (Optional[str]): Entity type. + changed_after (Optional[str]): Return only activities changed + after given iso datetime string. + changed_before (Optional[str]): Return only activities changed + before given iso datetime string. + reference_types (Optional[Iterable[ActivityReferenceType]]): + Reference types filter. Defaults to `['origin']`. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. + limit (Optional[int]): Limit number of activities to be fetched. + order (Optional[SortOrder]): Order activities in ascending + or descending order. It is recommended to set 'limit' + when used descending. Returns: - Generator[TaskDict, None, None]: Queried task entities. + Generator[dict[str, Any]]: Available activities matching filters. """ con = get_server_api_connection() - return con.get_tasks( + return con.get_activities( project_name=project_name, - task_ids=task_ids, - task_names=task_names, - task_types=task_types, - folder_ids=folder_ids, - assignees=assignees, - assignees_all=assignees_all, - statuses=statuses, - tags=tags, - active=active, + activity_ids=activity_ids, + activity_types=activity_types, + entity_ids=entity_ids, + entity_names=entity_names, + entity_type=entity_type, + changed_after=changed_after, + changed_before=changed_before, + reference_types=reference_types, fields=fields, - own_attributes=own_attributes, + limit=limit, + order=order, ) -def get_task_by_name( +def get_activity_by_id( project_name: str, - folder_id: str, - task_name: str, + activity_id: str, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by name and folder id. +) -> Optional[dict[str, Any]]: + """Get activity by id. Args: - project_name (str): Name of project where to look for queried - entities. - folder_id (str): Folder id. - task_name (str): Task name - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + reference_types: Optional[Iterable[ActivityReferenceType]]: Filter + by reference types. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + Optional[dict[str, Any]]: Activity data or None if activity is not + found. """ con = get_server_api_connection() - return con.get_task_by_name( + return con.get_activity_by_id( project_name=project_name, - folder_id=folder_id, - task_name=task_name, + activity_id=activity_id, + reference_types=reference_types, fields=fields, - own_attributes=own_attributes, ) -def get_task_by_id( +def create_activity( project_name: str, - task_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by id. + entity_id: str, + entity_type: str, + activity_type: "ActivityType", + activity_id: Optional[str] = None, + body: Optional[str] = None, + file_ids: Optional[list[str]] = None, + timestamp: Optional[str] = None, + data: Optional[dict[str, Any]] = None, +) -> str: + """Create activity on a project. Args: - project_name (str): Name of project where to look for queried - entities. - task_id (str): Task id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project on which activity happened. + entity_id (str): Entity id. + entity_type (str): Entity type. + activity_type (ActivityType): Activity type. + activity_id (Optional[str]): Activity id. + body (Optional[str]): Activity body. + file_ids (Optional[list[str]]): List of file ids attached + to activity. + timestamp (Optional[str]): Activity timestamp. + data (Optional[dict[str, Any]]): Additional data. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + str: Activity id. + + """ + con = get_server_api_connection() + return con.create_activity( + project_name=project_name, + entity_id=entity_id, + entity_type=entity_type, + activity_type=activity_type, + activity_id=activity_id, + body=body, + file_ids=file_ids, + timestamp=timestamp, + data=data, + ) + + +def update_activity( + project_name: str, + activity_id: str, + body: Optional[str] = None, + file_ids: Optional[list[str]] = None, + append_file_ids: Optional[bool] = False, + data: Optional[dict[str, Any]] = None, +): + """Update activity by id. + + Args: + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + body (str): Activity body. + file_ids (Optional[list[str]]): List of file ids attached + to activity. + append_file_ids (Optional[bool]): Append file ids to existing + list of file ids. + data (Optional[dict[str, Any]]): Update data in activity. """ con = get_server_api_connection() - return con.get_task_by_id( + return con.update_activity( project_name=project_name, - task_id=task_id, - fields=fields, - own_attributes=own_attributes, + activity_id=activity_id, + body=body, + file_ids=file_ids, + append_file_ids=append_file_ids, + data=data, ) -def get_tasks_by_folder_paths( +def delete_activity( project_name: str, - folder_paths: Iterable[str], - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Dict[str, List["TaskDict"]]: - """Query task entities from server by folder paths. + activity_id: str, +): + """Delete activity by id. Args: - project_name (str): Name of project. - folder_paths (list[str]): Folder paths. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Dict[str, List[TaskDict]]: Task entities by - folder path. + project_name (str): Project on which activity happened. + activity_id (str): Activity id to remove. """ con = get_server_api_connection() - return con.get_tasks_by_folder_paths( + return con.delete_activity( project_name=project_name, - folder_paths=folder_paths, - task_names=task_names, - task_types=task_types, - assignees=assignees, - assignees_all=assignees_all, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, + activity_id=activity_id, ) -def get_tasks_by_folder_path( +def send_activities_batch_operations( project_name: str, - folder_path: str, - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> List["TaskDict"]: - """Query task entities from server by folder path. + operations: list, + can_fail: bool = False, + raise_on_fail: bool = True, +) -> list: + """Post multiple CRUD activities operations to server. + + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. Args: - project_name (str): Name of project. - folder_path (str): Folder path. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. + + Returns: + list[dict[str, Any]]: Operations result with process details. """ con = get_server_api_connection() - return con.get_tasks_by_folder_path( + return con.send_activities_batch_operations( project_name=project_name, - folder_path=folder_path, - task_names=task_names, - task_types=task_types, - assignees=assignees, - assignees_all=assignees_all, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) -def get_task_by_folder_path( - project_name: str, - folder_path: str, - task_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by folder path and task name. +def get_addon_endpoint( + addon_name: str, + addon_version: str, + *subpaths, +) -> str: + """Calculate endpoint to addon route. + + Examples: + >>> from ayon_api import ServerAPI + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'addons/example/1.0.0/private/my.zip' Args: - project_name (str): Project name. - folder_path (str): Folder path. - task_name (str): Task name. - fields (Optional[Iterable[str]]): Task fields that should - be returned. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + str: Final url. """ con = get_server_api_connection() - return con.get_task_by_folder_path( - project_name=project_name, - folder_path=folder_path, - task_name=task_name, - fields=fields, - own_attributes=own_attributes, + return con.get_addon_endpoint( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, ) -def create_task( - project_name: str, - name: str, - task_type: str, - folder_id: str, - label: Optional[str] = None, - assignees: Optional[Iterable[str]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - task_id: Optional[str] = None, -) -> str: - """Create new task. +def get_addons_info( + details: bool = True, +) -> "AddonsInfoDict": + """Get information about addons available on server. Args: - project_name (str): Project name. - name (str): Folder name. - task_type (str): Task type. - folder_id (str): Parent folder id. - label (Optional[str]): Label of folder. - assignees (Optional[Iterable[str]]): Task assignees. - attrib (Optional[dict[str, Any]]): Task attributes. - data (Optional[dict[str, Any]]): Task data. - tags (Optional[Iterable[str]]): Task tags. - status (Optional[str]): Task status. - active (Optional[bool]): Task active state. - thumbnail_id (Optional[str]): Task thumbnail id. - task_id (Optional[str]): Task id. If not passed new id is - generated. - - Returns: - str: Task id. + details (Optional[bool]): Detailed data with information how + to get client code. """ con = get_server_api_connection() - return con.create_task( - project_name=project_name, - name=name, - task_type=task_type, - folder_id=folder_id, - label=label, - assignees=assignees, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - thumbnail_id=thumbnail_id, - task_id=task_id, + return con.get_addons_info( + details=details, ) -def update_task( - project_name: str, - task_id: str, - name: Optional[str] = None, - task_type: Optional[str] = None, - folder_id: Optional[str] = None, - label: Optional[str] = NOT_SET, - assignees: Optional[List[str]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, -): - """Update task entity on server. - - Do not pass ``label`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. - - Update of ``data`` will override existing value on folder entity. +def get_addon_url( + addon_name: str, + addon_version: str, + *subpaths, + use_rest: bool = True, +) -> str: + """Calculate url to addon route. - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. + Examples: + + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' Args: - project_name (str): Project name. - task_id (str): Task id. - name (Optional[str]): New name. - task_type (Optional[str]): New task type. - folder_id (Optional[str]): New folder id. - label (Optional[Union[str, None]]): New label. - assignees (Optional[str]): New assignees. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - thumbnail_id (Optional[Union[str, None]]): New thumbnail id. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. + use_rest (Optional[bool]): Use rest endpoint. + + Returns: + str: Final url. """ con = get_server_api_connection() - return con.update_task( - project_name=project_name, - task_id=task_id, - name=name, - task_type=task_type, - folder_id=folder_id, - label=label, - assignees=assignees, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - thumbnail_id=thumbnail_id, + return con.get_addon_url( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, + use_rest=use_rest, ) -def delete_task( - project_name: str, - task_id: str, -): - """Delete task. +def delete_addon( + addon_name: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon from server. + + Delete all versions of addon from server. Args: - project_name (str): Project name. - task_id (str): Task id to delete. + addon_name (str): Addon name. + purge (Optional[bool]): Purge all data related to the addon. """ con = get_server_api_connection() - return con.delete_task( - project_name=project_name, - task_id=task_id, + return con.delete_addon( + addon_name=addon_name, + purge=purge, ) -def get_products( - project_name: str, - product_ids: Optional[Iterable[str]] = None, - product_names: Optional[Iterable[str]] = None, - folder_ids: Optional[Iterable[str]] = None, - product_types: Optional[Iterable[str]] = None, - product_name_regex: Optional[str] = None, - product_path_regex: Optional[str] = None, - names_by_folder_ids: Optional[Dict[str, Iterable[str]]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["ProductDict", None, None]: - """Query products from server. +def delete_addon_version( + addon_name: str, + addon_version: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon version from server. - Todos: - Separate 'name_by_folder_ids' filtering to separated method. It - cannot be combined with some other filters. + Delete all versions of addon from server. Args: - project_name (str): Name of project. - product_ids (Optional[Iterable[str]]): Task ids to filter. - product_names (Optional[Iterable[str]]): Task names used for - filtering. - folder_ids (Optional[Iterable[str]]): Ids of task parents. - Use 'None' if folder is direct child of project. - product_types (Optional[Iterable[str]]): Product types used for - filtering. - product_name_regex (Optional[str]): Filter products by name regex. - product_path_regex (Optional[str]): Filter products by path regex. - Path starts with folder path and ends with product name. - names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product - name filtering by folder id. - statuses (Optional[Iterable[str]]): Product statuses used - for filtering. - tags (Optional[Iterable[str]]): Product tags used - for filtering. - active (Optional[bool]): Filter active/inactive products. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. - - Returns: - Generator[ProductDict, None, None]: Queried product entities. + addon_name (str): Addon name. + addon_version (str): Addon version. + purge (Optional[bool]): Purge all data related to the addon. """ con = get_server_api_connection() - return con.get_products( - project_name=project_name, - product_ids=product_ids, - product_names=product_names, - folder_ids=folder_ids, - product_types=product_types, - product_name_regex=product_name_regex, - product_path_regex=product_path_regex, - names_by_folder_ids=names_by_folder_ids, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, + return con.delete_addon_version( + addon_name=addon_name, + addon_version=addon_version, + purge=purge, ) -def get_product_by_id( - project_name: str, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["ProductDict"]: - """Query product entity by id. +def upload_addon_zip( + src_filepath: str, + progress: Optional[TransferProgress] = None, +): + """Upload addon zip file to server. + + File is validated on server. If it is valid, it is installed. It will + create an event job which can be tracked (tracking part is not + implemented yet). + + Example output:: + + {'eventId': 'a1bfbdee27c611eea7580242ac120003'} Args: - project_name (str): Name of project where to look for queried - entities. - product_id (str): Product id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. + src_filepath (str): Path to a zip file. + progress (Optional[TransferProgress]): Object to keep track about + upload state. Returns: - Optional[ProductDict]: Product entity data or None - if was not found. + dict[str, Any]: Response data from server. """ con = get_server_api_connection() - return con.get_product_by_id( - project_name=project_name, - product_id=product_id, - fields=fields, - own_attributes=own_attributes, + return con.upload_addon_zip( + src_filepath=src_filepath, + progress=progress, ) -def get_product_by_name( - project_name: str, - product_name: str, - folder_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["ProductDict"]: - """Query product entity by name and folder id. +def download_addon_private_file( + addon_name: str, + addon_version: str, + filename: str, + destination_dir: str, + destination_filename: Optional[str] = None, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, +) -> str: + """Download a file from addon private files. + + This method requires to have authorized token available. Private files + are not under '/api' restpoint. Args: - project_name (str): Name of project where to look for queried - entities. - product_name (str): Product name. - folder_id (str): Folder id (Folder is a parent of products). - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. + addon_name (str): Addon name. + addon_version (str): Addon version. + filename (str): Filename in private folder on server. + destination_dir (str): Where the file should be downloaded. + destination_filename (Optional[str]): Name of destination + filename. Source filename is used if not passed. + chunk_size (Optional[int]): Download chunk size. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. Returns: - Optional[ProductDict]: Product entity data or None - if was not found. + str: Filepath to downloaded file. """ con = get_server_api_connection() - return con.get_product_by_name( - project_name=project_name, - product_name=product_name, - folder_id=folder_id, - fields=fields, - own_attributes=own_attributes, + return con.download_addon_private_file( + addon_name=addon_name, + addon_version=addon_version, + filename=filename, + destination_dir=destination_dir, + destination_filename=destination_filename, + chunk_size=chunk_size, + progress=progress, ) -def get_product_types( - fields: Optional[Iterable[str]] = None, -) -> List["ProductTypeDict"]: - """Types of products. +def get_event( + event_id: str, +) -> Optional[dict[str, Any]]: + """Query full event data by id. - This is server wide information. Product types have 'name', 'icon' and - 'color'. + Events received using event server do not contain full information. To + get the full event information is required to receive it explicitly. Args: - fields (Optional[Iterable[str]]): Product types fields to query. + event_id (str): Event id. Returns: - list[ProductTypeDict]: Product types information. + dict[str, Any]: Full event data. """ con = get_server_api_connection() - return con.get_product_types( - fields=fields, + return con.get_event( + event_id=event_id, ) -def get_project_product_types( - project_name: str, +def get_events( + topics: Optional[Iterable[str]] = None, + event_ids: Optional[Iterable[str]] = None, + project_names: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + users: Optional[Iterable[str]] = None, + include_logs: Optional[bool] = None, + has_children: Optional[bool] = None, + newer_than: Optional[str] = None, + older_than: Optional[str] = None, fields: Optional[Iterable[str]] = None, -) -> List["ProductTypeDict"]: - """DEPRECATED Types of products available in a project. + limit: Optional[int] = None, + order: Optional[SortOrder] = None, + states: Optional[Iterable[str]] = None, +) -> Generator[dict[str, Any], None, None]: + """Get events from server with filtering options. - Filter only product types available in a project. + Notes: + Not all event happen on a project. Args: - project_name (str): Name of the project where to look for - product types. - fields (Optional[Iterable[str]]): Product types fields to query. + topics (Optional[Iterable[str]]): Name of topics. + event_ids (Optional[Iterable[str]]): Event ids. + project_names (Optional[Iterable[str]]): Project on which + event happened. + statuses (Optional[Iterable[str]]): Filtering by statuses. + users (Optional[Iterable[str]]): Filtering by users + who created/triggered an event. + include_logs (Optional[bool]): Query also log events. + has_children (Optional[bool]): Event is with/without children + events. If 'None' then all events are returned, default. + newer_than (Optional[str]): Return only events newer than given + iso datetime string. + older_than (Optional[str]): Return only events older than given + iso datetime string. + fields (Optional[Iterable[str]]): Fields that should be received + for each event. + limit (Optional[int]): Limit number of events to be fetched. + order (Optional[SortOrder]): Order events in ascending + or descending order. It is recommended to set 'limit' + when used descending. + states (Optional[Iterable[str]]): DEPRECATED Filtering by states. + Use 'statuses' instead. Returns: - List[ProductTypeDict]: Product types information. + Generator[dict[str, Any]]: Available events matching filters. """ con = get_server_api_connection() - return con.get_project_product_types( - project_name=project_name, + return con.get_events( + topics=topics, + event_ids=event_ids, + project_names=project_names, + statuses=statuses, + users=users, + include_logs=include_logs, + has_children=has_children, + newer_than=newer_than, + older_than=older_than, fields=fields, + limit=limit, + order=order, + states=states, ) -def get_product_type_names( +def update_event( + event_id: str, + sender: Optional[str] = None, project_name: Optional[str] = None, - product_ids: Optional[Iterable[str]] = None, -) -> Set[str]: - """DEPRECATED Product type names. - - Warnings: - This function will be probably removed. Matters if 'products_id' - filter has real use-case. + username: Optional[str] = None, + status: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + progress: Optional[int] = None, + retries: Optional[int] = None, +): + """Update event data. Args: - project_name (Optional[str]): Name of project where to look for - queried entities. - product_ids (Optional[Iterable[str]]): Product ids filter. Can be - used only with 'project_name'. - - Returns: - set[str]: Product type names. + event_id (str): Event id. + sender (Optional[str]): New sender of event. + project_name (Optional[str]): New project name. + username (Optional[str]): New username. + status (Optional[str]): New event status. Enum: "pending", + "in_progress", "finished", "failed", "aborted", "restarted" + description (Optional[str]): New description. + summary (Optional[dict[str, Any]]): New summary. + payload (Optional[dict[str, Any]]): New payload. + progress (Optional[int]): New progress. Range [0-100]. + retries (Optional[int]): New retries. """ con = get_server_api_connection() - return con.get_product_type_names( + return con.update_event( + event_id=event_id, + sender=sender, project_name=project_name, - product_ids=product_ids, + username=username, + status=status, + description=description, + summary=summary, + payload=payload, + progress=progress, + retries=retries, ) -def create_product( - project_name: str, - name: str, - product_type: str, - folder_id: str, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: "Union[bool, None]" = None, - product_id: Optional[str] = None, -) -> str: - """Create new product. +def dispatch_event( + topic: str, + sender: Optional[str] = None, + event_hash: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + depends_on: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + finished: bool = True, + store: bool = True, + dependencies: Optional[list[str]] = None, +): + """Dispatch event to server. Args: - project_name (str): Project name. - name (str): Product name. - product_type (str): Product type. - folder_id (str): Parent folder id. - attrib (Optional[dict[str, Any]]): Product attributes. - data (Optional[dict[str, Any]]): Product data. - tags (Optional[Iterable[str]]): Product tags. - status (Optional[str]): Product status. - active (Optional[bool]): Product active state. - product_id (Optional[str]): Product id. If not passed new id is - generated. + topic (str): Event topic used for filtering of listeners. + sender (Optional[str]): Sender of event. + event_hash (Optional[str]): Event hash. + project_name (Optional[str]): Project name. + depends_on (Optional[str]): Add dependency to another event. + username (Optional[str]): Username which triggered event. + description (Optional[str]): Description of event. + summary (Optional[dict[str, Any]]): Summary of event that can + be used for simple filtering on listeners. + payload (Optional[dict[str, Any]]): Full payload of event data with + all details. + finished (Optional[bool]): Mark event as finished on dispatch. + store (Optional[bool]): Store event in event queue for possible + future processing otherwise is event send only + to active listeners. + dependencies (Optional[list[str]]): Deprecated. + List of event id dependencies. Returns: - str: Product id. + RestApiResponse: Response from server. """ con = get_server_api_connection() - return con.create_product( + return con.dispatch_event( + topic=topic, + sender=sender, + event_hash=event_hash, project_name=project_name, - name=name, - product_type=product_type, - folder_id=folder_id, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - product_id=product_id, + username=username, + depends_on=depends_on, + description=description, + summary=summary, + payload=payload, + finished=finished, + store=store, + dependencies=dependencies, ) -def update_product( - project_name: str, - product_id: str, - name: Optional[str] = None, - folder_id: Optional[str] = None, - product_type: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, +def delete_event( + event_id: str, ): - """Update product entity on server. - - Update of ``data`` will override existing value on folder entity. + """Delete event by id. - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. + Supported since AYON server 1.6.0. Args: - project_name (str): Project name. - product_id (str): Product id. - name (Optional[str]): New product name. - folder_id (Optional[str]): New product id. - product_type (Optional[str]): New product type. - attrib (Optional[dict[str, Any]]): New product attributes. - data (Optional[dict[str, Any]]): New product data. - tags (Optional[Iterable[str]]): New product tags. - status (Optional[str]): New product status. - active (Optional[bool]): New product active state. + event_id (str): Event id. + + Returns: + RestApiResponse: Response from server. """ con = get_server_api_connection() - return con.update_product( - project_name=project_name, - product_id=product_id, - name=name, - folder_id=folder_id, - product_type=product_type, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, + return con.delete_event( + event_id=event_id, ) -def delete_product( - project_name: str, - product_id: str, -): - """Delete product. +def enroll_event_job( + source_topic: "Union[str, list[str]]", + target_topic: str, + sender: str, + description: Optional[str] = None, + sequential: Optional[bool] = None, + events_filter: Optional["EventFilter"] = None, + max_retries: Optional[int] = None, + ignore_older_than: Optional[str] = None, + ignore_sender_types: Optional[str] = None, +): + """Enroll job based on events. + + Enroll will find first unprocessed event with 'source_topic' and will + create new event with 'target_topic' for it and return the new event + data. + + Use 'sequential' to control that only single target event is created + at same time. Creation of new target events is blocked while there is + at least one unfinished event with target topic, when set to 'True'. + This helps when order of events matter and more than one process using + the same target is running at the same time. - Args: - project_name (str): Project name. - product_id (str): Product id to delete. + Make sure the new event has updated status to '"finished"' status + when you're done with logic - """ - con = get_server_api_connection() - return con.delete_product( - project_name=project_name, - product_id=product_id, - ) + Target topic should not clash with other processes/services. + Created target event have 'dependsOn' key where is id of source topic. -def get_versions( - project_name: str, - version_ids: Optional[Iterable[str]] = None, - product_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] = None, - versions: Optional[Iterable[str]] = None, - hero: bool = True, - standard: bool = True, - latest: Optional[bool] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["VersionDict", None, None]: - """Get version entities based on passed filters from server. + Use-case: + - Service 1 is creating events with topic 'my.leech' + - Service 2 process 'my.leech' and uses target topic 'my.process' + - this service can run on 1-n machines + - all events must be processed in a sequence by their creation + time and only one event can be processed at a time + - in this case 'sequential' should be set to 'True' so only + one machine is actually processing events, but if one goes + down there are other that can take place + - Service 3 process 'my.leech' and uses target topic 'my.discover' + - this service can run on 1-n machines + - order of events is not important + - 'sequential' should be 'False' Args: - project_name (str): Name of project where to look for versions. - version_ids (Optional[Iterable[str]]): Version ids used for - version filtering. - product_ids (Optional[Iterable[str]]): Product ids used for - version filtering. - task_ids (Optional[Iterable[str]]): Task ids used for - version filtering. - versions (Optional[Iterable[int]]): Versions we're interested in. - hero (Optional[bool]): Skip hero versions when set to False. - standard (Optional[bool]): Skip standard (non-hero) when - set to False. - latest (Optional[bool]): Return only latest version of standard - versions. This can be combined only with 'standard' attribute - set to True. - statuses (Optional[Iterable[str]]): Representation statuses used - for filtering. - tags (Optional[Iterable[str]]): Representation tags used - for filtering. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): Fields to be queried - for version. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + source_topic (Union[str, list[str]]): Source topic to enroll with + wildcards '*', or explicit list of topics. + target_topic (str): Topic of dependent event. + sender (str): Identifier of sender (e.g. service name or username). + description (Optional[str]): Human readable text shown + in target event. + sequential (Optional[bool]): The source topic must be processed + in sequence. + events_filter (Optional[dict[str, Any]]): Filtering conditions + to filter the source event. For more technical specifications + look to server backed 'ayon_server.sqlfilter.Filter'. + TODO: Add example of filters. + max_retries (Optional[int]): How many times can be event retried. + Default value is based on server (3 at the time of this PR). + ignore_older_than (Optional[int]): Ignore events older than + given number in days. + ignore_sender_types (Optional[list[str]]): Ignore events triggered + by given sender types. Returns: - Generator[VersionDict, None, None]: Queried version entities. + Optional[dict[str, Any]]: None if there is no event matching + filters. Created event with 'target_topic'. """ con = get_server_api_connection() - return con.get_versions( - project_name=project_name, - version_ids=version_ids, - product_ids=product_ids, - task_ids=task_ids, - versions=versions, - hero=hero, - standard=standard, - latest=latest, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, + return con.enroll_event_job( + source_topic=source_topic, + target_topic=target_topic, + sender=sender, + description=description, + sequential=sequential, + events_filter=events_filter, + max_retries=max_retries, + ignore_older_than=ignore_older_than, + ignore_sender_types=ignore_sender_types, ) -def get_version_by_id( - project_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query version entity by id. +def get_full_link_type_name( + link_type_name: str, + input_type: str, + output_type: str, +) -> str: + """Calculate full link type name used for query from server. Args: - project_name (str): Name of project where to look for queried - entities. - version_id (str): Version id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + link_type_name (str): Type of link. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + str: Full name of link type used for query from server. """ con = get_server_api_connection() - return con.get_version_by_id( - project_name=project_name, - version_id=version_id, - fields=fields, - own_attributes=own_attributes, + return con.get_full_link_type_name( + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) -def get_version_by_name( +def get_link_types( project_name: str, - version: int, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query version entity by version and product id. +) -> list[dict[str, Any]]: + """All link types available on a project. + + Example output: + [ + { + "name": "reference|folder|folder", + "link_type": "reference", + "input_type": "folder", + "output_type": "folder", + "data": {} + } + ] Args: - project_name (str): Name of project where to look for queried - entities. - version (int): Version of version entity. - product_id (str): Product id. Product is a parent of version. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Name of project where to look for link types. Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + list[dict[str, Any]]: Link types available on project. """ con = get_server_api_connection() - return con.get_version_by_name( + return con.get_link_types( project_name=project_name, - version=version, - product_id=product_id, - fields=fields, - own_attributes=own_attributes, ) -def get_hero_version_by_id( +def get_link_type( project_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query hero version entity by id. + link_type_name: str, + input_type: str, + output_type: str, +) -> Optional[dict[str, Any]]: + """Get link type data. + + There is not dedicated REST endpoint to get single link type, + so method 'get_link_types' is used. + + Example output: + { + "name": "reference|folder|folder", + "link_type": "reference", + "input_type": "folder", + "output_type": "folder", + "data": {} + } Args: - project_name (str): Name of project where to look for queried - entities. - version_id (int): Hero version id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where link type is available. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Optional[dict[str, Any]]: Link type information. """ con = get_server_api_connection() - return con.get_hero_version_by_id( + return con.get_link_type( project_name=project_name, - version_id=version_id, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) - -def get_hero_version_by_product_id( - project_name: str, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query hero version entity by product id. - - Only one hero version is available on a product. + +def create_link_type( + project_name: str, + link_type_name: str, + input_type: str, + output_type: str, + data: Optional[dict[str, Any]] = None, +): + """Create or update link type on server. + + Warning: + Because PUT is used for creation it is also used for update. Args: - project_name (str): Name of project where to look for queried - entities. - product_id (int): Product id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where link type is created. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + data (Optional[dict[str, Any]]): Additional data related to link. - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_hero_version_by_product_id( + return con.create_link_type( project_name=project_name, - product_id=product_id, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, + data=data, ) -def get_hero_versions( +def delete_link_type( project_name: str, - product_ids: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["VersionDict", None, None]: - """Query hero versions by multiple filters. - - Only one hero version is available on a product. + link_type_name: str, + input_type: str, + output_type: str, +): + """Remove link type from project. Args: - project_name (str): Name of project where to look for queried - entities. - product_ids (Optional[Iterable[str]]): Product ids. - version_ids (Optional[Iterable[str]]): Version ids. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where link type is created. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_hero_versions( + return con.delete_link_type( project_name=project_name, - product_ids=product_ids, - version_ids=version_ids, - active=active, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) -def get_last_versions( +def make_sure_link_type_exists( project_name: str, - product_ids: Iterable[str], - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Dict[str, Optional["VersionDict"]]: - """Query last version entities by product ids. + link_type_name: str, + input_type: str, + output_type: str, + data: Optional[dict[str, Any]] = None, +): + """Make sure link type exists on a project. Args: - project_name (str): Project where to look for representation. - product_ids (Iterable[str]): Product ids. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - dict[str, Optional[VersionDict]]: Last versions by product id. + project_name (str): Name of project. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + data (Optional[dict[str, Any]]): Link type related data. """ con = get_server_api_connection() - return con.get_last_versions( + return con.make_sure_link_type_exists( project_name=project_name, - product_ids=product_ids, - active=active, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, + data=data, ) -def get_last_version_by_product_id( +def create_link( project_name: str, - product_id: str, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query last version entity by product id. + link_type_name: str, + input_id: str, + input_type: str, + output_id: str, + output_type: str, + link_name: Optional[str] = None, +): + """Create link between 2 entities. + + Link has a type which must already exists on a project. + + Example output:: + + { + "id": "59a212c0d2e211eda0e20242ac120002" + } Args: - project_name (str): Project where to look for representation. - product_id (str): Product id. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where the link is created. + link_type_name (str): Type of link. + input_id (str): Input entity id. + input_type (str): Entity type of input entity. + output_id (str): Output entity id. + output_type (str): Entity type of output entity. + link_name (Optional[str]): Name of link. + Available from server version '1.0.0-rc.6'. Returns: - Optional[VersionDict]: Queried version entity or None. + dict[str, str]: Information about link. + + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_last_version_by_product_id( + return con.create_link( project_name=project_name, - product_id=product_id, - active=active, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_id=input_id, + input_type=input_type, + output_id=output_id, + output_type=output_type, + link_name=link_name, ) -def get_last_version_by_product_name( +def delete_link( project_name: str, - product_name: str, - folder_id: str, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query last version entity by product name and folder id. + link_id: str, +): + """Remove link by id. Args: - project_name (str): Project where to look for representation. - product_name (str): Product name. - folder_id (str): Folder id. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + project_name (str): Project where link exists. + link_id (str): Id of link. - Returns: - Optional[VersionDict]: Queried version entity or None. + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_last_version_by_product_name( + return con.delete_link( project_name=project_name, - product_name=product_name, - folder_id=folder_id, - active=active, - fields=fields, - own_attributes=own_attributes, + link_id=link_id, ) -def version_is_latest( +def get_entities_links( project_name: str, - version_id: str, -) -> bool: - """Is version latest from a product. + entity_type: str, + entity_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + link_names: Optional[Iterable[str]] = None, + link_name_regex: Optional[str] = None, +) -> dict[str, list[dict[str, Any]]]: + """Helper method to get links from server for entity types. + + .. highlight:: text + .. code-block:: text + + Example output: + { + "59a212c0d2e211eda0e20242ac120001": [ + { + "id": "59a212c0d2e211eda0e20242ac120002", + "linkType": "reference", + "description": "reference link between folders", + "projectName": "my_project", + "author": "frantadmin", + "entityId": "b1df109676db11ed8e8c6c9466b19aa8", + "entityType": "folder", + "direction": "out" + }, + ... + ], + ... + } Args: - project_name (str): Project where to look for representation. - version_id (str): Version id. + project_name (str): Project where links are. + entity_type (Literal["folder", "task", "product", + "version", "representations"]): Entity type. + entity_ids (Optional[Iterable[str]]): Ids of entities for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + link_names (Optional[Iterable[str]]): Link name filters. + link_name_regex (Optional[str]): Regex filter for link name. Returns: - bool: Version is latest or not. + dict[str, list[dict[str, Any]]]: Link info by entity ids. """ con = get_server_api_connection() - return con.version_is_latest( + return con.get_entities_links( project_name=project_name, - version_id=version_id, + entity_type=entity_type, + entity_ids=entity_ids, + link_types=link_types, + link_direction=link_direction, + link_names=link_names, + link_name_regex=link_name_regex, ) -def create_version( +def get_folders_links( project_name: str, - version: int, - product_id: str, - task_id: Optional[str] = None, - author: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - version_id: Optional[str] = None, -) -> str: - """Create new version. + folder_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query folders links from server. Args: - project_name (str): Project name. - version (int): Version. - product_id (str): Parent product id. - task_id (Optional[str]): Parent task id. - author (Optional[str]): Version author. - attrib (Optional[dict[str, Any]]): Version attributes. - data (Optional[dict[str, Any]]): Version data. - tags (Optional[Iterable[str]]): Version tags. - status (Optional[str]): Version status. - active (Optional[bool]): Version active state. - thumbnail_id (Optional[str]): Version thumbnail id. - version_id (Optional[str]): Version id. If not passed new id is - generated. + project_name (str): Project where links are. + folder_ids (Optional[Iterable[str]]): Ids of folders for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - str: Version id. + dict[str, list[dict[str, Any]]]: Link info by folder ids. """ con = get_server_api_connection() - return con.create_version( + return con.get_folders_links( project_name=project_name, - version=version, - product_id=product_id, - task_id=task_id, - author=author, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - thumbnail_id=thumbnail_id, - version_id=version_id, + folder_ids=folder_ids, + link_types=link_types, + link_direction=link_direction, ) -def update_version( +def get_folder_links( project_name: str, - version_id: str, - version: Optional[int] = None, - product_id: Optional[str] = None, - task_id: Optional[str] = NOT_SET, - author: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, -): - """Update version entity on server. - - Do not pass ``task_id`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. + folder_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query folder links from server. Args: - project_name (str): Project name. - version_id (str): Version id. - version (Optional[int]): New version. - product_id (Optional[str]): New product id. - task_id (Optional[Union[str, None]]): New task id. - author (Optional[str]): New author username. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - thumbnail_id (Optional[Union[str, None]]): New thumbnail id. - - """ - con = get_server_api_connection() - return con.update_version( - project_name=project_name, - version_id=version_id, - version=version, - product_id=product_id, - task_id=task_id, - author=author, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - thumbnail_id=thumbnail_id, - ) - - -def delete_version( - project_name: str, - version_id: str, -): - """Delete version. + project_name (str): Project where links are. + folder_id (str): Folder id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. - Args: - project_name (str): Project name. - version_id (str): Version id to delete. + Returns: + list[dict[str, Any]]: Link info of folder. """ con = get_server_api_connection() - return con.delete_version( + return con.get_folder_links( project_name=project_name, - version_id=version_id, + folder_id=folder_id, + link_types=link_types, + link_direction=link_direction, ) -def send_batch_operations( +def get_tasks_links( project_name: str, - operations: List[Dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True, -) -> List[Dict[str, Any]]: - """Post multiple CRUD operations to server. - - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. + task_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query tasks links from server. Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. - - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. + project_name (str): Project where links are. + task_ids (Optional[Iterable[str]]): Ids of tasks for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - list[dict[str, Any]]: Operations result with process details. + dict[str, list[dict[str, Any]]]: Link info by task ids. """ con = get_server_api_connection() - return con.send_batch_operations( + return con.get_tasks_links( project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, + task_ids=task_ids, + link_types=link_types, + link_direction=link_direction, ) -def get_actions( - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, - mode: Optional["ActionModeType"] = None, -) -> List["ActionManifestDict"]: - """Get actions for a context. +def get_task_links( + project_name: str, + task_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query task links from server. Args: - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. - mode (Optional[ActionModeType]): Action modes. + project_name (str): Project where links are. + task_id (str): Task id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - List[ActionManifestDict]: List of action manifests. + list[dict[str, Any]]: Link info of task. """ con = get_server_api_connection() - return con.get_actions( + return con.get_task_links( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, - mode=mode, - ) - - -def trigger_action( - identifier: str, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionTriggerResponse": - """Trigger action. + task_id=task_id, + link_types=link_types, + link_direction=link_direction, + ) + + +def get_products_links( + project_name: str, + product_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query products links from server. Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + project_name (str): Project where links are. + product_ids (Optional[Iterable[str]]): Ids of products for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by product ids. """ con = get_server_api_connection() - return con.trigger_action( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, + return con.get_products_links( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + product_ids=product_ids, + link_types=link_types, + link_direction=link_direction, ) -def get_action_config( - identifier: str, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Get action configuration. +def get_product_links( + project_name: str, + product_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query product links from server. Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + project_name (str): Project where links are. + product_id (str): Product id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - ActionConfigResponse: Action configuration data. + list[dict[str, Any]]: Link info of product. """ con = get_server_api_connection() - return con.get_action_config( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, + return con.get_product_links( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + product_id=product_id, + link_types=link_types, + link_direction=link_direction, ) -def set_action_config( - identifier: str, - addon_name: str, - addon_version: str, - value: Dict[str, Any], - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Set action configuration. +def get_versions_links( + project_name: str, + version_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query versions links from server. Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - value (Optional[Dict[str, Any]]): Value of the action - configuration. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + project_name (str): Project where links are. + version_ids (Optional[Iterable[str]]): Ids of versions for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - ActionConfigResponse: New action configuration data. + dict[str, list[dict[str, Any]]]: Link info by version ids. """ con = get_server_api_connection() - return con.set_action_config( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, - value=value, + return con.get_versions_links( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + version_ids=version_ids, + link_types=link_types, + link_direction=link_direction, ) -def take_action( - action_token: str, -) -> "ActionTakeResponse": - """Take action metadata using an action token. +def get_version_links( + project_name: str, + version_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query version links from server. Args: - action_token (str): AYON launcher action token. + project_name (str): Project where links are. + version_id (str): Version id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - ActionTakeResponse: Action metadata describing how to launch - action. + list[dict[str, Any]]: Link info of version. """ con = get_server_api_connection() - return con.take_action( - action_token=action_token, + return con.get_version_links( + project_name=project_name, + version_id=version_id, + link_types=link_types, + link_direction=link_direction, ) -def abort_action( - action_token: str, - message: Optional[str] = None, -) -> None: - """Abort action using an action token. +def get_representations_links( + project_name: str, + representation_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query representations links from server. Args: - action_token (str): AYON launcher action token. - message (Optional[str]): Message to display in the UI. + project_name (str): Project where links are. + representation_ids (Optional[Iterable[str]]): Ids of + representations for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by representation ids. """ con = get_server_api_connection() - return con.abort_action( - action_token=action_token, - message=message, + return con.get_representations_links( + project_name=project_name, + representation_ids=representation_ids, + link_types=link_types, + link_direction=link_direction, ) -def get_activities( +def get_representation_links( project_name: str, - activity_ids: Optional[Iterable[str]] = None, - activity_types: Optional[Iterable["ActivityType"]] = None, - entity_ids: Optional[Iterable[str]] = None, - entity_names: Optional[Iterable[str]] = None, - entity_type: Optional[str] = None, - changed_after: Optional[str] = None, - changed_before: Optional[str] = None, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, - limit: Optional[int] = None, - order: Optional[SortOrder] = None, -) -> Generator[dict[str, Any], None, None]: - """Get activities from server with filtering options. + representation_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query representation links from server. Args: - project_name (str): Project on which activities happened. - activity_ids (Optional[Iterable[str]]): Activity ids. - activity_types (Optional[Iterable[ActivityType]]): Activity types. - entity_ids (Optional[Iterable[str]]): Entity ids. - entity_names (Optional[Iterable[str]]): Entity names. - entity_type (Optional[str]): Entity type. - changed_after (Optional[str]): Return only activities changed - after given iso datetime string. - changed_before (Optional[str]): Return only activities changed - before given iso datetime string. - reference_types (Optional[Iterable[ActivityReferenceType]]): - Reference types filter. Defaults to `['origin']`. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. - limit (Optional[int]): Limit number of activities to be fetched. - order (Optional[SortOrder]): Order activities in ascending - or descending order. It is recommended to set 'limit' - when used descending. + project_name (str): Project where links are. + representation_id (str): Representation id for which links + should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - Generator[dict[str, Any]]: Available activities matching filters. + list[dict[str, Any]]: Link info of representation. """ con = get_server_api_connection() - return con.get_activities( + return con.get_representation_links( project_name=project_name, - activity_ids=activity_ids, - activity_types=activity_types, - entity_ids=entity_ids, - entity_names=entity_names, - entity_type=entity_type, - changed_after=changed_after, - changed_before=changed_before, - reference_types=reference_types, - fields=fields, - limit=limit, - order=order, + representation_id=representation_id, + link_types=link_types, + link_direction=link_direction, ) -def get_activity_by_id( +def get_entity_lists( project_name: str, - activity_id: str, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + *, + list_ids: Optional[Iterable[str]] = None, + active: Optional[bool] = None, fields: Optional[Iterable[str]] = None, -) -> Optional[dict[str, Any]]: - """Get activity by id. +) -> Generator[Dict[str, Any], None, None]: + """Fetch entity lists from server. Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - reference_types: Optional[Iterable[ActivityReferenceType]]: Filter - by reference types. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. + project_name (str): Project name where entity lists are. + list_ids (Optional[Iterable[str]]): List of entity list ids to + fetch. + active (Optional[bool]): Filter by active state of entity lists. + fields (Optional[Iterable[str]]): Fields to fetch from server. Returns: - Optional[dict[str, Any]]: Activity data or None if activity is not - found. + Generator[Dict[str, Any], None, None]: Entity list entities + matching defined filters. """ con = get_server_api_connection() - return con.get_activity_by_id( + return con.get_entity_lists( project_name=project_name, - activity_id=activity_id, - reference_types=reference_types, + list_ids=list_ids, + active=active, fields=fields, ) -def create_activity( +def get_entity_list_rest( project_name: str, - entity_id: str, - entity_type: str, - activity_type: "ActivityType", - activity_id: Optional[str] = None, - body: Optional[str] = None, - file_ids: Optional[list[str]] = None, - timestamp: Optional[str] = None, - data: Optional[dict[str, Any]] = None, -) -> str: - """Create activity on a project. + list_id: str, +) -> Optional[Dict[str, Any]]: + """Get entity list by id using REST API. Args: - project_name (str): Project on which activity happened. - entity_id (str): Entity id. - entity_type (str): Entity type. - activity_type (ActivityType): Activity type. - activity_id (Optional[str]): Activity id. - body (Optional[str]): Activity body. - file_ids (Optional[list[str]]): List of file ids attached - to activity. - timestamp (Optional[str]): Activity timestamp. - data (Optional[dict[str, Any]]): Additional data. + project_name (str): Project name. + list_id (str): Entity list id. Returns: - str: Activity id. + Optional[Dict[str, Any]]: Entity list data or None if not found. """ con = get_server_api_connection() - return con.create_activity( + return con.get_entity_list_rest( project_name=project_name, - entity_id=entity_id, - entity_type=entity_type, - activity_type=activity_type, - activity_id=activity_id, - body=body, - file_ids=file_ids, - timestamp=timestamp, - data=data, + list_id=list_id, ) -def update_activity( +def get_entity_list_by_id( project_name: str, - activity_id: str, - body: Optional[str] = None, - file_ids: Optional[list[str]] = None, - append_file_ids: Optional[bool] = False, - data: Optional[dict[str, Any]] = None, -): - """Update activity by id. + list_id: str, + fields: Optional[Iterable[str]] = None, +) -> Optional[Dict[str, Any]]: + """Get entity list by id using GraphQl. Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - body (str): Activity body. - file_ids (Optional[list[str]]): List of file ids attached - to activity. - append_file_ids (Optional[bool]): Append file ids to existing - list of file ids. - data (Optional[dict[str, Any]]): Update data in activity. + project_name (str): Project name. + list_id (str): Entity list id. + fields (Optional[Iterable[str]]): Fields to fetch from server. + + Returns: + Optional[Dict[str, Any]]: Entity list data or None if not found. """ con = get_server_api_connection() - return con.update_activity( + return con.get_entity_list_by_id( project_name=project_name, - activity_id=activity_id, - body=body, - file_ids=file_ids, - append_file_ids=append_file_ids, - data=data, + list_id=list_id, + fields=fields, ) -def delete_activity( +def create_entity_list( project_name: str, - activity_id: str, -): - """Delete activity by id. + entity_type: "EntityListEntityType", + label: str, + *, + list_type: Optional[str] = None, + access: Optional[Dict[str, Any]] = None, + attrib: Optional[List[Dict[str, Any]]] = None, + data: Optional[List[Dict[str, Any]]] = None, + tags: Optional[List[str]] = None, + template: Optional[Dict[str, Any]] = None, + owner: Optional[str] = None, + active: Optional[bool] = None, + items: Optional[List[Dict[str, Any]]] = None, + list_id: Optional[str] = None, +) -> str: + """Create entity list. Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id to remove. + project_name (str): Project name where entity list lives. + entity_type (EntityListEntityType): Which entity types can be + used in list. + label (str): Entity list label. + list_type (Optional[str]): Entity list type. + access (Optional[dict[str, Any]]): Access control for entity list. + attrib (Optional[dict[str, Any]]): Attribute values of + entity list. + data (Optional[dict[str, Any]]): Custom data of entity list. + tags (Optional[list[str]]): Entity list tags. + template (Optional[dict[str, Any]]): Dynamic list template. + owner (Optional[str]): New owner of the list. + active (Optional[bool]): Change active state of entity list. + items (Optional[list[dict[str, Any]]]): Initial items in + entity list. + list_id (Optional[str]): Entity list id. """ con = get_server_api_connection() - return con.delete_activity( + return con.create_entity_list( project_name=project_name, - activity_id=activity_id, + entity_type=entity_type, + label=label, + list_type=list_type, + access=access, + attrib=attrib, + data=data, + tags=tags, + template=template, + owner=owner, + active=active, + items=items, + list_id=list_id, ) -def send_activities_batch_operations( +def update_entity_list( project_name: str, - operations: list, - can_fail: bool = False, - raise_on_fail: bool = True, -) -> list: - """Post multiple CRUD activities operations to server. - - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. + list_id: str, + *, + label: Optional[str] = None, + access: Optional[Dict[str, Any]] = None, + attrib: Optional[List[Dict[str, Any]]] = None, + data: Optional[List[Dict[str, Any]]] = None, + tags: Optional[List[str]] = None, + owner: Optional[str] = None, + active: Optional[bool] = None, +) -> None: + """Update entity list. Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. + project_name (str): Project name where entity list lives. + list_id (str): Entity list id that will be updated. + label (Optional[str]): New label of entity list. + access (Optional[dict[str, Any]]): Access control for entity list. + attrib (Optional[dict[str, Any]]): Attribute values of + entity list. + data (Optional[dict[str, Any]]): Custom data of entity list. + tags (Optional[list[str]]): Entity list tags. + owner (Optional[str]): New owner of the list. + active (Optional[bool]): Change active state of entity list. - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. + """ + con = get_server_api_connection() + return con.update_entity_list( + project_name=project_name, + list_id=list_id, + label=label, + access=access, + attrib=attrib, + data=data, + tags=tags, + owner=owner, + active=active, + ) - Returns: - list[dict[str, Any]]: Operations result with process details. + +def delete_entity_list( + project_name: str, + list_id: str, +) -> None: + """Delete entity list from project. + + Args: + project_name (str): Project name. + list_id (str): Entity list id that will be removed. """ con = get_server_api_connection() - return con.send_activities_batch_operations( + return con.delete_entity_list( project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, + list_id=list_id, ) -def get_addon_endpoint( - addon_name: str, - addon_version: str, - *subpaths, -) -> str: - """Calculate endpoint to addon route. - - Examples: - >>> from ayon_api import ServerAPI - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'addons/example/1.0.0/private/my.zip' +def get_entity_list_attribute_definitions( + project_name: str, + list_id: str, +) -> List["EntityListAttributeDefinitionDict"]: + """Get attribute definitioins on entity list. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. + project_name (str): Project name. + list_id (str): Entity list id. Returns: - str: Final url. + List[EntityListAttributeDefinitionDict]: List of attribute + definitions. """ con = get_server_api_connection() - return con.get_addon_endpoint( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, + return con.get_entity_list_attribute_definitions( + project_name=project_name, + list_id=list_id, ) -def get_addons_info( - details: bool = True, -) -> "AddonsInfoDict": - """Get information about addons available on server. +def set_entity_list_attribute_definitions( + project_name: str, + list_id: str, + attribute_definitions: List["EntityListAttributeDefinitionDict"], +) -> None: + """Set attribute definitioins on entity list. Args: - details (Optional[bool]): Detailed data with information how - to get client code. + project_name (str): Project name. + list_id (str): Entity list id. + attribute_definitions (List[EntityListAttributeDefinitionDict]): + List of attribute definitions. """ con = get_server_api_connection() - return con.get_addons_info( - details=details, + return con.set_entity_list_attribute_definitions( + project_name=project_name, + list_id=list_id, + attribute_definitions=attribute_definitions, ) -def get_addon_url( - addon_name: str, - addon_version: str, - *subpaths, - use_rest: bool = True, +def create_entity_list_item( + project_name: str, + list_id: str, + *, + position: Optional[int] = None, + label: Optional[str] = None, + attrib: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + tags: Optional[List[str]] = None, + item_id: Optional[str] = None, ) -> str: - """Calculate url to addon route. - - Examples: - - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' + """Create entity list item. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. - use_rest (Optional[bool]): Use rest endpoint. + project_name (str): Project name where entity list lives. + list_id (str): Entity list id where item will be added. + position (Optional[int]): Position of item in entity list. + label (Optional[str]): Label of item in entity list. + attrib (Optional[dict[str, Any]]): Item attribute values. + data (Optional[dict[str, Any]]): Item data. + tags (Optional[list[str]]): Tags of item in entity list. + item_id (Optional[str]): Id of item that will be created. Returns: - str: Final url. + str: Item id. """ con = get_server_api_connection() - return con.get_addon_url( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, - use_rest=use_rest, + return con.create_entity_list_item( + project_name=project_name, + list_id=list_id, + position=position, + label=label, + attrib=attrib, + data=data, + tags=tags, + item_id=item_id, ) -def delete_addon( - addon_name: str, - purge: Optional[bool] = None, +def update_entity_list_items( + project_name: str, + list_id: str, + items: List[Dict[str, Any]], + mode: "EntityListItemMode", ) -> None: - """Delete addon from server. - - Delete all versions of addon from server. + """Update items in entity list. Args: - addon_name (str): Addon name. - purge (Optional[bool]): Purge all data related to the addon. + project_name (str): Project name where entity list live. + list_id (str): Entity list id. + items (List[Dict[str, Any]]): Entity list items. + mode (EntityListItemMode): Mode of items update. """ con = get_server_api_connection() - return con.delete_addon( - addon_name=addon_name, - purge=purge, + return con.update_entity_list_items( + project_name=project_name, + list_id=list_id, + items=items, + mode=mode, ) -def delete_addon_version( - addon_name: str, - addon_version: str, - purge: Optional[bool] = None, +def update_entity_list_item( + project_name: str, + list_id: str, + item_id: str, + *, + new_list_id: Optional[str], + position: Optional[int] = None, + label: Optional[str] = None, + attrib: Optional[Dict[str, Any]] = None, + data: Optional[Dict[str, Any]] = None, + tags: Optional[List[str]] = None, ) -> None: - """Delete addon version from server. - - Delete all versions of addon from server. + """Update item in entity list. Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - purge (Optional[bool]): Purge all data related to the addon. + project_name (str): Project name where entity list live. + list_id (str): Entity list id where item lives. + item_id (str): Item id that will be removed from entity list. + new_list_id (Optional[str]): New entity list id where item will be + added. + position (Optional[int]): Position of item in entity list. + label (Optional[str]): Label of item in entity list. + attrib (Optional[dict[str, Any]]): Attributes of item in entity + list. + data (Optional[dict[str, Any]]): Custom data of item in + entity list. + tags (Optional[list[str]]): Tags of item in entity list. """ con = get_server_api_connection() - return con.delete_addon_version( - addon_name=addon_name, - addon_version=addon_version, - purge=purge, + return con.update_entity_list_item( + project_name=project_name, + list_id=list_id, + item_id=item_id, + new_list_id=new_list_id, + position=position, + label=label, + attrib=attrib, + data=data, + tags=tags, ) -def upload_addon_zip( - src_filepath: str, - progress: Optional[TransferProgress] = None, -): - """Upload addon zip file to server. +def delete_entity_list_item( + project_name: str, + list_id: str, + item_id: str, +) -> None: + """Delete item from entity list. - File is validated on server. If it is valid, it is installed. It will - create an event job which can be tracked (tracking part is not - implemented yet). + Args: + project_name (str): Project name where entity list live. + list_id (str): Entity list id from which item will be removed. + item_id (str): Item id that will be removed from entity list. - Example output:: + """ + con = get_server_api_connection() + return con.delete_entity_list_item( + project_name=project_name, + list_id=list_id, + item_id=item_id, + ) - {'eventId': 'a1bfbdee27c611eea7580242ac120003'} + +def get_rest_project( + project_name: str, +) -> Optional["ProjectDict"]: + """Query project by name. + + This call returns project with anatomy data. Args: - src_filepath (str): Path to a zip file. - progress (Optional[TransferProgress]): Object to keep track about - upload state. + project_name (str): Name of project. Returns: - dict[str, Any]: Response data from server. + Optional[ProjectDict]: Project entity data or 'None' if + project was not found. """ con = get_server_api_connection() - return con.upload_addon_zip( - src_filepath=src_filepath, - progress=progress, + return con.get_rest_project( + project_name=project_name, ) -def download_addon_private_file( - addon_name: str, - addon_version: str, - filename: str, - destination_dir: str, - destination_filename: Optional[str] = None, - chunk_size: Optional[int] = None, - progress: Optional[TransferProgress] = None, -) -> str: - """Download a file from addon private files. - - This method requires to have authorized token available. Private files - are not under '/api' restpoint. - - Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - filename (str): Filename in private folder on server. - destination_dir (str): Where the file should be downloaded. - destination_filename (Optional[str]): Name of destination - filename. Source filename is used if not passed. - chunk_size (Optional[int]): Download chunk size. - progress (Optional[TransferProgress]): Object that gives ability - to track download progress. +def get_rest_projects( + active: Optional[bool] = True, + library: Optional[bool] = None, +) -> Generator["ProjectDict", None, None]: + """Query available project entities. + + User must be logged in. + + Args: + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. Returns: - str: Filepath to downloaded file. + Generator[ProjectDict, None, None]: Available projects. """ con = get_server_api_connection() - return con.download_addon_private_file( - addon_name=addon_name, - addon_version=addon_version, - filename=filename, - destination_dir=destination_dir, - destination_filename=destination_filename, - chunk_size=chunk_size, - progress=progress, + return con.get_rest_projects( + active=active, + library=library, ) -def get_event( - event_id: str, -) -> Optional[dict[str, Any]]: - """Query full event data by id. +def get_project_names( + active: Optional[bool] = True, + library: Optional[bool] = None, +) -> list[str]: + """Receive available project names. - Events received using event server do not contain full information. To - get the full event information is required to receive it explicitly. + User must be logged in. Args: - event_id (str): Event id. + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. Returns: - dict[str, Any]: Full event data. + list[str]: List of available project names. """ con = get_server_api_connection() - return con.get_event( - event_id=event_id, + return con.get_project_names( + active=active, + library=library, ) -def get_events( - topics: Optional[Iterable[str]] = None, - event_ids: Optional[Iterable[str]] = None, - project_names: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - users: Optional[Iterable[str]] = None, - include_logs: Optional[bool] = None, - has_children: Optional[bool] = None, - newer_than: Optional[str] = None, - older_than: Optional[str] = None, +def get_projects( + active: Optional[bool] = True, + library: Optional[bool] = None, fields: Optional[Iterable[str]] = None, - limit: Optional[int] = None, - order: Optional[SortOrder] = None, - states: Optional[Iterable[str]] = None, -) -> Generator[dict[str, Any], None, None]: - """Get events from server with filtering options. - - Notes: - Not all event happen on a project. + own_attributes: bool = False, +) -> Generator["ProjectDict", None, None]: + """Get projects. Args: - topics (Optional[Iterable[str]]): Name of topics. - event_ids (Optional[Iterable[str]]): Event ids. - project_names (Optional[Iterable[str]]): Project on which - event happened. - statuses (Optional[Iterable[str]]): Filtering by statuses. - users (Optional[Iterable[str]]): Filtering by users - who created/triggered an event. - include_logs (Optional[bool]): Query also log events. - has_children (Optional[bool]): Event is with/without children - events. If 'None' then all events are returned, default. - newer_than (Optional[str]): Return only events newer than given - iso datetime string. - older_than (Optional[str]): Return only events older than given - iso datetime string. - fields (Optional[Iterable[str]]): Fields that should be received - for each event. - limit (Optional[int]): Limit number of events to be fetched. - order (Optional[SortOrder]): Order events in ascending - or descending order. It is recommended to set 'limit' - when used descending. - states (Optional[Iterable[str]]): DEPRECATED Filtering by states. - Use 'statuses' instead. + active (Optional[bool]): Filter active or inactive projects. + Filter is disabled when 'None' is passed. + library (Optional[bool]): Filter library projects. Filter is + disabled when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - Generator[dict[str, Any]]: Available events matching filters. + Generator[ProjectDict, None, None]: Queried projects. """ con = get_server_api_connection() - return con.get_events( - topics=topics, - event_ids=event_ids, - project_names=project_names, - statuses=statuses, - users=users, - include_logs=include_logs, - has_children=has_children, - newer_than=newer_than, - older_than=older_than, + return con.get_projects( + active=active, + library=library, fields=fields, - limit=limit, - order=order, - states=states, + own_attributes=own_attributes, ) -def update_event( - event_id: str, - sender: Optional[str] = None, - project_name: Optional[str] = None, - username: Optional[str] = None, - status: Optional[str] = None, - description: Optional[str] = None, - summary: Optional[dict[str, Any]] = None, - payload: Optional[dict[str, Any]] = None, - progress: Optional[int] = None, - retries: Optional[int] = None, -): - """Update event data. +def get_project( + project_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["ProjectDict"]: + """Get project. Args: - event_id (str): Event id. - sender (Optional[str]): New sender of event. - project_name (Optional[str]): New project name. - username (Optional[str]): New username. - status (Optional[str]): New event status. Enum: "pending", - "in_progress", "finished", "failed", "aborted", "restarted" - description (Optional[str]): New description. - summary (Optional[dict[str, Any]]): New summary. - payload (Optional[dict[str, Any]]): New payload. - progress (Optional[int]): New progress. Range [0-100]. - retries (Optional[int]): New retries. + project_name (str): Name of project. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[ProjectDict]: Project entity data or None + if project was not found. """ con = get_server_api_connection() - return con.update_event( - event_id=event_id, - sender=sender, + return con.get_project( project_name=project_name, - username=username, - status=status, - description=description, - summary=summary, - payload=payload, - progress=progress, - retries=retries, + fields=fields, + own_attributes=own_attributes, ) -def dispatch_event( - topic: str, - sender: Optional[str] = None, - event_hash: Optional[str] = None, - project_name: Optional[str] = None, - username: Optional[str] = None, - depends_on: Optional[str] = None, - description: Optional[str] = None, - summary: Optional[dict[str, Any]] = None, - payload: Optional[dict[str, Any]] = None, - finished: bool = True, - store: bool = True, - dependencies: Optional[list[str]] = None, -): - """Dispatch event to server. +def create_project( + project_name: str, + project_code: str, + library_project: bool = False, + preset_name: Optional[str] = None, +) -> "ProjectDict": + """Create project using AYON settings. + + This project creation function is not validating project entity on + creation. It is because project entity is created blindly with only + minimum required information about project which is name and code. + + Entered project name must be unique and project must not exist yet. + + Note: + This function is here to be OP v4 ready but in v3 has more logic + to do. That's why inner imports are in the body. Args: - topic (str): Event topic used for filtering of listeners. - sender (Optional[str]): Sender of event. - event_hash (Optional[str]): Event hash. - project_name (Optional[str]): Project name. - depends_on (Optional[str]): Add dependency to another event. - username (Optional[str]): Username which triggered event. - description (Optional[str]): Description of event. - summary (Optional[dict[str, Any]]): Summary of event that can - be used for simple filtering on listeners. - payload (Optional[dict[str, Any]]): Full payload of event data with - all details. - finished (Optional[bool]): Mark event as finished on dispatch. - store (Optional[bool]): Store event in event queue for possible - future processing otherwise is event send only - to active listeners. - dependencies (Optional[list[str]]): Deprecated. - List of event id dependencies. + project_name (str): New project name. Should be unique. + project_code (str): Project's code should be unique too. + library_project (Optional[bool]): Project is library project. + preset_name (Optional[str]): Name of anatomy preset. Default is + used if not passed. + + Raises: + ValueError: When project name already exists. Returns: - RestApiResponse: Response from server. + ProjectDict: Created project entity. """ con = get_server_api_connection() - return con.dispatch_event( - topic=topic, - sender=sender, - event_hash=event_hash, + return con.create_project( project_name=project_name, - username=username, - depends_on=depends_on, - description=description, - summary=summary, - payload=payload, - finished=finished, - store=store, - dependencies=dependencies, + project_code=project_code, + library_project=library_project, + preset_name=preset_name, ) -def delete_event( - event_id: str, +def update_project( + project_name: str, + library: Optional[bool] = None, + folder_types: Optional[list[dict[str, Any]]] = None, + task_types: Optional[list[dict[str, Any]]] = None, + link_types: Optional[list[dict[str, Any]]] = None, + statuses: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[dict[str, Any]]] = None, + config: Optional[dict[str, Any]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + active: Optional[bool] = None, + project_code: Optional[str] = None, + **changes, ): - """Delete event by id. - - Supported since AYON server 1.6.0. + """Update project entity on server. Args: - event_id (str): Event id. - - Returns: - RestApiResponse: Response from server. + 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[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. """ con = get_server_api_connection() - return con.delete_event( - event_id=event_id, + return con.update_project( + project_name=project_name, + library=library, + folder_types=folder_types, + task_types=task_types, + link_types=link_types, + statuses=statuses, + tags=tags, + config=config, + attrib=attrib, + data=data, + active=active, + project_code=project_code, + **changes, ) -def enroll_event_job( - source_topic: "Union[str, list[str]]", - target_topic: str, - sender: str, - description: Optional[str] = None, - sequential: Optional[bool] = None, - events_filter: Optional["EventFilter"] = None, - max_retries: Optional[int] = None, - ignore_older_than: Optional[str] = None, - ignore_sender_types: Optional[str] = None, +def delete_project( + project_name: str, ): - """Enroll job based on events. - - Enroll will find first unprocessed event with 'source_topic' and will - create new event with 'target_topic' for it and return the new event - data. - - Use 'sequential' to control that only single target event is created - at same time. Creation of new target events is blocked while there is - at least one unfinished event with target topic, when set to 'True'. - This helps when order of events matter and more than one process using - the same target is running at the same time. - - Make sure the new event has updated status to '"finished"' status - when you're done with logic - - Target topic should not clash with other processes/services. - - Created target event have 'dependsOn' key where is id of source topic. + """Delete project from server. - Use-case: - - Service 1 is creating events with topic 'my.leech' - - Service 2 process 'my.leech' and uses target topic 'my.process' - - this service can run on 1-n machines - - all events must be processed in a sequence by their creation - time and only one event can be processed at a time - - in this case 'sequential' should be set to 'True' so only - one machine is actually processing events, but if one goes - down there are other that can take place - - Service 3 process 'my.leech' and uses target topic 'my.discover' - - this service can run on 1-n machines - - order of events is not important - - 'sequential' should be 'False' + This will completely remove project from server without any step back. Args: - source_topic (Union[str, list[str]]): Source topic to enroll with - wildcards '*', or explicit list of topics. - target_topic (str): Topic of dependent event. - sender (str): Identifier of sender (e.g. service name or username). - description (Optional[str]): Human readable text shown - in target event. - sequential (Optional[bool]): The source topic must be processed - in sequence. - events_filter (Optional[dict[str, Any]]): Filtering conditions - to filter the source event. For more technical specifications - look to server backed 'ayon_server.sqlfilter.Filter'. - TODO: Add example of filters. - max_retries (Optional[int]): How many times can be event retried. - Default value is based on server (3 at the time of this PR). - ignore_older_than (Optional[int]): Ignore events older than - given number in days. - ignore_sender_types (Optional[list[str]]): Ignore events triggered - by given sender types. - - Returns: - Optional[dict[str, Any]]: None if there is no event matching - filters. Created event with 'target_topic'. + project_name (str): Project name that will be removed. """ con = get_server_api_connection() - return con.enroll_event_job( - source_topic=source_topic, - target_topic=target_topic, - sender=sender, - description=description, - sequential=sequential, - events_filter=events_filter, - max_retries=max_retries, - ignore_older_than=ignore_older_than, - ignore_sender_types=ignore_sender_types, + return con.delete_project( + project_name=project_name, ) @@ -5284,1215 +5245,1254 @@ def delete_folder( Args: project_name (str): Project name. folder_id (str): Folder id to delete. - force (Optional[bool]): Folder delete folder with all children - folder, products, versions and representations. - - """ - con = get_server_api_connection() - return con.delete_folder( - project_name=project_name, - folder_id=folder_id, - force=force, - ) - - -def get_full_link_type_name( - link_type_name: str, - input_type: str, - output_type: str, -) -> str: - """Calculate full link type name used for query from server. - - Args: - link_type_name (str): Type of link. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - - Returns: - str: Full name of link type used for query from server. - - """ - con = get_server_api_connection() - return con.get_full_link_type_name( - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, - ) - - -def get_link_types( - project_name: str, -) -> list[dict[str, Any]]: - """All link types available on a project. - - Example output: - [ - { - "name": "reference|folder|folder", - "link_type": "reference", - "input_type": "folder", - "output_type": "folder", - "data": {} - } - ] - - Args: - project_name (str): Name of project where to look for link types. - - Returns: - list[dict[str, Any]]: Link types available on project. - - """ - con = get_server_api_connection() - return con.get_link_types( - project_name=project_name, - ) - - -def get_link_type( - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, -) -> Optional[dict[str, Any]]: - """Get link type data. - - There is not dedicated REST endpoint to get single link type, - so method 'get_link_types' is used. - - Example output: - { - "name": "reference|folder|folder", - "link_type": "reference", - "input_type": "folder", - "output_type": "folder", - "data": {} - } - - Args: - project_name (str): Project where link type is available. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - - Returns: - Optional[dict[str, Any]]: Link type information. - - """ - con = get_server_api_connection() - return con.get_link_type( - project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, - ) - - -def create_link_type( - project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - data: Optional[dict[str, Any]] = None, -): - """Create or update link type on server. - - Warning: - Because PUT is used for creation it is also used for update. - - Args: - project_name (str): Project where link type is created. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - data (Optional[dict[str, Any]]): Additional data related to link. - - Raises: - HTTPRequestError: Server error happened. + force (Optional[bool]): Folder delete folder with all children + folder, products, versions and representations. """ con = get_server_api_connection() - return con.create_link_type( + return con.delete_folder( project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, - data=data, + folder_id=folder_id, + force=force, ) -def delete_link_type( +def get_rest_task( project_name: str, - link_type_name: str, - input_type: str, - output_type: str, -): - """Remove link type from project. - - Args: - project_name (str): Project where link type is created. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - - Raises: - HTTPRequestError: Server error happened. - - """ + task_id: str, +) -> Optional["TaskDict"]: con = get_server_api_connection() - return con.delete_link_type( + return con.get_rest_task( project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, + task_id=task_id, ) -def make_sure_link_type_exists( +def get_tasks( project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - data: Optional[dict[str, Any]] = None, -): - """Make sure link type exists on a project. + task_ids: Optional[Iterable[str]] = None, + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + folder_ids: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Generator["TaskDict", None, None]: + """Query task entities from server. Args: project_name (str): Name of project. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - data (Optional[dict[str, Any]]): Link type related data. + task_ids (Iterable[str]): Task ids to filter. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + folder_ids (Iterable[str]): Ids of task parents. Use 'None' + if folder is direct child of project. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Generator[TaskDict, None, None]: Queried task entities. """ con = get_server_api_connection() - return con.make_sure_link_type_exists( + return con.get_tasks( project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, - data=data, + task_ids=task_ids, + task_names=task_names, + task_types=task_types, + folder_ids=folder_ids, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, + tags=tags, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def create_link( +def get_task_by_name( project_name: str, - link_type_name: str, - input_id: str, - input_type: str, - output_id: str, - output_type: str, - link_name: Optional[str] = None, -): - """Create link between 2 entities. - - Link has a type which must already exists on a project. - - Example output:: - - { - "id": "59a212c0d2e211eda0e20242ac120002" - } + folder_id: str, + task_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["TaskDict"]: + """Query task entity by name and folder id. Args: - project_name (str): Project where the link is created. - link_type_name (str): Type of link. - input_id (str): Input entity id. - input_type (str): Entity type of input entity. - output_id (str): Output entity id. - output_type (str): Entity type of output entity. - link_name (Optional[str]): Name of link. - Available from server version '1.0.0-rc.6'. + project_name (str): Name of project where to look for queried + entities. + folder_id (str): Folder id. + task_name (str): Task name + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - dict[str, str]: Information about link. - - Raises: - HTTPRequestError: Server error happened. + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.create_link( + return con.get_task_by_name( project_name=project_name, - link_type_name=link_type_name, - input_id=input_id, - input_type=input_type, - output_id=output_id, - output_type=output_type, - link_name=link_name, + folder_id=folder_id, + task_name=task_name, + fields=fields, + own_attributes=own_attributes, ) -def delete_link( +def get_task_by_id( project_name: str, - link_id: str, -): - """Remove link by id. + task_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["TaskDict"]: + """Query task entity by id. Args: - project_name (str): Project where link exists. - link_id (str): Id of link. + project_name (str): Name of project where to look for queried + entities. + task_id (str): Task id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. - Raises: - HTTPRequestError: Server error happened. + Returns: + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.delete_link( + return con.get_task_by_id( project_name=project_name, - link_id=link_id, + task_id=task_id, + fields=fields, + own_attributes=own_attributes, ) -def get_entities_links( +def get_tasks_by_folder_paths( project_name: str, - entity_type: str, - entity_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - link_names: Optional[Iterable[str]] = None, - link_name_regex: Optional[str] = None, -) -> dict[str, list[dict[str, Any]]]: - """Helper method to get links from server for entity types. - - .. highlight:: text - .. code-block:: text - - Example output: - { - "59a212c0d2e211eda0e20242ac120001": [ - { - "id": "59a212c0d2e211eda0e20242ac120002", - "linkType": "reference", - "description": "reference link between folders", - "projectName": "my_project", - "author": "frantadmin", - "entityId": "b1df109676db11ed8e8c6c9466b19aa8", - "entityType": "folder", - "direction": "out" - }, - ... - ], - ... - } + folder_paths: Iterable[str], + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> dict[str, list["TaskDict"]]: + """Query task entities from server by folder paths. Args: - project_name (str): Project where links are. - entity_type (Literal["folder", "task", "product", - "version", "representations"]): Entity type. - entity_ids (Optional[Iterable[str]]): Ids of entities for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - link_names (Optional[Iterable[str]]): Link name filters. - link_name_regex (Optional[str]): Regex filter for link name. + project_name (str): Name of project. + folder_paths (list[str]): Folder paths. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - dict[str, list[dict[str, Any]]]: Link info by entity ids. + dict[str, list[TaskDict]]: Task entities by + folder path. """ con = get_server_api_connection() - return con.get_entities_links( + return con.get_tasks_by_folder_paths( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - link_types=link_types, - link_direction=link_direction, - link_names=link_names, - link_name_regex=link_name_regex, + folder_paths=folder_paths, + task_names=task_names, + task_types=task_types, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, + tags=tags, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def get_folders_links( +def get_tasks_by_folder_path( project_name: str, - folder_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query folders links from server. + folder_path: str, + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> list["TaskDict"]: + """Query task entities from server by folder path. Args: - project_name (str): Project where links are. - folder_ids (Optional[Iterable[str]]): Ids of folders for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by folder ids. + project_name (str): Name of project. + folder_path (str): Folder path. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. """ con = get_server_api_connection() - return con.get_folders_links( + return con.get_tasks_by_folder_path( project_name=project_name, - folder_ids=folder_ids, - link_types=link_types, - link_direction=link_direction, + folder_path=folder_path, + task_names=task_names, + task_types=task_types, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, + tags=tags, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def get_folder_links( +def get_task_by_folder_path( project_name: str, - folder_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query folder links from server. + folder_path: str, + task_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["TaskDict"]: + """Query task entity by folder path and task name. Args: - project_name (str): Project where links are. - folder_id (str): Folder id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Project name. + folder_path (str): Folder path. + task_name (str): Task name. + fields (Optional[Iterable[str]]): Task fields that should + be returned. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - list[dict[str, Any]]: Link info of folder. + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.get_folder_links( + return con.get_task_by_folder_path( project_name=project_name, - folder_id=folder_id, - link_types=link_types, - link_direction=link_direction, + folder_path=folder_path, + task_name=task_name, + fields=fields, + own_attributes=own_attributes, ) -def get_tasks_links( +def create_task( project_name: str, - task_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query tasks links from server. + name: str, + task_type: str, + folder_id: str, + label: Optional[str] = None, + assignees: Optional[Iterable[str]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + task_id: Optional[str] = None, +) -> str: + """Create new task. Args: - project_name (str): Project where links are. - task_ids (Optional[Iterable[str]]): Ids of tasks for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Project name. + name (str): Folder name. + task_type (str): Task type. + folder_id (str): Parent folder id. + label (Optional[str]): Label of folder. + assignees (Optional[Iterable[str]]): Task assignees. + attrib (Optional[dict[str, Any]]): Task attributes. + data (Optional[dict[str, Any]]): Task data. + tags (Optional[Iterable[str]]): Task tags. + status (Optional[str]): Task status. + active (Optional[bool]): Task active state. + thumbnail_id (Optional[str]): Task thumbnail id. + task_id (Optional[str]): Task id. If not passed new id is + generated. Returns: - dict[str, list[dict[str, Any]]]: Link info by task ids. + str: Task id. """ con = get_server_api_connection() - return con.get_tasks_links( + return con.create_task( project_name=project_name, - task_ids=task_ids, - link_types=link_types, - link_direction=link_direction, + name=name, + task_type=task_type, + folder_id=folder_id, + label=label, + assignees=assignees, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, + task_id=task_id, ) -def get_task_links( +def update_task( project_name: str, task_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query task links from server. - - Args: - project_name (str): Project where links are. - task_id (str): Task id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of task. + name: Optional[str] = None, + task_type: Optional[str] = None, + folder_id: Optional[str] = None, + label: Optional[str] = NOT_SET, + assignees: Optional[list[str]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = NOT_SET, +): + """Update task entity on server. - """ - con = get_server_api_connection() - return con.get_task_links( - project_name=project_name, - task_id=task_id, - link_types=link_types, - link_direction=link_direction, - ) + Do not pass ``label`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. + Update of ``data`` will override existing value on folder entity. -def get_products_links( - project_name: str, - product_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query products links from server. + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. Args: - project_name (str): Project where links are. - product_ids (Optional[Iterable[str]]): Ids of products for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by product ids. + project_name (str): Project name. + task_id (str): Task id. + name (Optional[str]): New name. + task_type (Optional[str]): New task type. + folder_id (Optional[str]): New folder id. + label (Optional[Optional[str]]): New label. + assignees (Optional[str]): New assignees. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[str]): New thumbnail id. """ con = get_server_api_connection() - return con.get_products_links( + return con.update_task( project_name=project_name, - product_ids=product_ids, - link_types=link_types, - link_direction=link_direction, + task_id=task_id, + name=name, + task_type=task_type, + folder_id=folder_id, + label=label, + assignees=assignees, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, ) -def get_product_links( +def delete_task( project_name: str, - product_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query product links from server. + task_id: str, +): + """Delete task. Args: - project_name (str): Project where links are. - product_id (str): Product id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of product. + project_name (str): Project name. + task_id (str): Task id to delete. """ con = get_server_api_connection() - return con.get_product_links( + return con.delete_task( project_name=project_name, - product_id=product_id, - link_types=link_types, - link_direction=link_direction, + task_id=task_id, ) -def get_versions_links( +def get_rest_product( project_name: str, - version_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query versions links from server. - - Args: - project_name (str): Project where links are. - version_ids (Optional[Iterable[str]]): Ids of versions for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by version ids. - - """ + product_id: str, +) -> Optional["ProductDict"]: con = get_server_api_connection() - return con.get_versions_links( + return con.get_rest_product( project_name=project_name, - version_ids=version_ids, - link_types=link_types, - link_direction=link_direction, + product_id=product_id, ) -def get_version_links( +def get_products( project_name: str, - version_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query version links from server. + product_ids: Optional[Iterable[str]] = None, + product_names: Optional[Iterable[str]] = None, + folder_ids: Optional[Iterable[str]] = None, + product_types: Optional[Iterable[str]] = None, + product_name_regex: Optional[str] = None, + product_path_regex: Optional[str] = None, + names_by_folder_ids: Optional[dict[str, Iterable[str]]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Generator["ProductDict", None, None]: + """Query products from server. + + Todos: + Separate 'name_by_folder_ids' filtering to separated method. It + cannot be combined with some other filters. Args: - project_name (str): Project where links are. - version_id (str): Version id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project. + product_ids (Optional[Iterable[str]]): Task ids to filter. + product_names (Optional[Iterable[str]]): Task names used for + filtering. + folder_ids (Optional[Iterable[str]]): Ids of task parents. + Use 'None' if folder is direct child of project. + product_types (Optional[Iterable[str]]): Product types used for + filtering. + product_name_regex (Optional[str]): Filter products by name regex. + product_path_regex (Optional[str]): Filter products by path regex. + Path starts with folder path and ends with product name. + names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product + name filtering by folder id. + statuses (Optional[Iterable[str]]): Product statuses used + for filtering. + tags (Optional[Iterable[str]]): Product tags used + for filtering. + active (Optional[bool]): Filter active/inactive products. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. Returns: - list[dict[str, Any]]: Link info of version. + Generator[ProductDict, None, None]: Queried product entities. """ con = get_server_api_connection() - return con.get_version_links( + return con.get_products( project_name=project_name, - version_id=version_id, - link_types=link_types, - link_direction=link_direction, + product_ids=product_ids, + product_names=product_names, + folder_ids=folder_ids, + product_types=product_types, + product_name_regex=product_name_regex, + product_path_regex=product_path_regex, + names_by_folder_ids=names_by_folder_ids, + statuses=statuses, + tags=tags, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def get_representations_links( +def get_product_by_id( project_name: str, - representation_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query representations links from server. + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["ProductDict"]: + """Query product entity by id. Args: - project_name (str): Project where links are. - representation_ids (Optional[Iterable[str]]): Ids of - representations for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project where to look for queried + entities. + product_id (str): Product id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. Returns: - dict[str, list[dict[str, Any]]]: Link info by representation ids. + Optional[ProductDict]: Product entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_representations_links( + return con.get_product_by_id( project_name=project_name, - representation_ids=representation_ids, - link_types=link_types, - link_direction=link_direction, + product_id=product_id, + fields=fields, + own_attributes=own_attributes, ) -def get_representation_links( +def get_product_by_name( project_name: str, - representation_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query representation links from server. + product_name: str, + folder_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["ProductDict"]: + """Query product entity by name and folder id. Args: - project_name (str): Project where links are. - representation_id (str): Representation id for which links - should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project where to look for queried + entities. + product_name (str): Product name. + folder_id (str): Folder id (Folder is a parent of products). + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. Returns: - list[dict[str, Any]]: Link info of representation. + Optional[ProductDict]: Product entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_representation_links( + return con.get_product_by_name( project_name=project_name, - representation_id=representation_id, - link_types=link_types, - link_direction=link_direction, + product_name=product_name, + folder_id=folder_id, + fields=fields, + own_attributes=own_attributes, ) -def get_entity_lists( - project_name: str, - *, - list_ids: Optional[Iterable[str]] = None, - active: Optional[bool] = None, +def get_product_types( fields: Optional[Iterable[str]] = None, -) -> Generator[Dict[str, Any], None, None]: - """Fetch entity lists from server. +) -> list["ProductTypeDict"]: + """Types of products. + + This is server wide information. Product types have 'name', 'icon' and + 'color'. Args: - project_name (str): Project name where entity lists are. - list_ids (Optional[Iterable[str]]): List of entity list ids to - fetch. - active (Optional[bool]): Filter by active state of entity lists. - fields (Optional[Iterable[str]]): Fields to fetch from server. + fields (Optional[Iterable[str]]): Product types fields to query. Returns: - Generator[Dict[str, Any], None, None]: Entity list entities - matching defined filters. + list[ProductTypeDict]: Product types information. """ con = get_server_api_connection() - return con.get_entity_lists( - project_name=project_name, - list_ids=list_ids, - active=active, + return con.get_product_types( fields=fields, ) -def get_entity_list_rest( +def get_project_product_types( project_name: str, - list_id: str, -) -> Optional[Dict[str, Any]]: - """Get entity list by id using REST API. + fields: Optional[Iterable[str]] = None, +) -> list["ProductTypeDict"]: + """DEPRECATED Types of products available in a project. + + Filter only product types available in a project. Args: - project_name (str): Project name. - list_id (str): Entity list id. + project_name (str): Name of the project where to look for + product types. + fields (Optional[Iterable[str]]): Product types fields to query. Returns: - Optional[Dict[str, Any]]: Entity list data or None if not found. + list[ProductTypeDict]: Product types information. """ con = get_server_api_connection() - return con.get_entity_list_rest( + return con.get_project_product_types( project_name=project_name, - list_id=list_id, + fields=fields, ) -def get_entity_list_by_id( - project_name: str, - list_id: str, - fields: Optional[Iterable[str]] = None, -) -> Optional[Dict[str, Any]]: - """Get entity list by id using GraphQl. +def get_product_type_names( + project_name: Optional[str] = None, + product_ids: Optional[Iterable[str]] = None, +) -> set[str]: + """DEPRECATED Product type names. + + Warnings: + This function will be probably removed. Matters if 'products_id' + filter has real use-case. Args: - project_name (str): Project name. - list_id (str): Entity list id. - fields (Optional[Iterable[str]]): Fields to fetch from server. + project_name (Optional[str]): Name of project where to look for + queried entities. + product_ids (Optional[Iterable[str]]): Product ids filter. Can be + used only with 'project_name'. Returns: - Optional[Dict[str, Any]]: Entity list data or None if not found. + set[str]: Product type names. """ con = get_server_api_connection() - return con.get_entity_list_by_id( + return con.get_product_type_names( project_name=project_name, - list_id=list_id, - fields=fields, + product_ids=product_ids, ) -def create_entity_list( +def create_product( project_name: str, - entity_type: "EntityListEntityType", - label: str, - *, - list_type: Optional[str] = None, - access: Optional[Dict[str, Any]] = None, - attrib: Optional[List[Dict[str, Any]]] = None, - data: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[str]] = None, - template: Optional[Dict[str, Any]] = None, - owner: Optional[str] = None, + name: str, + product_type: str, + folder_id: str, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, active: Optional[bool] = None, - items: Optional[List[Dict[str, Any]]] = None, - list_id: Optional[str] = None, + product_id: Optional[str] = None, ) -> str: - """Create entity list. + """Create new product. Args: - project_name (str): Project name where entity list lives. - entity_type (EntityListEntityType): Which entity types can be - used in list. - label (str): Entity list label. - list_type (Optional[str]): Entity list type. - access (Optional[dict[str, Any]]): Access control for entity list. - attrib (Optional[dict[str, Any]]): Attribute values of - entity list. - data (Optional[dict[str, Any]]): Custom data of entity list. - tags (Optional[list[str]]): Entity list tags. - template (Optional[dict[str, Any]]): Dynamic list template. - owner (Optional[str]): New owner of the list. - active (Optional[bool]): Change active state of entity list. - items (Optional[list[dict[str, Any]]]): Initial items in - entity list. - list_id (Optional[str]): Entity list id. + project_name (str): Project name. + name (str): Product name. + product_type (str): Product type. + folder_id (str): Parent folder id. + attrib (Optional[dict[str, Any]]): Product attributes. + data (Optional[dict[str, Any]]): Product data. + tags (Optional[Iterable[str]]): Product tags. + status (Optional[str]): Product status. + active (Optional[bool]): Product active state. + product_id (Optional[str]): Product id. If not passed new id is + generated. + + Returns: + str: Product id. """ con = get_server_api_connection() - return con.create_entity_list( + return con.create_product( project_name=project_name, - entity_type=entity_type, - label=label, - list_type=list_type, - access=access, + name=name, + product_type=product_type, + folder_id=folder_id, attrib=attrib, data=data, tags=tags, - template=template, - owner=owner, + status=status, active=active, - items=items, - list_id=list_id, + product_id=product_id, ) -def update_entity_list( +def update_product( project_name: str, - list_id: str, - *, - label: Optional[str] = None, - access: Optional[Dict[str, Any]] = None, - attrib: Optional[List[Dict[str, Any]]] = None, - data: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[str]] = None, - owner: Optional[str] = None, + product_id: str, + name: Optional[str] = None, + folder_id: Optional[str] = None, + product_type: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, active: Optional[bool] = None, -) -> None: - """Update entity list. +): + """Update product entity on server. + + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. Args: - project_name (str): Project name where entity list lives. - list_id (str): Entity list id that will be updated. - label (Optional[str]): New label of entity list. - access (Optional[dict[str, Any]]): Access control for entity list. - attrib (Optional[dict[str, Any]]): Attribute values of - entity list. - data (Optional[dict[str, Any]]): Custom data of entity list. - tags (Optional[list[str]]): Entity list tags. - owner (Optional[str]): New owner of the list. - active (Optional[bool]): Change active state of entity list. + project_name (str): Project name. + product_id (str): Product id. + name (Optional[str]): New product name. + folder_id (Optional[str]): New product id. + product_type (Optional[str]): New product type. + attrib (Optional[dict[str, Any]]): New product attributes. + data (Optional[dict[str, Any]]): New product data. + tags (Optional[Iterable[str]]): New product tags. + status (Optional[str]): New product status. + active (Optional[bool]): New product active state. """ con = get_server_api_connection() - return con.update_entity_list( + return con.update_product( project_name=project_name, - list_id=list_id, - label=label, - access=access, + product_id=product_id, + name=name, + folder_id=folder_id, + product_type=product_type, attrib=attrib, data=data, tags=tags, - owner=owner, + status=status, active=active, ) -def delete_entity_list( - project_name: str, - list_id: str, -) -> None: - """Delete entity list from project. - - Args: - project_name (str): Project name. - list_id (str): Entity list id that will be removed. - - """ - con = get_server_api_connection() - return con.delete_entity_list( - project_name=project_name, - list_id=list_id, - ) - - -def get_entity_list_attribute_definitions( +def delete_product( project_name: str, - list_id: str, -) -> List["EntityListAttributeDefinitionDict"]: - """Get attribute definitioins on entity list. + product_id: str, +): + """Delete product. Args: project_name (str): Project name. - list_id (str): Entity list id. - - Returns: - List[EntityListAttributeDefinitionDict]: List of attribute - definitions. + product_id (str): Product id to delete. """ con = get_server_api_connection() - return con.get_entity_list_attribute_definitions( + return con.delete_product( project_name=project_name, - list_id=list_id, + product_id=product_id, ) -def set_entity_list_attribute_definitions( +def get_rest_version( project_name: str, - list_id: str, - attribute_definitions: List["EntityListAttributeDefinitionDict"], -) -> None: - """Set attribute definitioins on entity list. - - Args: - project_name (str): Project name. - list_id (str): Entity list id. - attribute_definitions (List[EntityListAttributeDefinitionDict]): - List of attribute definitions. - - """ + version_id: str, +) -> Optional["VersionDict"]: con = get_server_api_connection() - return con.set_entity_list_attribute_definitions( + return con.get_rest_version( project_name=project_name, - list_id=list_id, - attribute_definitions=attribute_definitions, + version_id=version_id, ) -def create_entity_list_item( +def get_versions( project_name: str, - list_id: str, - *, - position: Optional[int] = None, - label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - item_id: Optional[str] = None, -) -> str: - """Create entity list item. - - Args: - project_name (str): Project name where entity list lives. - list_id (str): Entity list id where item will be added. - position (Optional[int]): Position of item in entity list. - label (Optional[str]): Label of item in entity list. - attrib (Optional[dict[str, Any]]): Item attribute values. - data (Optional[dict[str, Any]]): Item data. - tags (Optional[list[str]]): Tags of item in entity list. - item_id (Optional[str]): Id of item that will be created. + version_ids: Optional[Iterable[str]] = None, + product_ids: Optional[Iterable[str]] = None, + task_ids: Optional[Iterable[str]] = None, + versions: Optional[Iterable[str]] = None, + hero: bool = True, + standard: bool = True, + latest: Optional[bool] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Generator["VersionDict", None, None]: + """Get version entities based on passed filters from server. + + Args: + project_name (str): Name of project where to look for versions. + version_ids (Optional[Iterable[str]]): Version ids used for + version filtering. + product_ids (Optional[Iterable[str]]): Product ids used for + version filtering. + task_ids (Optional[Iterable[str]]): Task ids used for + version filtering. + versions (Optional[Iterable[int]]): Versions we're interested in. + hero (Optional[bool]): Skip hero versions when set to False. + standard (Optional[bool]): Skip standard (non-hero) when + set to False. + latest (Optional[bool]): Return only latest version of standard + versions. This can be combined only with 'standard' attribute + set to True. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): Fields to be queried + for version. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - str: Item id. + Generator[VersionDict, None, None]: Queried version entities. """ con = get_server_api_connection() - return con.create_entity_list_item( + return con.get_versions( project_name=project_name, - list_id=list_id, - position=position, - label=label, - attrib=attrib, - data=data, + version_ids=version_ids, + product_ids=product_ids, + task_ids=task_ids, + versions=versions, + hero=hero, + standard=standard, + latest=latest, + statuses=statuses, tags=tags, - item_id=item_id, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def update_entity_list_items( +def get_version_by_id( project_name: str, - list_id: str, - items: List[Dict[str, Any]], - mode: "EntityListItemMode", -) -> None: - """Update items in entity list. + version_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query version entity by id. Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id. - items (List[Dict[str, Any]]): Entity list items. - mode (EntityListItemMode): Mode of items update. + project_name (str): Name of project where to look for queried + entities. + version_id (str): Version id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.update_entity_list_items( + return con.get_version_by_id( project_name=project_name, - list_id=list_id, - items=items, - mode=mode, + version_id=version_id, + fields=fields, + own_attributes=own_attributes, ) -def update_entity_list_item( +def get_version_by_name( project_name: str, - list_id: str, - item_id: str, - *, - new_list_id: Optional[str], - position: Optional[int] = None, - label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, -) -> None: - """Update item in entity list. + version: int, + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query version entity by version and product id. Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id where item lives. - item_id (str): Item id that will be removed from entity list. - new_list_id (Optional[str]): New entity list id where item will be - added. - position (Optional[int]): Position of item in entity list. - label (Optional[str]): Label of item in entity list. - attrib (Optional[dict[str, Any]]): Attributes of item in entity - list. - data (Optional[dict[str, Any]]): Custom data of item in - entity list. - tags (Optional[list[str]]): Tags of item in entity list. + project_name (str): Name of project where to look for queried + entities. + version (int): Version of version entity. + product_id (str): Product id. Product is a parent of version. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.update_entity_list_item( + return con.get_version_by_name( project_name=project_name, - list_id=list_id, - item_id=item_id, - new_list_id=new_list_id, - position=position, - label=label, - attrib=attrib, - data=data, - tags=tags, + version=version, + product_id=product_id, + fields=fields, + own_attributes=own_attributes, ) -def delete_entity_list_item( +def get_hero_version_by_id( project_name: str, - list_id: str, - item_id: str, -) -> None: - """Delete item from entity list. + version_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query hero version entity by id. Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id from which item will be removed. - item_id (str): Item id that will be removed from entity list. + project_name (str): Name of project where to look for queried + entities. + version_id (int): Hero version id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.delete_entity_list_item( + return con.get_hero_version_by_id( project_name=project_name, - list_id=list_id, - item_id=item_id, + version_id=version_id, + fields=fields, + own_attributes=own_attributes, ) -def get_rest_project( +def get_hero_version_by_product_id( project_name: str, -) -> Optional["ProjectDict"]: - """Query project by name. + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query hero version entity by product id. - This call returns project with anatomy data. + Only one hero version is available on a product. Args: - project_name (str): Name of project. + project_name (str): Name of project where to look for queried + entities. + product_id (int): Product id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - Optional[ProjectDict]: Project entity data or 'None' if - project was not found. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_rest_project( + return con.get_hero_version_by_product_id( project_name=project_name, + product_id=product_id, + fields=fields, + own_attributes=own_attributes, ) -def get_rest_projects( +def get_hero_versions( + project_name: str, + product_ids: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, active: Optional[bool] = True, - library: Optional[bool] = None, -) -> Generator["ProjectDict", None, None]: - """Query available project entities. + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Generator["VersionDict", None, None]: + """Query hero versions by multiple filters. - User must be logged in. + Only one hero version is available on a product. Args: - active (Optional[bool]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Optional[bool]): Filter standard/library projects. Both - are returned if 'None' is passed. + project_name (str): Name of project where to look for queried + entities. + product_ids (Optional[Iterable[str]]): Product ids. + version_ids (Optional[Iterable[str]]): Version ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - Generator[ProjectDict, None, None]: Available projects. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_rest_projects( + return con.get_hero_versions( + project_name=project_name, + product_ids=product_ids, + version_ids=version_ids, active=active, - library=library, + fields=fields, + own_attributes=own_attributes, ) -def get_project_names( +def get_last_versions( + project_name: str, + product_ids: Iterable[str], active: Optional[bool] = True, - library: Optional[bool] = None, -) -> list[str]: - """Receive available project names. - - User must be logged in. + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> dict[str, Optional["VersionDict"]]: + """Query last version entities by product ids. Args: - active (Optional[bool]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Optional[bool]): Filter standard/library projects. Both - are returned if 'None' is passed. + project_name (str): Project where to look for representation. + product_ids (Iterable[str]): Product ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - list[str]: List of available project names. + dict[str, Optional[VersionDict]]: Last versions by product id. """ con = get_server_api_connection() - return con.get_project_names( + return con.get_last_versions( + project_name=project_name, + product_ids=product_ids, active=active, - library=library, + fields=fields, + own_attributes=own_attributes, ) -def get_projects( +def get_last_version_by_product_id( + project_name: str, + product_id: str, active: Optional[bool] = True, - library: Optional[bool] = None, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Generator["ProjectDict", None, None]: - """Get projects. + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query last version entity by product id. Args: - active (Optional[bool]): Filter active or inactive projects. - Filter is disabled when 'None' is passed. - library (Optional[bool]): Filter library projects. Filter is - disabled when 'None' is passed. + project_name (str): Project where to look for representation. + product_id (str): Product id. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - Generator[ProjectDict, None, None]: Queried projects. + Optional[VersionDict]: Queried version entity or None. """ con = get_server_api_connection() - return con.get_projects( + return con.get_last_version_by_product_id( + project_name=project_name, + product_id=product_id, active=active, - library=library, fields=fields, own_attributes=own_attributes, ) -def get_project( +def get_last_version_by_product_name( project_name: str, + product_name: str, + folder_id: str, + active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["ProjectDict"]: - """Get project. + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query last version entity by product name and folder id. Args: - project_name (str): Name of project. + project_name (str): Project where to look for representation. + product_name (str): Product name. + folder_id (str): Folder id. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. Returns: - Optional[ProjectDict]: Project entity data or None - if project was not found. + Optional[VersionDict]: Queried version entity or None. """ con = get_server_api_connection() - return con.get_project( + return con.get_last_version_by_product_name( project_name=project_name, + product_name=product_name, + folder_id=folder_id, + active=active, fields=fields, own_attributes=own_attributes, ) -def create_project( +def version_is_latest( project_name: str, - project_code: str, - library_project: bool = False, - preset_name: Optional[str] = None, -) -> "ProjectDict": - """Create project using AYON settings. + version_id: str, +) -> bool: + """Is version latest from a product. - This project creation function is not validating project entity on - creation. It is because project entity is created blindly with only - minimum required information about project which is name and code. + Args: + project_name (str): Project where to look for representation. + version_id (str): Version id. - Entered project name must be unique and project must not exist yet. + Returns: + bool: Version is latest or not. - Note: - This function is here to be OP v4 ready but in v3 has more logic - to do. That's why inner imports are in the body. + """ + con = get_server_api_connection() + return con.version_is_latest( + project_name=project_name, + version_id=version_id, + ) - Args: - project_name (str): New project name. Should be unique. - project_code (str): Project's code should be unique too. - library_project (Optional[bool]): Project is library project. - preset_name (Optional[str]): Name of anatomy preset. Default is - used if not passed. - Raises: - ValueError: When project name already exists. +def create_version( + project_name: str, + version: int, + product_id: str, + task_id: Optional[str] = None, + author: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + version_id: Optional[str] = None, +) -> str: + """Create new version. + + Args: + project_name (str): Project name. + version (int): Version. + product_id (str): Parent product id. + task_id (Optional[str]): Parent task id. + author (Optional[str]): Version author. + attrib (Optional[dict[str, Any]]): Version attributes. + data (Optional[dict[str, Any]]): Version data. + tags (Optional[Iterable[str]]): Version tags. + status (Optional[str]): Version status. + active (Optional[bool]): Version active state. + thumbnail_id (Optional[str]): Version thumbnail id. + version_id (Optional[str]): Version id. If not passed new id is + generated. Returns: - ProjectDict: Created project entity. + str: Version id. """ con = get_server_api_connection() - return con.create_project( + return con.create_version( project_name=project_name, - project_code=project_code, - library_project=library_project, - preset_name=preset_name, + version=version, + product_id=product_id, + task_id=task_id, + author=author, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, + version_id=version_id, ) -def update_project( +def update_version( project_name: str, - library: Optional[bool] = None, - folder_types: Optional[list[dict[str, Any]]] = None, - task_types: Optional[list[dict[str, Any]]] = None, - link_types: Optional[list[dict[str, Any]]] = None, - statuses: Optional[list[dict[str, Any]]] = None, - tags: Optional[list[dict[str, Any]]] = None, - config: Optional[dict[str, Any]] = None, + version_id: str, + version: Optional[int] = None, + product_id: Optional[str] = None, + task_id: Optional[str] = NOT_SET, + author: Optional[str] = None, attrib: Optional[dict[str, Any]] = None, data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, active: Optional[bool] = None, - project_code: Optional[str] = None, - **changes, + thumbnail_id: Optional[str] = NOT_SET, ): - """Update project entity on server. + """Update version entity on server. + + Do not pass ``task_id`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. + + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. 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[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. + project_name (str): Project name. + version_id (str): Version id. + version (Optional[int]): New version. + product_id (Optional[str]): New product id. + task_id (Optional[str]): New task id. + author (Optional[str]): New author username. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[str]): New thumbnail id. """ con = get_server_api_connection() - return con.update_project( + return con.update_version( project_name=project_name, - library=library, - folder_types=folder_types, - task_types=task_types, - link_types=link_types, - statuses=statuses, - tags=tags, - config=config, + version_id=version_id, + version=version, + product_id=product_id, + task_id=task_id, + author=author, attrib=attrib, data=data, + tags=tags, + status=status, active=active, - project_code=project_code, - **changes, + thumbnail_id=thumbnail_id, ) -def delete_project( +def delete_version( project_name: str, + version_id: str, ): - """Delete project from server. - - This will completely remove project from server without any step back. + """Delete version. Args: - project_name (str): Project name that will be removed. + project_name (str): Project name. + version_id (str): Version id to delete. """ con = get_server_api_connection() - return con.delete_project( + return con.delete_version( project_name=project_name, + version_id=version_id, ) diff --git a/ayon_api/_products.py b/ayon_api/_products.py new file mode 100644 index 000000000..eff1384f5 --- /dev/null +++ b/ayon_api/_products.py @@ -0,0 +1,501 @@ +from __future__ import annotations + +import collections +import warnings +import typing +from typing import Optional, Iterable, Generator, Any + +from ._base import _BaseServerAPI, _PLACEHOLDER +from .utils import ( + prepare_list_filters, + create_entity_id, +) +from .graphql_queries import ( + products_graphql_query, + product_types_query, +) + +if typing.TYPE_CHECKING: + from .typing import ProductDict, ProductTypeDict + + +class _ProductsAPI(_BaseServerAPI): + def get_rest_product( + self, project_name: str, product_id: str + ) -> Optional["ProductDict"]: + return self.get_rest_entity_by_id(project_name, "product", product_id) + + def get_products( + self, + project_name: str, + product_ids: Optional[Iterable[str]] = None, + product_names: Optional[Iterable[str]]=None, + folder_ids: Optional[Iterable[str]]=None, + product_types: Optional[Iterable[str]]=None, + product_name_regex: Optional[str] = None, + product_path_regex: Optional[str] = None, + names_by_folder_ids: Optional[dict[str, Iterable[str]]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER + ) -> Generator["ProductDict", None, None]: + """Query products from server. + + Todos: + Separate 'name_by_folder_ids' filtering to separated method. It + cannot be combined with some other filters. + + Args: + project_name (str): Name of project. + product_ids (Optional[Iterable[str]]): Task ids to filter. + product_names (Optional[Iterable[str]]): Task names used for + filtering. + folder_ids (Optional[Iterable[str]]): Ids of task parents. + Use 'None' if folder is direct child of project. + product_types (Optional[Iterable[str]]): Product types used for + filtering. + product_name_regex (Optional[str]): Filter products by name regex. + product_path_regex (Optional[str]): Filter products by path regex. + Path starts with folder path and ends with product name. + names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product + name filtering by folder id. + statuses (Optional[Iterable[str]]): Product statuses used + for filtering. + tags (Optional[Iterable[str]]): Product tags used + for filtering. + active (Optional[bool]): Filter active/inactive products. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. + + Returns: + Generator[ProductDict, None, None]: Queried product entities. + + """ + if not project_name: + return + + # Prepare these filters before 'name_by_filter_ids' filter + filter_product_names = None + if product_names is not None: + filter_product_names = set(product_names) + if not filter_product_names: + return + + filter_folder_ids = None + if folder_ids is not None: + filter_folder_ids = set(folder_ids) + if not filter_folder_ids: + return + + # This will disable 'folder_ids' and 'product_names' filters + # - maybe could be enhanced in future? + if names_by_folder_ids is not None: + filter_product_names = set() + filter_folder_ids = set() + + for folder_id, names in names_by_folder_ids.items(): + if folder_id and names: + filter_folder_ids.add(folder_id) + filter_product_names |= set(names) + + if not filter_product_names or not filter_folder_ids: + return + + # Convert fields and add minimum required fields + if fields: + fields = set(fields) | {"id"} + self._prepare_fields("product", fields) + else: + fields = self.get_default_fields_for_type("product") + + if active is not None: + fields.add("active") + + if own_attributes is not _PLACEHOLDER: + warnings.warn( + ( + "'own_attributes' is not supported for products. The" + " argument will be removed from function signature in" + " future (apx. version 1.0.10 or 1.1.0)." + ), + DeprecationWarning + ) + + # Add 'name' and 'folderId' if 'names_by_folder_ids' filter is entered + if names_by_folder_ids: + fields.add("name") + fields.add("folderId") + + # Prepare filters for query + filters = { + "projectName": project_name + } + + if filter_folder_ids: + filters["folderIds"] = list(filter_folder_ids) + + if filter_product_names: + filters["productNames"] = list(filter_product_names) + + if not prepare_list_filters( + filters, + ("productIds", product_ids), + ("productTypes", product_types), + ("productStatuses", statuses), + ("productTags", tags), + ): + return + + for filter_key, filter_value in ( + ("productNameRegex", product_name_regex), + ("productPathRegex", product_path_regex), + ): + if filter_value: + filters[filter_key] = filter_value + + query = products_graphql_query(fields) + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + parsed_data = query.query(self) + + products = parsed_data.get("project", {}).get("products", []) + # Filter products by 'names_by_folder_ids' + if names_by_folder_ids: + products_by_folder_id = collections.defaultdict(list) + for product in products: + filtered_product = self._filter_product( + project_name, product, active + ) + if filtered_product is not None: + folder_id = filtered_product["folderId"] + products_by_folder_id[folder_id].append(filtered_product) + + for folder_id, names in names_by_folder_ids.items(): + for folder_product in products_by_folder_id[folder_id]: + if folder_product["name"] in names: + yield folder_product + + else: + for product in products: + filtered_product = self._filter_product( + project_name, product, active + ) + if filtered_product is not None: + yield filtered_product + + def get_product_by_id( + self, + project_name: str, + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER + ) -> Optional["ProductDict"]: + """Query product entity by id. + + Args: + project_name (str): Name of project where to look for queried + entities. + product_id (str): Product id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. + + Returns: + Optional[ProductDict]: Product entity data or None + if was not found. + + """ + products = self.get_products( + project_name, + product_ids=[product_id], + active=None, + fields=fields, + own_attributes=own_attributes + ) + for product in products: + return product + return None + + def get_product_by_name( + self, + project_name: str, + product_name: str, + folder_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER + ) -> Optional["ProductDict"]: + """Query product entity by name and folder id. + + Args: + project_name (str): Name of project where to look for queried + entities. + product_name (str): Product name. + folder_id (str): Folder id (Folder is a parent of products). + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. + + Returns: + Optional[ProductDict]: Product entity data or None + if was not found. + + """ + products = self.get_products( + project_name, + product_names=[product_name], + folder_ids=[folder_id], + active=None, + fields=fields, + own_attributes=own_attributes + ) + for product in products: + return product + return None + + def get_product_types( + self, fields: Optional[Iterable[str]] = None + ) -> list["ProductTypeDict"]: + """Types of products. + + This is server wide information. Product types have 'name', 'icon' and + 'color'. + + Args: + fields (Optional[Iterable[str]]): Product types fields to query. + + Returns: + list[ProductTypeDict]: Product types information. + + """ + if not fields: + fields = self.get_default_fields_for_type("productType") + + query = product_types_query(fields) + + parsed_data = query.query(self) + + return parsed_data.get("productTypes", []) + + def get_project_product_types( + self, project_name: str, fields: Optional[Iterable[str]] = None + ) -> list["ProductTypeDict"]: + """DEPRECATED Types of products available in a project. + + Filter only product types available in a project. + + Args: + project_name (str): Name of the project where to look for + product types. + fields (Optional[Iterable[str]]): Product types fields to query. + + Returns: + list[ProductTypeDict]: Product types information. + + """ + warnings.warn( + "Used deprecated function 'get_project_product_types'." + " Use 'get_project' with 'productTypes' in 'fields' instead.", + DeprecationWarning, + stacklevel=2, + ) + if fields is None: + fields = {"productTypes"} + else: + fields = { + f"productTypes.{key}" + for key in fields + } + + project = self.get_project(project_name, fields=fields) + return project["productTypes"] + + def get_product_type_names( + self, + project_name: Optional[str] = None, + product_ids: Optional[Iterable[str]] = None, + ) -> set[str]: + """DEPRECATED Product type names. + + Warnings: + This function will be probably removed. Matters if 'products_id' + filter has real use-case. + + Args: + project_name (Optional[str]): Name of project where to look for + queried entities. + product_ids (Optional[Iterable[str]]): Product ids filter. Can be + used only with 'project_name'. + + Returns: + set[str]: Product type names. + + """ + warnings.warn( + "Used deprecated function 'get_product_type_names'." + " Use 'get_product_types' or 'get_products' instead.", + DeprecationWarning, + stacklevel=2, + ) + if project_name: + if not product_ids: + return set() + products = self.get_products( + project_name, + product_ids=product_ids, + fields=["productType"], + active=None, + ) + return { + product["productType"] + for product in products + } + + return { + product_info["name"] + for product_info in self.get_product_types(project_name) + } + + def create_product( + self, + project_name: str, + name: str, + product_type: str, + folder_id: str, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] =None, + status: Optional[str] = None, + active: Optional[bool] = None, + product_id: Optional[str] = None, + ) -> str: + """Create new product. + + Args: + project_name (str): Project name. + name (str): Product name. + product_type (str): Product type. + folder_id (str): Parent folder id. + attrib (Optional[dict[str, Any]]): Product attributes. + data (Optional[dict[str, Any]]): Product data. + tags (Optional[Iterable[str]]): Product tags. + status (Optional[str]): Product status. + active (Optional[bool]): Product active state. + product_id (Optional[str]): Product id. If not passed new id is + generated. + + Returns: + str: Product id. + + """ + if not product_id: + product_id = create_entity_id() + create_data = { + "id": product_id, + "name": name, + "productType": product_type, + "folderId": folder_id, + } + for key, value in ( + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ): + if value is not None: + create_data[key] = value + + response = self.post( + f"projects/{project_name}/products", + **create_data + ) + response.raise_for_status() + return product_id + + def update_product( + self, + project_name: str, + product_id: str, + name: Optional[str] = None, + folder_id: Optional[str] = None, + product_type: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + ): + """Update product entity on server. + + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. + + Args: + project_name (str): Project name. + product_id (str): Product id. + name (Optional[str]): New product name. + folder_id (Optional[str]): New product id. + product_type (Optional[str]): New product type. + attrib (Optional[dict[str, Any]]): New product attributes. + data (Optional[dict[str, Any]]): New product data. + tags (Optional[Iterable[str]]): New product tags. + status (Optional[str]): New product status. + active (Optional[bool]): New product active state. + + """ + update_data = {} + for key, value in ( + ("name", name), + ("productType", product_type), + ("folderId", folder_id), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ): + if value is not None: + update_data[key] = value + + response = self.patch( + f"projects/{project_name}/products/{product_id}", + **update_data + ) + response.raise_for_status() + + def delete_product(self, project_name: str, product_id: str): + """Delete product. + + Args: + project_name (str): Project name. + product_id (str): Product id to delete. + + """ + response = self.delete( + f"projects/{project_name}/products/{product_id}" + ) + response.raise_for_status() + + def _filter_product( + self, + project_name: str, + product: "ProductDict", + active: Optional[bool], + ) -> Optional["ProductDict"]: + if active is not None and product["active"] is not active: + return None + + self._convert_entity_data(product) + + return product diff --git a/ayon_api/_tasks.py b/ayon_api/_tasks.py new file mode 100644 index 000000000..ed5f22fec --- /dev/null +++ b/ayon_api/_tasks.py @@ -0,0 +1,514 @@ +from __future__ import annotations + +import typing +from typing import Optional, Iterable, Generator, Any + +from ._base import _BaseServerAPI +from .utils import ( + prepare_list_filters, + fill_own_attribs, + create_entity_id, + NOT_SET, +) +from .graphql_queries import ( + tasks_graphql_query, + tasks_by_folder_paths_graphql_query, +) + +if typing.TYPE_CHECKING: + from .typing import TaskDict + + +class _TasksAPI(_BaseServerAPI): + def get_rest_task( + self, project_name: str, task_id: str + ) -> Optional["TaskDict"]: + return self.get_rest_entity_by_id(project_name, "task", task_id) + + def get_tasks( + self, + project_name: str, + task_ids: Optional[Iterable[str]] = None, + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + folder_ids: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False + ) -> Generator["TaskDict", None, None]: + """Query task entities from server. + + Args: + project_name (str): Name of project. + task_ids (Iterable[str]): Task ids to filter. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + folder_ids (Iterable[str]): Ids of task parents. Use 'None' + if folder is direct child of project. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Generator[TaskDict, None, None]: Queried task entities. + + """ + if not project_name: + return + + filters = { + "projectName": project_name + } + if not prepare_list_filters( + filters, + ("taskIds", task_ids), + ("taskNames", task_names), + ("taskTypes", task_types), + ("folderIds", folder_ids), + ("taskAssigneesAny", assignees), + ("taskAssigneesAll", assignees_all), + ("taskStatuses", statuses), + ("taskTags", tags), + ): + return + + if not fields: + fields = self.get_default_fields_for_type("task") + else: + fields = set(fields) + self._prepare_fields("task", fields, own_attributes) + + if active is not None: + fields.add("active") + + query = tasks_graphql_query(fields) + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + for parsed_data in query.continuous_query(self): + for task in parsed_data["project"]["tasks"]: + if active is not None and active is not task["active"]: + continue + + self._convert_entity_data(task) + + if own_attributes: + fill_own_attribs(task) + yield task + + def get_task_by_name( + self, + project_name: str, + folder_id: str, + task_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, + ) -> Optional["TaskDict"]: + """Query task entity by name and folder id. + + Args: + project_name (str): Name of project where to look for queried + entities. + folder_id (str): Folder id. + task_name (str): Task name + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[TaskDict]: Task entity data or None if was not found. + + """ + for task in self.get_tasks( + project_name, + folder_ids=[folder_id], + task_names=[task_name], + active=None, + fields=fields, + own_attributes=own_attributes + ): + return task + return None + + def get_task_by_id( + self, + project_name: str, + task_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False + ) -> Optional["TaskDict"]: + """Query task entity by id. + + Args: + project_name (str): Name of project where to look for queried + entities. + task_id (str): Task id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[TaskDict]: Task entity data or None if was not found. + + """ + for task in self.get_tasks( + project_name, + task_ids=[task_id], + active=None, + fields=fields, + own_attributes=own_attributes + ): + return task + return None + + def get_tasks_by_folder_paths( + self, + project_name: str, + folder_paths: Iterable[str], + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False + ) -> dict[str, list["TaskDict"]]: + """Query task entities from server by folder paths. + + Args: + project_name (str): Name of project. + folder_paths (list[str]): Folder paths. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + dict[str, list[TaskDict]]: Task entities by + folder path. + + """ + folder_paths = set(folder_paths) + if not project_name or not folder_paths: + return {} + + filters = { + "projectName": project_name, + "folderPaths": list(folder_paths), + } + if not prepare_list_filters( + filters, + ("taskNames", task_names), + ("taskTypes", task_types), + ("taskAssigneesAny", assignees), + ("taskAssigneesAll", assignees_all), + ("taskStatuses", statuses), + ("taskTags", tags), + ): + return {} + + if not fields: + fields = self.get_default_fields_for_type("task") + else: + fields = set(fields) + self._prepare_fields("task", fields, own_attributes) + + if active is not None: + fields.add("active") + + query = tasks_by_folder_paths_graphql_query(fields) + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + + output = { + folder_path: [] + for folder_path in folder_paths + } + for parsed_data in query.continuous_query(self): + for folder in parsed_data["project"]["folders"]: + folder_path = folder["path"] + for task in folder["tasks"]: + if active is not None and active is not task["active"]: + continue + + self._convert_entity_data(task) + + if own_attributes: + fill_own_attribs(task) + output[folder_path].append(task) + return output + + def get_tasks_by_folder_path( + self, + project_name: str, + folder_path: str, + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False + ) -> list["TaskDict"]: + """Query task entities from server by folder path. + + Args: + project_name (str): Name of project. + folder_path (str): Folder path. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + """ + return self.get_tasks_by_folder_paths( + project_name, + [folder_path], + task_names, + task_types=task_types, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, + tags=tags, + active=active, + fields=fields, + own_attributes=own_attributes + )[folder_path] + + def get_task_by_folder_path( + self, + project_name: str, + folder_path: str, + task_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False + ) -> Optional["TaskDict"]: + """Query task entity by folder path and task name. + + Args: + project_name (str): Project name. + folder_path (str): Folder path. + task_name (str): Task name. + fields (Optional[Iterable[str]]): Task fields that should + be returned. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[TaskDict]: Task entity data or None if was not found. + + """ + for task in self.get_tasks_by_folder_path( + project_name, + folder_path, + active=None, + task_names=[task_name], + fields=fields, + own_attributes=own_attributes, + ): + return task + return None + + def create_task( + self, + project_name: str, + name: str, + task_type: str, + folder_id: str, + label: Optional[str] = None, + assignees: Optional[Iterable[str]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + task_id: Optional[str] = None, + ) -> str: + """Create new task. + + Args: + project_name (str): Project name. + name (str): Folder name. + task_type (str): Task type. + folder_id (str): Parent folder id. + label (Optional[str]): Label of folder. + assignees (Optional[Iterable[str]]): Task assignees. + attrib (Optional[dict[str, Any]]): Task attributes. + data (Optional[dict[str, Any]]): Task data. + tags (Optional[Iterable[str]]): Task tags. + status (Optional[str]): Task status. + active (Optional[bool]): Task active state. + thumbnail_id (Optional[str]): Task thumbnail id. + task_id (Optional[str]): Task id. If not passed new id is + generated. + + Returns: + str: Task id. + + """ + if not task_id: + task_id = create_entity_id() + create_data = { + "id": task_id, + "name": name, + "taskType": task_type, + "folderId": folder_id, + } + for key, value in ( + ("label", label), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("assignees", assignees), + ("active", active), + ("thumbnailId", thumbnail_id), + ): + if value is not None: + create_data[key] = value + + response = self.post( + f"projects/{project_name}/tasks", + **create_data + ) + response.raise_for_status() + return task_id + + def update_task( + self, + project_name: str, + task_id: str, + name: Optional[str] = None, + task_type: Optional[str] = None, + folder_id: Optional[str] = None, + label: Optional[str] = NOT_SET, + assignees: Optional[list[str]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = NOT_SET, + ): + """Update task entity on server. + + Do not pass ``label`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. + + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. + + Args: + project_name (str): Project name. + task_id (str): Task id. + name (Optional[str]): New name. + task_type (Optional[str]): New task type. + folder_id (Optional[str]): New folder id. + label (Optional[Optional[str]]): New label. + assignees (Optional[str]): New assignees. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[str]): New thumbnail id. + + """ + update_data = {} + for key, value in ( + ("name", name), + ("taskType", task_type), + ("folderId", folder_id), + ("assignees", assignees), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ): + if value is not None: + update_data[key] = value + + for key, value in ( + ("label", label), + ("thumbnailId", thumbnail_id), + ): + if value is not NOT_SET: + update_data[key] = value + + response = self.patch( + f"projects/{project_name}/tasks/{task_id}", + **update_data + ) + response.raise_for_status() + + def delete_task(self, project_name: str, task_id: str): + """Delete task. + + Args: + project_name (str): Project name. + task_id (str): Task id to delete. + + """ + response = self.delete( + f"projects/{project_name}/tasks/{task_id}" + ) + response.raise_for_status() \ No newline at end of file diff --git a/ayon_api/_versions.py b/ayon_api/_versions.py new file mode 100644 index 000000000..e805505a7 --- /dev/null +++ b/ayon_api/_versions.py @@ -0,0 +1,639 @@ +from __future__ import annotations + +import warnings +import typing +from typing import Optional, Iterable, Generator, Any + +from ._base import _BaseServerAPI, _PLACEHOLDER +from .utils import ( + NOT_SET, + create_entity_id, + prepare_list_filters, +) +from .graphql import GraphQlQuery +from .graphql_queries import versions_graphql_query + +if typing.TYPE_CHECKING: + from .typing import VersionDict + + +class _VersionsAPI(_BaseServerAPI): + def get_rest_version( + self, project_name: str, version_id: str + ) -> Optional["VersionDict"]: + return self.get_rest_entity_by_id(project_name, "version", version_id) + + def get_versions( + self, + project_name: str, + version_ids: Optional[Iterable[str]] = None, + product_ids: Optional[Iterable[str]] = None, + task_ids: Optional[Iterable[str]] = None, + versions: Optional[Iterable[str]] = None, + hero: bool = True, + standard: bool = True, + latest: Optional[bool] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER + ) -> Generator["VersionDict", None, None]: + """Get version entities based on passed filters from server. + + Args: + project_name (str): Name of project where to look for versions. + version_ids (Optional[Iterable[str]]): Version ids used for + version filtering. + product_ids (Optional[Iterable[str]]): Product ids used for + version filtering. + task_ids (Optional[Iterable[str]]): Task ids used for + version filtering. + versions (Optional[Iterable[int]]): Versions we're interested in. + hero (Optional[bool]): Skip hero versions when set to False. + standard (Optional[bool]): Skip standard (non-hero) when + set to False. + latest (Optional[bool]): Return only latest version of standard + versions. This can be combined only with 'standard' attribute + set to True. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): Fields to be queried + for version. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Generator[VersionDict, None, None]: Queried version entities. + + """ + if not fields: + fields = self.get_default_fields_for_type("version") + else: + fields = set(fields) + self._prepare_fields("version", fields) + + # Make sure fields have minimum required fields + fields |= {"id", "version"} + + if active is not None: + fields.add("active") + + if own_attributes is not _PLACEHOLDER: + warnings.warn( + ( + "'own_attributes' is not supported for versions. The" + " argument will be removed form function signature in" + " future (apx. version 1.0.10 or 1.1.0)." + ), + DeprecationWarning + ) + + if not hero and not standard: + return + + filters = { + "projectName": project_name + } + if not prepare_list_filters( + filters, + ("taskIds", task_ids), + ("versionIds", version_ids), + ("productIds", product_ids), + ("taskIds", task_ids), + ("versions", versions), + ("versionStatuses", statuses), + ("versionTags", tags), + ): + return + + queries = [] + # Add filters based on 'hero' and 'standard' + # NOTE: There is not a filter to "ignore" hero versions or to get + # latest and hero version + # - if latest and hero versions should be returned it must be done in + # 2 graphql queries + if standard and not latest: + # This query all versions standard + hero + # - hero must be filtered out if is not enabled during loop + query = versions_graphql_query(fields) + for attr, filter_value in filters.items(): + query.set_variable_value(attr, filter_value) + queries.append(query) + else: + if hero: + # Add hero query if hero is enabled + hero_query = versions_graphql_query(fields) + for attr, filter_value in filters.items(): + hero_query.set_variable_value(attr, filter_value) + + hero_query.set_variable_value("heroOnly", True) + queries.append(hero_query) + + if standard: + standard_query = versions_graphql_query(fields) + for attr, filter_value in filters.items(): + standard_query.set_variable_value(attr, filter_value) + + if latest: + standard_query.set_variable_value("latestOnly", True) + queries.append(standard_query) + + for query in queries: + for parsed_data in query.continuous_query(self): + for version in parsed_data["project"]["versions"]: + if active is not None and version["active"] is not active: + continue + + if not hero and version["version"] < 0: + continue + + self._convert_entity_data(version) + + yield version + + def get_version_by_id( + self, + project_name: str, + version_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER + ) -> Optional["VersionDict"]: + """Query version entity by id. + + Args: + project_name (str): Name of project where to look for queried + entities. + version_id (str): Version id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. + + """ + versions = self.get_versions( + project_name, + version_ids={version_id}, + active=None, + hero=True, + fields=fields, + own_attributes=own_attributes + ) + for version in versions: + return version + return None + + def get_version_by_name( + self, + project_name: str, + version: int, + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER + ) -> Optional["VersionDict"]: + """Query version entity by version and product id. + + Args: + project_name (str): Name of project where to look for queried + entities. + version (int): Version of version entity. + product_id (str): Product id. Product is a parent of version. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. + + """ + versions = self.get_versions( + project_name, + product_ids={product_id}, + versions={version}, + active=None, + fields=fields, + own_attributes=own_attributes + ) + for version in versions: + return version + return None + + def get_hero_version_by_id( + self, + project_name: str, + version_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER + ) -> Optional["VersionDict"]: + """Query hero version entity by id. + + Args: + project_name (str): Name of project where to look for queried + entities. + version_id (int): Hero version id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. + + """ + versions = self.get_hero_versions( + project_name, + version_ids=[version_id], + fields=fields, + own_attributes=own_attributes + ) + for version in versions: + return version + return None + + def get_hero_version_by_product_id( + self, + project_name: str, + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER + ) -> Optional["VersionDict"]: + """Query hero version entity by product id. + + Only one hero version is available on a product. + + Args: + project_name (str): Name of project where to look for queried + entities. + product_id (int): Product id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. + + """ + versions = self.get_hero_versions( + project_name, + product_ids=[product_id], + fields=fields, + own_attributes=own_attributes + ) + for version in versions: + return version + return None + + def get_hero_versions( + self, + project_name: str, + product_ids: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Generator["VersionDict", None, None]: + """Query hero versions by multiple filters. + + Only one hero version is available on a product. + + Args: + project_name (str): Name of project where to look for queried + entities. + product_ids (Optional[Iterable[str]]): Product ids. + version_ids (Optional[Iterable[str]]): Version ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. + + """ + return self.get_versions( + project_name, + version_ids=version_ids, + product_ids=product_ids, + hero=True, + standard=False, + active=active, + fields=fields, + own_attributes=own_attributes + ) + + def get_last_versions( + self, + project_name: str, + product_ids: Iterable[str], + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> dict[str, Optional["VersionDict"]]: + """Query last version entities by product ids. + + Args: + project_name (str): Project where to look for representation. + product_ids (Iterable[str]): Product ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + dict[str, Optional[VersionDict]]: Last versions by product id. + + """ + if fields: + fields = set(fields) + fields.add("productId") + product_ids = set(product_ids) + versions = self.get_versions( + project_name, + product_ids=product_ids, + latest=True, + hero=False, + active=active, + fields=fields, + own_attributes=own_attributes + ) + output = { + version["productId"]: version + for version in versions + } + for product_id in product_ids: + output.setdefault(product_id, None) + return output + + def get_last_version_by_product_id( + self, + project_name: str, + product_id: str, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Optional["VersionDict"]: + """Query last version entity by product id. + + Args: + project_name (str): Project where to look for representation. + product_id (str): Product id. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Queried version entity or None. + + """ + versions = self.get_versions( + project_name, + product_ids=[product_id], + latest=True, + hero=False, + active=active, + fields=fields, + own_attributes=own_attributes + ) + for version in versions: + return version + return None + + def get_last_version_by_product_name( + self, + project_name: str, + product_name: str, + folder_id: str, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, + ) -> Optional["VersionDict"]: + """Query last version entity by product name and folder id. + + Args: + project_name (str): Project where to look for representation. + product_name (str): Product name. + folder_id (str): Folder id. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. + + Returns: + Optional[VersionDict]: Queried version entity or None. + + """ + if not folder_id: + return None + + product = self.get_product_by_name( + project_name, product_name, folder_id, fields={"id"} + ) + if not product: + return None + return self.get_last_version_by_product_id( + project_name, + product["id"], + active=active, + fields=fields, + own_attributes=own_attributes + ) + + def version_is_latest(self, project_name: str, version_id: str) -> bool: + """Is version latest from a product. + + Args: + project_name (str): Project where to look for representation. + version_id (str): Version id. + + Returns: + bool: Version is latest or not. + + """ + query = GraphQlQuery("VersionIsLatest") + project_name_var = query.add_variable( + "projectName", "String!", project_name + ) + version_id_var = query.add_variable( + "versionId", "String!", version_id + ) + project_query = query.add_field("project") + project_query.set_filter("name", project_name_var) + version_query = project_query.add_field("version") + version_query.set_filter("id", version_id_var) + product_query = version_query.add_field("product") + latest_version_query = product_query.add_field("latestVersion") + latest_version_query.add_field("id") + + parsed_data = query.query(self) + latest_version = ( + parsed_data["project"]["version"]["product"]["latestVersion"] + ) + return latest_version["id"] == version_id + + def create_version( + self, + project_name: str, + version: int, + product_id: str, + task_id: Optional[str] = None, + author: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + version_id: Optional[str] = None, + ) -> str: + """Create new version. + + Args: + project_name (str): Project name. + version (int): Version. + product_id (str): Parent product id. + task_id (Optional[str]): Parent task id. + author (Optional[str]): Version author. + attrib (Optional[dict[str, Any]]): Version attributes. + data (Optional[dict[str, Any]]): Version data. + tags (Optional[Iterable[str]]): Version tags. + status (Optional[str]): Version status. + active (Optional[bool]): Version active state. + thumbnail_id (Optional[str]): Version thumbnail id. + version_id (Optional[str]): Version id. If not passed new id is + generated. + + Returns: + str: Version id. + + """ + if not version_id: + version_id = create_entity_id() + create_data = { + "id": version_id, + "version": version, + "productId": product_id, + } + for key, value in ( + ("taskId", task_id), + ("author", author), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ("thumbnailId", thumbnail_id), + ): + if value is not None: + create_data[key] = value + + response = self.post( + f"projects/{project_name}/versions", + **create_data + ) + response.raise_for_status() + return version_id + + def update_version( + self, + project_name: str, + version_id: str, + version: Optional[int] = None, + product_id: Optional[str] = None, + task_id: Optional[str] = NOT_SET, + author: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = NOT_SET, + ): + """Update version entity on server. + + Do not pass ``task_id`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. + + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. + + Args: + project_name (str): Project name. + version_id (str): Version id. + version (Optional[int]): New version. + product_id (Optional[str]): New product id. + task_id (Optional[str]): New task id. + author (Optional[str]): New author username. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[str]): New thumbnail id. + + """ + update_data = {} + for key, value in ( + ("version", version), + ("productId", product_id), + ("attrib", attrib), + ("data", data), + ("tags", tags), + ("status", status), + ("active", active), + ("author", author), + ): + if value is not None: + update_data[key] = value + + for key, value in ( + ("taskId", task_id), + ("thumbnailId", thumbnail_id), + ): + if value is not NOT_SET: + update_data[key] = value + + response = self.patch( + f"projects/{project_name}/versions/{version_id}", + **update_data + ) + response.raise_for_status() + + def delete_version(self, project_name: str, version_id: str): + """Delete version. + + Args: + project_name (str): Project name. + version_id (str): Version id to delete. + + """ + response = self.delete( + f"projects/{project_name}/versions/{version_id}" + ) + response.raise_for_status() diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 4a2767255..80f7ccb80 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -11,7 +11,6 @@ import json import time import logging -import collections import platform import copy import uuid @@ -42,18 +41,8 @@ DEFAULT_USER_FIELDS, DEFAULT_ENTITY_LIST_FIELDS, ) -from .graphql import GraphQlQuery, INTROSPECTION_QUERY -from .graphql_queries import ( - product_types_query, - tasks_graphql_query, - tasks_by_folder_paths_graphql_query, - products_graphql_query, - versions_graphql_query, - representations_graphql_query, - representations_hierarchy_qraphql_query, - workfiles_info_graphql_query, - users_graphql_query, -) +from .graphql import INTROSPECTION_QUERY +from .graphql_queries import users_graphql_query from .exceptions import ( FailedOperations, UnauthorizedError, @@ -64,8 +53,6 @@ RequestType, RequestTypes, RestApiResponse, - RepresentationParents, - RepresentationHierarchy, prepare_query_string, logout_from_server, create_entity_id, @@ -79,21 +66,24 @@ get_media_mime_type, get_machine_name, fill_own_attribs, - prepare_list_filters, ) from ._base import _PLACEHOLDER from ._actions import _ActionsAPI from ._activities import _ActivitiesAPI from ._addons import _AddonsAPI from ._events import _EventsAPI -from ._folders import _FoldersAPI from ._links import _LinksAPI from ._lists import _ListsAPI from ._projects import _ProjectsAPI +from ._folders import _FoldersAPI +from ._tasks import _TasksAPI +from ._products import _ProductsAPI +from ._versions import _VersionsAPI from ._thumbnails import _ThumbnailsAPI from ._workfiles import _WorkfilesAPI from ._representations import _RepresentationsAPI + if typing.TYPE_CHECKING: from typing import Union from .typing import ( @@ -239,6 +229,9 @@ class ServerAPI( _EventsAPI, _ProjectsAPI, _FoldersAPI, + _TasksAPI, + _ProductsAPI, + _VersionsAPI, _RepresentationsAPI, _WorkfilesAPI, _LinksAPI, @@ -3425,1599 +3418,6 @@ def get_rest_entity_by_id( return response.data return None - def get_rest_task( - self, project_name: str, task_id: str - ) -> Optional["TaskDict"]: - return self.get_rest_entity_by_id(project_name, "task", task_id) - - def get_rest_product( - self, project_name: str, product_id: str - ) -> Optional["ProductDict"]: - return self.get_rest_entity_by_id(project_name, "product", product_id) - - def get_rest_version( - self, project_name: str, version_id: str - ) -> Optional["VersionDict"]: - return self.get_rest_entity_by_id(project_name, "version", version_id) - - def get_tasks( - self, - project_name: str, - task_ids: Optional[Iterable[str]] = None, - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - folder_ids: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False - ) -> Generator["TaskDict", None, None]: - """Query task entities from server. - - Args: - project_name (str): Name of project. - task_ids (Iterable[str]): Task ids to filter. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - folder_ids (Iterable[str]): Ids of task parents. Use 'None' - if folder is direct child of project. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Generator[TaskDict, None, None]: Queried task entities. - - """ - if not project_name: - return - - filters = { - "projectName": project_name - } - if not prepare_list_filters( - filters, - ("taskIds", task_ids), - ("taskNames", task_names), - ("taskTypes", task_types), - ("folderIds", folder_ids), - ("taskAssigneesAny", assignees), - ("taskAssigneesAll", assignees_all), - ("taskStatuses", statuses), - ("taskTags", tags), - ): - return - - if not fields: - fields = self.get_default_fields_for_type("task") - else: - fields = set(fields) - self._prepare_fields("task", fields, own_attributes) - - if active is not None: - fields.add("active") - - query = tasks_graphql_query(fields) - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - for parsed_data in query.continuous_query(self): - for task in parsed_data["project"]["tasks"]: - if active is not None and active is not task["active"]: - continue - - self._convert_entity_data(task) - - if own_attributes: - fill_own_attribs(task) - yield task - - def get_task_by_name( - self, - project_name: str, - folder_id: str, - task_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, - ) -> Optional["TaskDict"]: - """Query task entity by name and folder id. - - Args: - project_name (str): Name of project where to look for queried - entities. - folder_id (str): Folder id. - task_name (str): Task name - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Optional[TaskDict]: Task entity data or None if was not found. - - """ - for task in self.get_tasks( - project_name, - folder_ids=[folder_id], - task_names=[task_name], - active=None, - fields=fields, - own_attributes=own_attributes - ): - return task - return None - - def get_task_by_id( - self, - project_name: str, - task_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False - ) -> Optional["TaskDict"]: - """Query task entity by id. - - Args: - project_name (str): Name of project where to look for queried - entities. - task_id (str): Task id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Optional[TaskDict]: Task entity data or None if was not found. - - """ - for task in self.get_tasks( - project_name, - task_ids=[task_id], - active=None, - fields=fields, - own_attributes=own_attributes - ): - return task - return None - - def get_tasks_by_folder_paths( - self, - project_name: str, - folder_paths: Iterable[str], - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False - ) -> Dict[str, List["TaskDict"]]: - """Query task entities from server by folder paths. - - Args: - project_name (str): Name of project. - folder_paths (list[str]): Folder paths. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Dict[str, List[TaskDict]]: Task entities by - folder path. - - """ - folder_paths = set(folder_paths) - if not project_name or not folder_paths: - return {} - - filters = { - "projectName": project_name, - "folderPaths": list(folder_paths), - } - if not prepare_list_filters( - filters, - ("taskNames", task_names), - ("taskTypes", task_types), - ("taskAssigneesAny", assignees), - ("taskAssigneesAll", assignees_all), - ("taskStatuses", statuses), - ("taskTags", tags), - ): - return {} - - if not fields: - fields = self.get_default_fields_for_type("task") - else: - fields = set(fields) - self._prepare_fields("task", fields, own_attributes) - - if active is not None: - fields.add("active") - - query = tasks_by_folder_paths_graphql_query(fields) - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - output = { - folder_path: [] - for folder_path in folder_paths - } - for parsed_data in query.continuous_query(self): - for folder in parsed_data["project"]["folders"]: - folder_path = folder["path"] - for task in folder["tasks"]: - if active is not None and active is not task["active"]: - continue - - self._convert_entity_data(task) - - if own_attributes: - fill_own_attribs(task) - output[folder_path].append(task) - return output - - def get_tasks_by_folder_path( - self, - project_name: str, - folder_path: str, - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False - ) -> List["TaskDict"]: - """Query task entities from server by folder path. - - Args: - project_name (str): Name of project. - folder_path (str): Folder path. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - """ - return self.get_tasks_by_folder_paths( - project_name, - [folder_path], - task_names, - task_types=task_types, - assignees=assignees, - assignees_all=assignees_all, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes - )[folder_path] - - def get_task_by_folder_path( - self, - project_name: str, - folder_path: str, - task_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False - ) -> Optional["TaskDict"]: - """Query task entity by folder path and task name. - - Args: - project_name (str): Project name. - folder_path (str): Folder path. - task_name (str): Task name. - fields (Optional[Iterable[str]]): Task fields that should - be returned. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. - - Returns: - Optional[TaskDict]: Task entity data or None if was not found. - - """ - for task in self.get_tasks_by_folder_path( - project_name, - folder_path, - active=None, - task_names=[task_name], - fields=fields, - own_attributes=own_attributes, - ): - return task - return None - - def create_task( - self, - project_name: str, - name: str, - task_type: str, - folder_id: str, - label: Optional[str] = None, - assignees: Optional[Iterable[str]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - task_id: Optional[str] = None, - ) -> str: - """Create new task. - - Args: - project_name (str): Project name. - name (str): Folder name. - task_type (str): Task type. - folder_id (str): Parent folder id. - label (Optional[str]): Label of folder. - assignees (Optional[Iterable[str]]): Task assignees. - attrib (Optional[dict[str, Any]]): Task attributes. - data (Optional[dict[str, Any]]): Task data. - tags (Optional[Iterable[str]]): Task tags. - status (Optional[str]): Task status. - active (Optional[bool]): Task active state. - thumbnail_id (Optional[str]): Task thumbnail id. - task_id (Optional[str]): Task id. If not passed new id is - generated. - - Returns: - str: Task id. - - """ - if not task_id: - task_id = create_entity_id() - create_data = { - "id": task_id, - "name": name, - "taskType": task_type, - "folderId": folder_id, - } - for key, value in ( - ("label", label), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("assignees", assignees), - ("active", active), - ("thumbnailId", thumbnail_id), - ): - if value is not None: - create_data[key] = value - - response = self.post( - f"projects/{project_name}/tasks", - **create_data - ) - response.raise_for_status() - return task_id - - def update_task( - self, - project_name: str, - task_id: str, - name: Optional[str] = None, - task_type: Optional[str] = None, - folder_id: Optional[str] = None, - label: Optional[str] = NOT_SET, - assignees: Optional[List[str]] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, - ): - """Update task entity on server. - - Do not pass ``label`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. - - Args: - project_name (str): Project name. - task_id (str): Task id. - name (Optional[str]): New name. - task_type (Optional[str]): New task type. - folder_id (Optional[str]): New folder id. - label (Optional[Union[str, None]]): New label. - assignees (Optional[str]): New assignees. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - thumbnail_id (Optional[Union[str, None]]): New thumbnail id. - - """ - update_data = {} - for key, value in ( - ("name", name), - ("taskType", task_type), - ("folderId", folder_id), - ("assignees", assignees), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ): - if value is not None: - update_data[key] = value - - for key, value in ( - ("label", label), - ("thumbnailId", thumbnail_id), - ): - if value is not NOT_SET: - update_data[key] = value - - response = self.patch( - f"projects/{project_name}/tasks/{task_id}", - **update_data - ) - response.raise_for_status() - - def delete_task(self, project_name: str, task_id: str): - """Delete task. - - Args: - project_name (str): Project name. - task_id (str): Task id to delete. - - """ - response = self.delete( - f"projects/{project_name}/tasks/{task_id}" - ) - response.raise_for_status() - - def _filter_product( - self, - project_name: str, - product: "ProductDict", - active: "Union[bool, None]", - ) -> Optional["ProductDict"]: - if active is not None and product["active"] is not active: - return None - - self._convert_entity_data(product) - - return product - - def get_products( - self, - project_name: str, - product_ids: Optional[Iterable[str]] = None, - product_names: Optional[Iterable[str]]=None, - folder_ids: Optional[Iterable[str]]=None, - product_types: Optional[Iterable[str]]=None, - product_name_regex: Optional[str] = None, - product_path_regex: Optional[str] = None, - names_by_folder_ids: Optional[Dict[str, Iterable[str]]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER - ) -> Generator["ProductDict", None, None]: - """Query products from server. - - Todos: - Separate 'name_by_folder_ids' filtering to separated method. It - cannot be combined with some other filters. - - Args: - project_name (str): Name of project. - product_ids (Optional[Iterable[str]]): Task ids to filter. - product_names (Optional[Iterable[str]]): Task names used for - filtering. - folder_ids (Optional[Iterable[str]]): Ids of task parents. - Use 'None' if folder is direct child of project. - product_types (Optional[Iterable[str]]): Product types used for - filtering. - product_name_regex (Optional[str]): Filter products by name regex. - product_path_regex (Optional[str]): Filter products by path regex. - Path starts with folder path and ends with product name. - names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product - name filtering by folder id. - statuses (Optional[Iterable[str]]): Product statuses used - for filtering. - tags (Optional[Iterable[str]]): Product tags used - for filtering. - active (Optional[bool]): Filter active/inactive products. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. - - Returns: - Generator[ProductDict, None, None]: Queried product entities. - - """ - if not project_name: - return - - # Prepare these filters before 'name_by_filter_ids' filter - filter_product_names = None - if product_names is not None: - filter_product_names = set(product_names) - if not filter_product_names: - return - - filter_folder_ids = None - if folder_ids is not None: - filter_folder_ids = set(folder_ids) - if not filter_folder_ids: - return - - # This will disable 'folder_ids' and 'product_names' filters - # - maybe could be enhanced in future? - if names_by_folder_ids is not None: - filter_product_names = set() - filter_folder_ids = set() - - for folder_id, names in names_by_folder_ids.items(): - if folder_id and names: - filter_folder_ids.add(folder_id) - filter_product_names |= set(names) - - if not filter_product_names or not filter_folder_ids: - return - - # Convert fields and add minimum required fields - if fields: - fields = set(fields) | {"id"} - self._prepare_fields("product", fields) - else: - fields = self.get_default_fields_for_type("product") - - if active is not None: - fields.add("active") - - if own_attributes is not _PLACEHOLDER: - warnings.warn( - ( - "'own_attributes' is not supported for products. The" - " argument will be removed from function signature in" - " future (apx. version 1.0.10 or 1.1.0)." - ), - DeprecationWarning - ) - - # Add 'name' and 'folderId' if 'names_by_folder_ids' filter is entered - if names_by_folder_ids: - fields.add("name") - fields.add("folderId") - - # Prepare filters for query - filters = { - "projectName": project_name - } - - if filter_folder_ids: - filters["folderIds"] = list(filter_folder_ids) - - if filter_product_names: - filters["productNames"] = list(filter_product_names) - - if not prepare_list_filters( - filters, - ("productIds", product_ids), - ("productTypes", product_types), - ("productStatuses", statuses), - ("productTags", tags), - ): - return - - for filter_key, filter_value in ( - ("productNameRegex", product_name_regex), - ("productPathRegex", product_path_regex), - ): - if filter_value: - filters[filter_key] = filter_value - - query = products_graphql_query(fields) - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - - parsed_data = query.query(self) - - products = parsed_data.get("project", {}).get("products", []) - # Filter products by 'names_by_folder_ids' - if names_by_folder_ids: - products_by_folder_id = collections.defaultdict(list) - for product in products: - filtered_product = self._filter_product( - project_name, product, active - ) - if filtered_product is not None: - folder_id = filtered_product["folderId"] - products_by_folder_id[folder_id].append(filtered_product) - - for folder_id, names in names_by_folder_ids.items(): - for folder_product in products_by_folder_id[folder_id]: - if folder_product["name"] in names: - yield folder_product - - else: - for product in products: - filtered_product = self._filter_product( - project_name, product, active - ) - if filtered_product is not None: - yield filtered_product - - def get_product_by_id( - self, - project_name: str, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER - ) -> Optional["ProductDict"]: - """Query product entity by id. - - Args: - project_name (str): Name of project where to look for queried - entities. - product_id (str): Product id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. - - Returns: - Optional[ProductDict]: Product entity data or None - if was not found. - - """ - products = self.get_products( - project_name, - product_ids=[product_id], - active=None, - fields=fields, - own_attributes=own_attributes - ) - for product in products: - return product - return None - - def get_product_by_name( - self, - project_name: str, - product_name: str, - folder_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER - ) -> Optional["ProductDict"]: - """Query product entity by name and folder id. - - Args: - project_name (str): Name of project where to look for queried - entities. - product_name (str): Product name. - folder_id (str): Folder id (Folder is a parent of products). - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. - - Returns: - Optional[ProductDict]: Product entity data or None - if was not found. - - """ - products = self.get_products( - project_name, - product_names=[product_name], - folder_ids=[folder_id], - active=None, - fields=fields, - own_attributes=own_attributes - ) - for product in products: - return product - return None - - def get_product_types( - self, fields: Optional[Iterable[str]] = None - ) -> List["ProductTypeDict"]: - """Types of products. - - This is server wide information. Product types have 'name', 'icon' and - 'color'. - - Args: - fields (Optional[Iterable[str]]): Product types fields to query. - - Returns: - list[ProductTypeDict]: Product types information. - - """ - if not fields: - fields = self.get_default_fields_for_type("productType") - - query = product_types_query(fields) - - parsed_data = query.query(self) - - return parsed_data.get("productTypes", []) - - def get_project_product_types( - self, project_name: str, fields: Optional[Iterable[str]] = None - ) -> List["ProductTypeDict"]: - """DEPRECATED Types of products available in a project. - - Filter only product types available in a project. - - Args: - project_name (str): Name of the project where to look for - product types. - fields (Optional[Iterable[str]]): Product types fields to query. - - Returns: - List[ProductTypeDict]: Product types information. - - """ - warnings.warn( - "Used deprecated function 'get_project_product_types'." - " Use 'get_project' with 'productTypes' in 'fields' instead.", - DeprecationWarning, - stacklevel=2, - ) - if fields is None: - fields = {"productTypes"} - else: - fields = { - f"productTypes.{key}" - for key in fields - } - - project = self.get_project(project_name, fields=fields) - return project["productTypes"] - - def get_product_type_names( - self, - project_name: Optional[str] = None, - product_ids: Optional[Iterable[str]] = None, - ) -> Set[str]: - """DEPRECATED Product type names. - - Warnings: - This function will be probably removed. Matters if 'products_id' - filter has real use-case. - - Args: - project_name (Optional[str]): Name of project where to look for - queried entities. - product_ids (Optional[Iterable[str]]): Product ids filter. Can be - used only with 'project_name'. - - Returns: - set[str]: Product type names. - - """ - warnings.warn( - "Used deprecated function 'get_product_type_names'." - " Use 'get_product_types' or 'get_products' instead.", - DeprecationWarning, - stacklevel=2, - ) - if project_name: - if not product_ids: - return set() - products = self.get_products( - project_name, - product_ids=product_ids, - fields=["productType"], - active=None, - ) - return { - product["productType"] - for product in products - } - - return { - product_info["name"] - for product_info in self.get_product_types(project_name) - } - - def create_product( - self, - project_name: str, - name: str, - product_type: str, - folder_id: str, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] =None, - status: Optional[str] = None, - active: "Union[bool, None]" = None, - product_id: Optional[str] = None, - ) -> str: - """Create new product. - - Args: - project_name (str): Project name. - name (str): Product name. - product_type (str): Product type. - folder_id (str): Parent folder id. - attrib (Optional[dict[str, Any]]): Product attributes. - data (Optional[dict[str, Any]]): Product data. - tags (Optional[Iterable[str]]): Product tags. - status (Optional[str]): Product status. - active (Optional[bool]): Product active state. - product_id (Optional[str]): Product id. If not passed new id is - generated. - - Returns: - str: Product id. - - """ - if not product_id: - product_id = create_entity_id() - create_data = { - "id": product_id, - "name": name, - "productType": product_type, - "folderId": folder_id, - } - for key, value in ( - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ): - if value is not None: - create_data[key] = value - - response = self.post( - f"projects/{project_name}/products", - **create_data - ) - response.raise_for_status() - return product_id - - def update_product( - self, - project_name: str, - product_id: str, - name: Optional[str] = None, - folder_id: Optional[str] = None, - product_type: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - ): - """Update product entity on server. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. - - Args: - project_name (str): Project name. - product_id (str): Product id. - name (Optional[str]): New product name. - folder_id (Optional[str]): New product id. - product_type (Optional[str]): New product type. - attrib (Optional[dict[str, Any]]): New product attributes. - data (Optional[dict[str, Any]]): New product data. - tags (Optional[Iterable[str]]): New product tags. - status (Optional[str]): New product status. - active (Optional[bool]): New product active state. - - """ - update_data = {} - for key, value in ( - ("name", name), - ("productType", product_type), - ("folderId", folder_id), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ): - if value is not None: - update_data[key] = value - - response = self.patch( - f"projects/{project_name}/products/{product_id}", - **update_data - ) - response.raise_for_status() - - def delete_product(self, project_name: str, product_id: str): - """Delete product. - - Args: - project_name (str): Project name. - product_id (str): Product id to delete. - - """ - response = self.delete( - f"projects/{project_name}/products/{product_id}" - ) - response.raise_for_status() - - def get_versions( - self, - project_name: str, - version_ids: Optional[Iterable[str]] = None, - product_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] = None, - versions: Optional[Iterable[str]] = None, - hero: bool = True, - standard: bool = True, - latest: Optional[bool] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER - ) -> Generator["VersionDict", None, None]: - """Get version entities based on passed filters from server. - - Args: - project_name (str): Name of project where to look for versions. - version_ids (Optional[Iterable[str]]): Version ids used for - version filtering. - product_ids (Optional[Iterable[str]]): Product ids used for - version filtering. - task_ids (Optional[Iterable[str]]): Task ids used for - version filtering. - versions (Optional[Iterable[int]]): Versions we're interested in. - hero (Optional[bool]): Skip hero versions when set to False. - standard (Optional[bool]): Skip standard (non-hero) when - set to False. - latest (Optional[bool]): Return only latest version of standard - versions. This can be combined only with 'standard' attribute - set to True. - statuses (Optional[Iterable[str]]): Representation statuses used - for filtering. - tags (Optional[Iterable[str]]): Representation tags used - for filtering. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): Fields to be queried - for version. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Generator[VersionDict, None, None]: Queried version entities. - - """ - if not fields: - fields = self.get_default_fields_for_type("version") - else: - fields = set(fields) - self._prepare_fields("version", fields) - - # Make sure fields have minimum required fields - fields |= {"id", "version"} - - if active is not None: - fields.add("active") - - if own_attributes is not _PLACEHOLDER: - warnings.warn( - ( - "'own_attributes' is not supported for versions. The" - " argument will be removed form function signature in" - " future (apx. version 1.0.10 or 1.1.0)." - ), - DeprecationWarning - ) - - if not hero and not standard: - return - - filters = { - "projectName": project_name - } - if not prepare_list_filters( - filters, - ("taskIds", task_ids), - ("versionIds", version_ids), - ("productIds", product_ids), - ("taskIds", task_ids), - ("versions", versions), - ("versionStatuses", statuses), - ("versionTags", tags), - ): - return - - queries = [] - # Add filters based on 'hero' and 'standard' - # NOTE: There is not a filter to "ignore" hero versions or to get - # latest and hero version - # - if latest and hero versions should be returned it must be done in - # 2 graphql queries - if standard and not latest: - # This query all versions standard + hero - # - hero must be filtered out if is not enabled during loop - query = versions_graphql_query(fields) - for attr, filter_value in filters.items(): - query.set_variable_value(attr, filter_value) - queries.append(query) - else: - if hero: - # Add hero query if hero is enabled - hero_query = versions_graphql_query(fields) - for attr, filter_value in filters.items(): - hero_query.set_variable_value(attr, filter_value) - - hero_query.set_variable_value("heroOnly", True) - queries.append(hero_query) - - if standard: - standard_query = versions_graphql_query(fields) - for attr, filter_value in filters.items(): - standard_query.set_variable_value(attr, filter_value) - - if latest: - standard_query.set_variable_value("latestOnly", True) - queries.append(standard_query) - - for query in queries: - for parsed_data in query.continuous_query(self): - for version in parsed_data["project"]["versions"]: - if active is not None and version["active"] is not active: - continue - - if not hero and version["version"] < 0: - continue - - self._convert_entity_data(version) - - yield version - - def get_version_by_id( - self, - project_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER - ) -> Optional["VersionDict"]: - """Query version entity by id. - - Args: - project_name (str): Name of project where to look for queried - entities. - version_id (str): Version id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. - - """ - versions = self.get_versions( - project_name, - version_ids=[version_id], - active=None, - hero=True, - fields=fields, - own_attributes=own_attributes - ) - for version in versions: - return version - return None - - def get_version_by_name( - self, - project_name: str, - version: int, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER - ) -> Optional["VersionDict"]: - """Query version entity by version and product id. - - Args: - project_name (str): Name of project where to look for queried - entities. - version (int): Version of version entity. - product_id (str): Product id. Product is a parent of version. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. - - """ - versions = self.get_versions( - project_name, - product_ids=[product_id], - versions=[version], - active=None, - fields=fields, - own_attributes=own_attributes - ) - for version in versions: - return version - return None - - def get_hero_version_by_id( - self, - project_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER - ) -> Optional["VersionDict"]: - """Query hero version entity by id. - - Args: - project_name (str): Name of project where to look for queried - entities. - version_id (int): Hero version id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. - - """ - versions = self.get_hero_versions( - project_name, - version_ids=[version_id], - fields=fields, - own_attributes=own_attributes - ) - for version in versions: - return version - return None - - def get_hero_version_by_product_id( - self, - project_name: str, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER - ) -> Optional["VersionDict"]: - """Query hero version entity by product id. - - Only one hero version is available on a product. - - Args: - project_name (str): Name of project where to look for queried - entities. - product_id (int): Product id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. - - """ - versions = self.get_hero_versions( - project_name, - product_ids=[product_id], - fields=fields, - own_attributes=own_attributes - ) - for version in versions: - return version - return None - - def get_hero_versions( - self, - project_name: str, - product_ids: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Generator["VersionDict", None, None]: - """Query hero versions by multiple filters. - - Only one hero version is available on a product. - - Args: - project_name (str): Name of project where to look for queried - entities. - product_ids (Optional[Iterable[str]]): Product ids. - version_ids (Optional[Iterable[str]]): Version ids. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. - - """ - return self.get_versions( - project_name, - version_ids=version_ids, - product_ids=product_ids, - hero=True, - standard=False, - active=active, - fields=fields, - own_attributes=own_attributes - ) - - def get_last_versions( - self, - project_name: str, - product_ids: Iterable[str], - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Dict[str, Optional["VersionDict"]]: - """Query last version entities by product ids. - - Args: - project_name (str): Project where to look for representation. - product_ids (Iterable[str]): Product ids. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - dict[str, Optional[VersionDict]]: Last versions by product id. - - """ - if fields: - fields = set(fields) - fields.add("productId") - product_ids = set(product_ids) - versions = self.get_versions( - project_name, - product_ids=product_ids, - latest=True, - hero=False, - active=active, - fields=fields, - own_attributes=own_attributes - ) - output = { - version["productId"]: version - for version in versions - } - for product_id in product_ids: - output.setdefault(product_id, None) - return output - - def get_last_version_by_product_id( - self, - project_name: str, - product_id: str, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Optional["VersionDict"]: - """Query last version entity by product id. - - Args: - project_name (str): Project where to look for representation. - product_id (str): Product id. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Optional[VersionDict]: Queried version entity or None. - - """ - versions = self.get_versions( - project_name, - product_ids=[product_id], - latest=True, - hero=False, - active=active, - fields=fields, - own_attributes=own_attributes - ) - for version in versions: - return version - return None - - def get_last_version_by_product_name( - self, - project_name: str, - product_name: str, - folder_id: str, - active: "Union[bool, None]" = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, - ) -> Optional["VersionDict"]: - """Query last version entity by product name and folder id. - - Args: - project_name (str): Project where to look for representation. - product_name (str): Product name. - folder_id (str): Folder id. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. - - Returns: - Optional[VersionDict]: Queried version entity or None. - - """ - if not folder_id: - return None - - product = self.get_product_by_name( - project_name, product_name, folder_id, fields={"id"} - ) - if not product: - return None - return self.get_last_version_by_product_id( - project_name, - product["id"], - active=active, - fields=fields, - own_attributes=own_attributes - ) - - def version_is_latest(self, project_name: str, version_id: str) -> bool: - """Is version latest from a product. - - Args: - project_name (str): Project where to look for representation. - version_id (str): Version id. - - Returns: - bool: Version is latest or not. - - """ - query = GraphQlQuery("VersionIsLatest") - project_name_var = query.add_variable( - "projectName", "String!", project_name - ) - version_id_var = query.add_variable( - "versionId", "String!", version_id - ) - project_query = query.add_field("project") - project_query.set_filter("name", project_name_var) - version_query = project_query.add_field("version") - version_query.set_filter("id", version_id_var) - product_query = version_query.add_field("product") - latest_version_query = product_query.add_field("latestVersion") - latest_version_query.add_field("id") - - parsed_data = query.query(self) - latest_version = ( - parsed_data["project"]["version"]["product"]["latestVersion"] - ) - return latest_version["id"] == version_id - - def create_version( - self, - project_name: str, - version: int, - product_id: str, - task_id: Optional[str] = None, - author: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - version_id: Optional[str] = None, - ) -> str: - """Create new version. - - Args: - project_name (str): Project name. - version (int): Version. - product_id (str): Parent product id. - task_id (Optional[str]): Parent task id. - author (Optional[str]): Version author. - attrib (Optional[dict[str, Any]]): Version attributes. - data (Optional[dict[str, Any]]): Version data. - tags (Optional[Iterable[str]]): Version tags. - status (Optional[str]): Version status. - active (Optional[bool]): Version active state. - thumbnail_id (Optional[str]): Version thumbnail id. - version_id (Optional[str]): Version id. If not passed new id is - generated. - - Returns: - str: Version id. - - """ - if not version_id: - version_id = create_entity_id() - create_data = { - "id": version_id, - "version": version, - "productId": product_id, - } - for key, value in ( - ("taskId", task_id), - ("author", author), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ("thumbnailId", thumbnail_id), - ): - if value is not None: - create_data[key] = value - - response = self.post( - f"projects/{project_name}/versions", - **create_data - ) - response.raise_for_status() - return version_id - - def update_version( - self, - project_name: str, - version_id: str, - version: Optional[int] = None, - product_id: Optional[str] = None, - task_id: Optional[str] = NOT_SET, - author: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, - ): - """Update version entity on server. - - Do not pass ``task_id`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. - - Args: - project_name (str): Project name. - version_id (str): Version id. - version (Optional[int]): New version. - product_id (Optional[str]): New product id. - task_id (Optional[Union[str, None]]): New task id. - author (Optional[str]): New author username. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - thumbnail_id (Optional[Union[str, None]]): New thumbnail id. - - """ - update_data = {} - for key, value in ( - ("version", version), - ("productId", product_id), - ("attrib", attrib), - ("data", data), - ("tags", tags), - ("status", status), - ("active", active), - ("author", author), - ): - if value is not None: - update_data[key] = value - - for key, value in ( - ("taskId", task_id), - ("thumbnailId", thumbnail_id), - ): - if value is not NOT_SET: - update_data[key] = value - - response = self.patch( - f"projects/{project_name}/versions/{version_id}", - **update_data - ) - response.raise_for_status() - - def delete_version(self, project_name: str, version_id: str): - """Delete version. - - Args: - project_name (str): Project name. - version_id (str): Version id to delete. - - """ - response = self.delete( - f"projects/{project_name}/versions/{version_id}" - ) - response.raise_for_status() - # --- Batch operations processing --- def send_batch_operations( self, From 81729e38795d5d8838182d3055bd015c03a04b81 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:02:35 +0200 Subject: [PATCH 26/53] use set, list and dict for typehints --- ayon_api/server_api.py | 122 ++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 80f7ccb80..32cc9e727 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -17,7 +17,7 @@ import warnings from contextlib import contextmanager import typing -from typing import Optional, Iterable, Tuple, Generator, Dict, List, Set, Any +from typing import Optional, Iterable, Tuple, Generator, Any import requests @@ -854,7 +854,7 @@ def _update_session_headers(self): elif key in self._session.headers: self._session.headers.pop(key) - def get_info(self) -> Dict[str, Any]: + def get_info(self) -> dict[str, Any]: """Get information about current used api key. By default, the 'info' contains only 'uptime' and 'version'. With @@ -924,7 +924,7 @@ def graphql_allows_traits_in_representations(self) -> bool: ) return self._graphql_allows_traits_in_representations - def _get_user_info(self) -> Optional[Dict[str, Any]]: + def _get_user_info(self) -> Optional[dict[str, Any]]: if self._access_token is None: return None @@ -953,7 +953,7 @@ def get_users( usernames: Optional[Iterable[str]] = None, emails: Optional[Iterable[str]] = None, fields: Optional[Iterable[str]] = None, - ) -> Generator[Dict[str, Any], None, None]: + ) -> Generator[dict[str, Any], None, None]: """Get Users. Only administrators and managers can fetch all users. For other users @@ -1028,7 +1028,7 @@ def get_user_by_name( username: str, project_name: Optional[str] = None, fields: Optional[Iterable[str]] = None, - ) -> Optional[Dict[str, Any]]: + ) -> Optional[dict[str, Any]]: """Get user by name using GraphQl. Only administrators and managers can fetch all users. For other users @@ -1058,7 +1058,7 @@ def get_user_by_name( def get_user( self, username: Optional[str] = None - ) -> Optional[Dict[str, Any]]: + ) -> Optional[dict[str, Any]]: """Get user info using REST endpoint. User contains only explicitly set attributes in 'attrib'. @@ -1067,7 +1067,7 @@ def get_user( username (Optional[str]): Username. Returns: - Optional[Dict[str, Any]]: User info or None if user is not + Optional[dict[str, Any]]: User info or None if user is not found. """ @@ -1090,7 +1090,7 @@ def get_user( def get_headers( self, content_type: Optional[str] = None - ) -> Dict[str, str]: + ) -> dict[str, str]: if content_type is None: content_type = "application/json" @@ -1652,7 +1652,7 @@ def upload_reviewable( content_type: Optional[str] = None, filename: Optional[str] = None, progress: Optional[TransferProgress] = None, - headers: Optional[Dict[str, Any]] = None, + headers: Optional[dict[str, Any]] = None, **kwargs ) -> requests.Response: """Upload reviewable file to server. @@ -1667,7 +1667,7 @@ def upload_reviewable( filename (Optional[str]): User as original filename. Filename from 'filepath' is used when not filled. progress (Optional[TransferProgress]): Progress. - headers (Optional[Dict[str, Any]]): Headers. + headers (Optional[dict[str, Any]]): Headers. Returns: requests.Response: Server response. @@ -1729,7 +1729,7 @@ def trigger_server_restart(self): def query_graphql( self, query: str, - variables: Optional[Dict[str, Any]] = None, + variables: Optional[dict[str, Any]] = None, ) -> GraphQlResponse: """Execute GraphQl query. @@ -1751,10 +1751,10 @@ def query_graphql( response.raise_for_status() return GraphQlResponse(response) - def get_graphql_schema(self) -> Dict[str, Any]: + def get_graphql_schema(self) -> dict[str, Any]: return self.query_graphql(INTROSPECTION_QUERY).data["data"] - def get_server_schema(self) -> Optional[Dict[str, Any]]: + def get_server_schema(self) -> Optional[dict[str, Any]]: """Get server schema with info, url paths, components etc. Todos: @@ -1770,7 +1770,7 @@ def get_server_schema(self) -> Optional[Dict[str, Any]]: return response.data return None - def get_schemas(self) -> Dict[str, Any]: + def get_schemas(self) -> dict[str, Any]: """Get components schema. Name of components does not match entity type names e.g. 'project' is @@ -1805,7 +1805,7 @@ def set_attribute_config( self, attribute_name: str, data: "AttributeSchemaDataDict", - scope: List["AttributeScope"], + scope: list["AttributeScope"], position: Optional[int] = None, builtin: bool = False, ): @@ -1858,7 +1858,7 @@ def remove_attribute_config(self, attribute_name: str): def get_attributes_for_type( self, entity_type: "AttributeScope" - ) -> Dict[str, "AttributeSchemaDict"]: + ) -> dict[str, "AttributeSchemaDict"]: """Get attribute schemas available for an entity type. Example:: @@ -1911,7 +1911,7 @@ def get_attributes_for_type( def get_attributes_fields_for_type( self, entity_type: "AttributeScope" - ) -> Set[str]: + ) -> set[str]: """Prepare attribute fields for entity type. Returns: @@ -1924,7 +1924,7 @@ def get_attributes_fields_for_type( for attr in attributes } - def get_default_fields_for_type(self, entity_type: str) -> Set[str]: + def get_default_fields_for_type(self, entity_type: str) -> set[str]: """Default fields for entity type. Returns most of commonly used fields from server. @@ -2021,12 +2021,12 @@ def create_installer( version: str, python_version: str, platform_name: str, - python_modules: Dict[str, str], - runtime_python_modules: Dict[str, str], + python_modules: dict[str, str], + runtime_python_modules: dict[str, str], checksum: str, checksum_algorithm: str, file_size: int, - sources: Optional[List[Dict[str, Any]]] = None, + sources: Optional[list[dict[str, Any]]] = None, ): """Create new installer information on server. @@ -2070,7 +2070,7 @@ def create_installer( response = self.post("desktop/installers", **body) response.raise_for_status() - def update_installer(self, filename: str, sources: List[Dict[str, Any]]): + def update_installer(self, filename: str, sources: list[dict[str, Any]]): """Update installer information on server. Args: @@ -2187,13 +2187,13 @@ def get_dependency_packages(self) -> "DependencyPackagesDict": def create_dependency_package( self, filename: str, - python_modules: Dict[str, str], - source_addons: Dict[str, str], + python_modules: dict[str, str], + source_addons: dict[str, str], installer_version: str, checksum: str, checksum_algorithm: str, file_size: int, - sources: Optional[List[Dict[str, Any]]] = None, + sources: Optional[list[dict[str, Any]]] = None, platform_name: Optional[str] = None, ): """Create dependency package on server. @@ -2243,7 +2243,7 @@ def create_dependency_package( response.raise_for_status() def update_dependency_package( - self, filename: str, sources: List[Dict[str, Any]] + self, filename: str, sources: list[dict[str, Any]] ): """Update dependency package metadata on server. @@ -2402,15 +2402,15 @@ def get_bundles(self) -> "BundlesInfoDict": def create_bundle( self, name: str, - addon_versions: Dict[str, str], + addon_versions: dict[str, str], installer_version: str, - dependency_packages: Optional[Dict[str, str]] = None, + dependency_packages: Optional[dict[str, str]] = None, is_production: Optional[bool] = None, is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, dev_addons_config: Optional[ - Dict[str, "DevBundleAddonInfoDict"]] = None, + dict[str, "DevBundleAddonInfoDict"]] = None, ): """Create bundle on server. @@ -2473,15 +2473,15 @@ def create_bundle( def update_bundle( self, bundle_name: str, - addon_versions: Optional[Dict[str, str]] = None, + addon_versions: Optional[dict[str, str]] = None, installer_version: Optional[str] = None, - dependency_packages: Optional[Dict[str, str]] = None, + dependency_packages: Optional[dict[str, str]] = None, is_production: Optional[bool] = None, is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, dev_addons_config: Optional[ - Dict[str, "DevBundleAddonInfoDict"]] = None, + dict[str, "DevBundleAddonInfoDict"]] = None, ): """Update bundle on server. @@ -2531,16 +2531,16 @@ def update_bundle( def check_bundle_compatibility( self, name: str, - addon_versions: Dict[str, str], + addon_versions: dict[str, str], installer_version: str, - dependency_packages: Optional[Dict[str, str]] = None, + dependency_packages: Optional[dict[str, str]] = None, is_production: Optional[bool] = None, is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, dev_addons_config: Optional[ - Dict[str, "DevBundleAddonInfoDict"]] = None, - ) -> Dict[str, Any]: + dict[str, "DevBundleAddonInfoDict"]] = None, + ) -> dict[str, Any]: """Check bundle compatibility. Can be used as per-flight validation before creating bundle. @@ -2562,7 +2562,7 @@ def check_bundle_compatibility( dev addons. Can be used only if 'is_dev' is set to 'True'. Returns: - Dict[str, Any]: Server response, with 'success' and 'issues'. + dict[str, Any]: Server response, with 'success' and 'issues'. """ body = { @@ -2597,7 +2597,7 @@ def delete_bundle(self, bundle_name: str): response.raise_for_status() # Anatomy presets - def get_project_anatomy_presets(self) -> List["AnatomyPresetDict"]: + def get_project_anatomy_presets(self) -> list["AnatomyPresetDict"]: """Anatomy presets available on server. Content has basic information about presets. Example output:: @@ -2688,7 +2688,7 @@ def get_build_in_anatomy_preset(self) -> "AnatomyPresetDict": def get_project_root_overrides( self, project_name: str - ) -> Dict[str, Dict[str, str]]: + ) -> dict[str, dict[str, str]]: """Root overrides per site name. Method is based on logged user and can't be received for any other @@ -2709,7 +2709,7 @@ def get_project_root_overrides( def get_project_roots_by_site( self, project_name: str - ) -> Dict[str, Dict[str, str]]: + ) -> dict[str, dict[str, str]]: """Root overrides per site name. Method is based on logged user and can't be received for any other @@ -2739,7 +2739,7 @@ def get_project_roots_by_site( def get_project_root_overrides_by_site_id( self, project_name: str, site_id: Optional[str] = None - ) -> Dict[str, str]: + ) -> dict[str, str]: """Root overrides for site. If site id is not passed a site set in current api object is used @@ -2765,7 +2765,7 @@ def get_project_root_overrides_by_site_id( def get_project_roots_for_site( self, project_name: str, site_id: Optional[str] = None - ) -> Dict[str, str]: + ) -> dict[str, str]: """Root overrides for site. If site id is not passed a site set in current api object is used @@ -2798,7 +2798,7 @@ def _get_project_roots_values( project_name: str, site_id: Optional[str] = None, platform_name: Optional[str] = None, - ) -> Dict[str, str]: + ) -> dict[str, str]: """Root values for site or platform. Helper function that treats 'siteRoots' endpoint. The endpoint @@ -2840,7 +2840,7 @@ def _get_project_roots_values( def get_project_roots_by_site_id( self, project_name: str, site_id: Optional[str] = None - ) -> Dict[str, str]: + ) -> dict[str, str]: """Root values for a site. If site id is not passed a site set in current api object is used @@ -2863,7 +2863,7 @@ def get_project_roots_by_site_id( def get_project_roots_by_platform( self, project_name: str, platform_name: Optional[str] = None - ) -> Dict[str, str]: + ) -> dict[str, str]: """Root values for a site. If platform name is not passed current platform name is used instead. @@ -2890,7 +2890,7 @@ def get_addon_settings_schema( addon_name: str, addon_version: str, project_name: Optional[str] = None - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Sudio/Project settings schema of an addon. Project schema may look differently as some enums are based on project @@ -2919,7 +2919,7 @@ def get_addon_settings_schema( def get_addon_site_settings_schema( self, addon_name: str, addon_version: str - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Site settings schema of an addon. Args: @@ -2941,7 +2941,7 @@ def get_addon_studio_settings( addon_name: str, addon_version: str, variant: Optional[str] = None, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Addon studio settings. Receive studio settings for specific version of an addon. @@ -2975,7 +2975,7 @@ def get_addon_project_settings( variant: Optional[str] = None, site_id: Optional[str] = None, use_site: bool = True - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Addon project settings. Receive project settings for specific version of an addon. The settings @@ -3029,7 +3029,7 @@ def get_addon_settings( variant: Optional[str] = None, site_id: Optional[str] = None, use_site: bool = True - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Receive addon settings. Receive addon settings based on project name value. Some arguments may @@ -3067,7 +3067,7 @@ def get_addon_site_settings( addon_name: str, addon_version: str, site_id: Optional[str] = None, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Site settings of an addon. If site id is not available an empty dictionary is returned. @@ -3102,7 +3102,7 @@ def get_bundle_settings( variant: Optional[str] = None, site_id: Optional[str] = None, use_site: bool = True, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Get complete set of settings for given data. If project is not passed then studio settings are returned. If variant @@ -3156,7 +3156,7 @@ def get_addons_studio_settings( site_id: Optional[str] = None, use_site: bool = True, only_values: bool = True, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """All addons settings in one bulk. Warnings: @@ -3202,7 +3202,7 @@ def get_addons_project_settings( site_id: Optional[str] = None, use_site: bool = True, only_values: bool = True, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Project settings of all addons. Server returns information about used addon versions, so full output @@ -3269,7 +3269,7 @@ def get_addons_settings( site_id: Optional[str] = None, use_site: bool = True, only_values: bool = True, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: """Universal function to receive all addon settings. Based on 'project_name' will receive studio settings or project @@ -3314,7 +3314,7 @@ def get_addons_settings( only_values=only_values ) - def get_secrets(self) -> List["SecretDict"]: + def get_secrets(self) -> list["SecretDict"]: """Get all secrets. Example output:: @@ -3422,10 +3422,10 @@ def get_rest_entity_by_id( def send_batch_operations( self, project_name: str, - operations: List[Dict[str, Any]], + operations: list[dict[str, Any]], can_fail: bool = False, raise_on_fail: bool = True - ) -> List[Dict[str, Any]]: + ) -> list[dict[str, Any]]: """Post multiple CRUD operations to server. When multiple changes should be made on server side this is the best @@ -3461,10 +3461,10 @@ def send_batch_operations( def _send_batch_operations( self, uri: str, - operations: List[Dict[str, Any]], + operations: list[dict[str, Any]], can_fail: bool, raise_on_fail: bool - ) -> List[Dict[str, Any]]: + ) -> list[dict[str, Any]]: if not operations: return [] @@ -3527,7 +3527,7 @@ def _send_batch_operations( return op_results def _prepare_fields( - self, entity_type: str, fields: Set[str], own_attributes: bool = False + self, entity_type: str, fields: set[str], own_attributes: bool = False ): if not fields: return From dc058e0ac103a80accd8af04d561267dfe67cf88 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:02:50 +0200 Subject: [PATCH 27/53] use list, dict and set in typing too --- ayon_api/typing.py | 130 ++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/ayon_api/typing.py b/ayon_api/typing.py index 541ed734d..038372f0c 100644 --- a/ayon_api/typing.py +++ b/ayon_api/typing.py @@ -1,8 +1,8 @@ +from __future__ import annotations + import io from typing import ( Literal, - Dict, - List, Any, TypedDict, Union, @@ -49,7 +49,7 @@ EventFilterValueType = Union[ None, str, int, float, - List[str], List[int], List[float], + list[str], list[int], list[float], ] @@ -84,7 +84,7 @@ class EventFilterCondition(TypedDict): class EventFilter(TypedDict): - conditions: List[EventFilterCondition] + conditions: list[EventFilterCondition] operator: Literal["and", "or"] @@ -138,38 +138,38 @@ class AttributeSchemaDataDict(TypedDict): minItems: Optional[int] maxItems: Optional[int] regex: Optional[str] - enum: Optional[List[AttributeEnumItemDict]] + enum: Optional[list[AttributeEnumItemDict]] class AttributeSchemaDict(TypedDict): name: str position: int - scope: List[AttributeScope] + scope: list[AttributeScope] builtin: bool data: AttributeSchemaDataDict class AttributesSchemaDict(TypedDict): - attributes: List[AttributeSchemaDict] + attributes: list[AttributeSchemaDict] class AddonVersionInfoDict(TypedDict): hasSettings: bool hasSiteSettings: bool - frontendScopes: Dict[str, Any] - clientPyproject: Dict[str, Any] - clientSourceInfo: List[Dict[str, Any]] + frontendScopes: dict[str, Any] + clientPyproject: dict[str, Any] + clientSourceInfo: list[dict[str, Any]] isBroken: bool class AddonInfoDict(TypedDict): name: str title: str - versions: Dict[str, AddonVersionInfoDict] + versions: dict[str, AddonVersionInfoDict] class AddonsInfoDict(TypedDict): - addons: List[AddonInfoDict] + addons: list[AddonInfoDict] class InstallerInfoDict(TypedDict): @@ -178,15 +178,15 @@ class InstallerInfoDict(TypedDict): size: int checksum: str checksumAlgorithm: str - sources: List[Dict[str, Any]] + sources: list[dict[str, Any]] version: str pythonVersion: str - pythonModules: Dict[str, str] - runtimePythonModules: Dict[str, str] + pythonModules: dict[str, str] + runtimePythonModules: dict[str, str] class InstallersInfoDict(TypedDict): - installers: List[InstallerInfoDict] + installers: list[InstallerInfoDict] class DependencyPackageDict(TypedDict): @@ -195,14 +195,14 @@ class DependencyPackageDict(TypedDict): size: int checksum: str checksumAlgorithm: str - sources: List[Dict[str, Any]] + sources: list[dict[str, Any]] installerVersion: str - sourceAddons: Dict[str, str] - pythonModules: Dict[str, str] + sourceAddons: dict[str, str] + pythonModules: dict[str, str] class DependencyPackagesDict(TypedDict): - packages: List[DependencyPackageDict] + packages: list[DependencyPackageDict] class DevBundleAddonInfoDict(TypedDict): @@ -213,10 +213,10 @@ class DevBundleAddonInfoDict(TypedDict): class BundleInfoDict(TypedDict): name: str createdAt: str - addons: Dict[str, str] + addons: dict[str, str] installerVersion: str - dependencyPackages: Dict[str, str] - addonDevelopment: Dict[str, DevBundleAddonInfoDict] + dependencyPackages: dict[str, str] + addonDevelopment: dict[str, DevBundleAddonInfoDict] isProduction: bool isStaging: bool isArchived: bool @@ -225,9 +225,9 @@ class BundleInfoDict(TypedDict): class BundlesInfoDict(TypedDict): - bundles: List[BundleInfoDict] + bundles: list[BundleInfoDict] productionBundle: str - devBundles: List[str] + devBundles: list[str] class AnatomyPresetInfoDict(TypedDict): @@ -254,12 +254,12 @@ class AnatomyPresetTemplatesDict(TypedDict): version: str frame_padding: int frame: str - work: List[AnatomyPresetTemplateDict] - publish: List[AnatomyPresetTemplateDict] - hero: List[AnatomyPresetTemplateDict] - delivery: List[AnatomyPresetTemplateDict] - staging: List[AnatomyPresetTemplateDict] - others: List[AnatomyPresetTemplateDict] + work: list[AnatomyPresetTemplateDict] + publish: list[AnatomyPresetTemplateDict] + hero: list[AnatomyPresetTemplateDict] + delivery: list[AnatomyPresetTemplateDict] + staging: list[AnatomyPresetTemplateDict] + others: list[AnatomyPresetTemplateDict] class AnatomyPresetSubtypeDict(TypedDict): @@ -293,7 +293,7 @@ class AnatomyPresetStatusDict(TypedDict): state: str icon: str color: str - scope: List[StatusScope] + scope: list[StatusScope] original_name: str @@ -304,29 +304,29 @@ class AnatomyPresetTagDict(TypedDict): class AnatomyPresetDict(TypedDict): - roots: List[AnatomyPresetRootDict] + roots: list[AnatomyPresetRootDict] templates: AnatomyPresetTemplatesDict - attributes: Dict[str, Any] - folder_types: List[AnatomyPresetSubtypeDict] - task_types: List[AnatomyPresetSubtypeDict] - link_types: List[AnatomyPresetLinkTypeDict] - statuses: List[AnatomyPresetStatusDict] - tags: List[AnatomyPresetTagDict] + attributes: dict[str, Any] + folder_types: list[AnatomyPresetSubtypeDict] + task_types: list[AnatomyPresetSubtypeDict] + link_types: list[AnatomyPresetLinkTypeDict] + statuses: list[AnatomyPresetStatusDict] + tags: list[AnatomyPresetTagDict] class SecretDict(TypedDict): name: str value: str -ProjectDict = Dict[str, Any] -FolderDict = Dict[str, Any] -TaskDict = Dict[str, Any] -ProductDict = Dict[str, Any] -VersionDict = Dict[str, Any] -RepresentationDict = Dict[str, Any] -WorkfileInfoDict = Dict[str, Any] -EventDict = Dict[str, Any] -ActivityDict = Dict[str, Any] +ProjectDict = dict[str, Any] +FolderDict = dict[str, Any] +TaskDict = dict[str, Any] +ProductDict = dict[str, Any] +VersionDict = dict[str, Any] +RepresentationDict = dict[str, Any] +WorkfileInfoDict = dict[str, Any] +EventDict = dict[str, Any] +ActivityDict = dict[str, Any] AnyEntityDict = Union[ ProjectDict, FolderDict, @@ -344,16 +344,16 @@ class FlatFolderDict(TypedDict): id: str parentId: Optional[str] path: str - parents: List[str] + parents: list[str] name: str label: Optional[str] folderType: str hasTasks: bool hasChildren: bool - taskNames: List[str] + taskNames: list[str] status: str - attrib: Dict[str, Any] - ownAttrib: List[str] + attrib: dict[str, Any] + ownAttrib: list[str] updatedAt: str @@ -364,14 +364,14 @@ class ProjectHierarchyItemDict(TypedDict): status: str folderType: str hasTasks: bool - taskNames: List[str] - parents: List[str] + taskNames: list[str] + parents: list[str] parentId: Optional[str] - children: List["ProjectHierarchyItemDict"] + children: list["ProjectHierarchyItemDict"] class ProjectHierarchyDict(TypedDict): - hierarchy: List[ProjectHierarchyItemDict] + hierarchy: list[ProjectHierarchyItemDict] class ProductTypeDict(TypedDict): @@ -401,7 +401,7 @@ class ActionManifestDict(TypedDict): icon: Optional[IconDefType] adminOnly: bool managerOnly: bool - configFields: List[Dict[str, Any]] + configFields: list[dict[str, Any]] featured: bool addonName: str addonVersion: str @@ -444,7 +444,7 @@ class ActionQueryPayload(BaseActionPayload): class ActionFormPayload(BaseActionPayload): title: str - fields: List[Dict[str, Any]] + fields: list[dict[str, Any]] submit_label: str submit_icon: str cancel_label: str @@ -471,8 +471,8 @@ class ActionTriggerResponse(TypedDict): class ActionTakeResponse(TypedDict): eventId: str actionIdentifier: str - args: List[str] - context: Dict[str, Any] + args: list[str] + context: dict[str, Any] addonName: str addonVersion: str variant: str @@ -482,10 +482,10 @@ class ActionTakeResponse(TypedDict): class ActionConfigResponse(TypedDict): projectName: str entityType: str - entitySubtypes: List[str] - entityIds: List[str] - formData: Dict[str, Any] - value: Dict[str, Any] + entitySubtypes: list[str] + entityIds: list[str] + formData: dict[str, Any] + value: dict[str, Any] StreamType = Union[io.BytesIO, BinaryIO] @@ -493,4 +493,4 @@ class ActionConfigResponse(TypedDict): class EntityListAttributeDefinitionDict(TypedDict): name: str - data: Dict[str, Any] + data: dict[str, Any] From 99df5d9e26e91466c40654bcb0517da91f1dbc6c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 12 Aug 2025 20:03:05 +0200 Subject: [PATCH 28/53] updated typehints in public api --- ayon_api/_api.py | 108 +++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 8ae32db4d..e30357663 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -666,7 +666,7 @@ def set_sender_type( ) -def get_info() -> Dict[str, Any]: +def get_info() -> dict[str, Any]: """Get information about current used api key. By default, the 'info' contains only 'uptime' and 'version'. With @@ -718,7 +718,7 @@ def get_users( usernames: Optional[Iterable[str]] = None, emails: Optional[Iterable[str]] = None, fields: Optional[Iterable[str]] = None, -) -> Generator[Dict[str, Any], None, None]: +) -> Generator[dict[str, Any], None, None]: """Get Users. Only administrators and managers can fetch all users. For other users @@ -748,7 +748,7 @@ def get_user_by_name( username: str, project_name: Optional[str] = None, fields: Optional[Iterable[str]] = None, -) -> Optional[Dict[str, Any]]: +) -> Optional[dict[str, Any]]: """Get user by name using GraphQl. Only administrators and managers can fetch all users. For other users @@ -775,7 +775,7 @@ def get_user_by_name( def get_user( username: Optional[str] = None, -) -> Optional[Dict[str, Any]]: +) -> Optional[dict[str, Any]]: """Get user info using REST endpoint. User contains only explicitly set attributes in 'attrib'. @@ -784,7 +784,7 @@ def get_user( username (Optional[str]): Username. Returns: - Optional[Dict[str, Any]]: User info or None if user is not + Optional[dict[str, Any]]: User info or None if user is not found. """ @@ -1059,7 +1059,7 @@ def upload_reviewable( content_type: Optional[str] = None, filename: Optional[str] = None, progress: Optional[TransferProgress] = None, - headers: Optional[Dict[str, Any]] = None, + headers: Optional[dict[str, Any]] = None, **kwargs, ) -> requests.Response: """Upload reviewable file to server. @@ -1074,7 +1074,7 @@ def upload_reviewable( filename (Optional[str]): User as original filename. Filename from 'filepath' is used when not filled. progress (Optional[TransferProgress]): Progress. - headers (Optional[Dict[str, Any]]): Headers. + headers (Optional[dict[str, Any]]): Headers. Returns: requests.Response: Server response. @@ -1107,7 +1107,7 @@ def trigger_server_restart(): def query_graphql( query: str, - variables: Optional[Dict[str, Any]] = None, + variables: Optional[dict[str, Any]] = None, ) -> GraphQlResponse: """Execute GraphQl query. @@ -1127,12 +1127,12 @@ def query_graphql( ) -def get_graphql_schema() -> Dict[str, Any]: +def get_graphql_schema() -> dict[str, Any]: con = get_server_api_connection() return con.get_graphql_schema() -def get_server_schema() -> Optional[Dict[str, Any]]: +def get_server_schema() -> Optional[dict[str, Any]]: """Get server schema with info, url paths, components etc. Todos: @@ -1146,7 +1146,7 @@ def get_server_schema() -> Optional[Dict[str, Any]]: return con.get_server_schema() -def get_schemas() -> Dict[str, Any]: +def get_schemas() -> dict[str, Any]: """Get components schema. Name of components does not match entity type names e.g. 'project' is @@ -1179,7 +1179,7 @@ def reset_attributes_schema(): def set_attribute_config( attribute_name: str, data: "AttributeSchemaDataDict", - scope: List["AttributeScope"], + scope: list["AttributeScope"], position: Optional[int] = None, builtin: bool = False, ): @@ -1212,7 +1212,7 @@ def remove_attribute_config( def get_attributes_for_type( entity_type: "AttributeScope", -) -> Dict[str, "AttributeSchemaDict"]: +) -> dict[str, "AttributeSchemaDict"]: """Get attribute schemas available for an entity type. Example:: @@ -1257,7 +1257,7 @@ def get_attributes_for_type( def get_attributes_fields_for_type( entity_type: "AttributeScope", -) -> Set[str]: +) -> set[str]: """Prepare attribute fields for entity type. Returns: @@ -1272,7 +1272,7 @@ def get_attributes_fields_for_type( def get_default_fields_for_type( entity_type: str, -) -> Set[str]: +) -> set[str]: """Default fields for entity type. Returns most of commonly used fields from server. @@ -1319,12 +1319,12 @@ def create_installer( version: str, python_version: str, platform_name: str, - python_modules: Dict[str, str], - runtime_python_modules: Dict[str, str], + python_modules: dict[str, str], + runtime_python_modules: dict[str, str], checksum: str, checksum_algorithm: str, file_size: int, - sources: Optional[List[Dict[str, Any]]] = None, + sources: Optional[list[dict[str, Any]]] = None, ): """Create new installer information on server. @@ -1368,7 +1368,7 @@ def create_installer( def update_installer( filename: str, - sources: List[Dict[str, Any]], + sources: list[dict[str, Any]], ): """Update installer information on server. @@ -1484,13 +1484,13 @@ def get_dependency_packages() -> "DependencyPackagesDict": def create_dependency_package( filename: str, - python_modules: Dict[str, str], - source_addons: Dict[str, str], + python_modules: dict[str, str], + source_addons: dict[str, str], installer_version: str, checksum: str, checksum_algorithm: str, file_size: int, - sources: Optional[List[Dict[str, Any]]] = None, + sources: Optional[list[dict[str, Any]]] = None, platform_name: Optional[str] = None, ): """Create dependency package on server. @@ -1538,7 +1538,7 @@ def create_dependency_package( def update_dependency_package( filename: str, - sources: List[Dict[str, Any]], + sources: list[dict[str, Any]], ): """Update dependency package metadata on server. @@ -1676,14 +1676,14 @@ def get_bundles() -> "BundlesInfoDict": def create_bundle( name: str, - addon_versions: Dict[str, str], + addon_versions: dict[str, str], installer_version: str, - dependency_packages: Optional[Dict[str, str]] = None, + dependency_packages: Optional[dict[str, str]] = None, is_production: Optional[bool] = None, is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[Dict[str, "DevBundleAddonInfoDict"]] = None, + dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, ): """Create bundle on server. @@ -1739,14 +1739,14 @@ def create_bundle( def update_bundle( bundle_name: str, - addon_versions: Optional[Dict[str, str]] = None, + addon_versions: Optional[dict[str, str]] = None, installer_version: Optional[str] = None, - dependency_packages: Optional[Dict[str, str]] = None, + dependency_packages: Optional[dict[str, str]] = None, is_production: Optional[bool] = None, is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[Dict[str, "DevBundleAddonInfoDict"]] = None, + dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, ): """Update bundle on server. @@ -1788,15 +1788,15 @@ def update_bundle( def check_bundle_compatibility( name: str, - addon_versions: Dict[str, str], + addon_versions: dict[str, str], installer_version: str, - dependency_packages: Optional[Dict[str, str]] = None, + dependency_packages: Optional[dict[str, str]] = None, is_production: Optional[bool] = None, is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[Dict[str, "DevBundleAddonInfoDict"]] = None, -) -> Dict[str, Any]: + dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, +) -> dict[str, Any]: """Check bundle compatibility. Can be used as per-flight validation before creating bundle. @@ -1818,7 +1818,7 @@ def check_bundle_compatibility( dev addons. Can be used only if 'is_dev' is set to 'True'. Returns: - Dict[str, Any]: Server response, with 'success' and 'issues'. + dict[str, Any]: Server response, with 'success' and 'issues'. """ con = get_server_api_connection() @@ -1850,7 +1850,7 @@ def delete_bundle( ) -def get_project_anatomy_presets() -> List["AnatomyPresetDict"]: +def get_project_anatomy_presets() -> list["AnatomyPresetDict"]: """Anatomy presets available on server. Content has basic information about presets. Example output:: @@ -1929,7 +1929,7 @@ def get_build_in_anatomy_preset() -> "AnatomyPresetDict": def get_project_root_overrides( project_name: str, -) -> Dict[str, Dict[str, str]]: +) -> dict[str, dict[str, str]]: """Root overrides per site name. Method is based on logged user and can't be received for any other @@ -1952,7 +1952,7 @@ def get_project_root_overrides( def get_project_roots_by_site( project_name: str, -) -> Dict[str, Dict[str, str]]: +) -> dict[str, dict[str, str]]: """Root overrides per site name. Method is based on logged user and can't be received for any other @@ -1980,7 +1980,7 @@ def get_project_roots_by_site( def get_project_root_overrides_by_site_id( project_name: str, site_id: Optional[str] = None, -) -> Dict[str, str]: +) -> dict[str, str]: """Root overrides for site. If site id is not passed a site set in current api object is used @@ -2006,7 +2006,7 @@ def get_project_root_overrides_by_site_id( def get_project_roots_for_site( project_name: str, site_id: Optional[str] = None, -) -> Dict[str, str]: +) -> dict[str, str]: """Root overrides for site. If site id is not passed a site set in current api object is used @@ -2035,7 +2035,7 @@ def get_project_roots_for_site( def get_project_roots_by_site_id( project_name: str, site_id: Optional[str] = None, -) -> Dict[str, str]: +) -> dict[str, str]: """Root values for a site. If site id is not passed a site set in current api object is used @@ -2061,7 +2061,7 @@ def get_project_roots_by_site_id( def get_project_roots_by_platform( project_name: str, platform_name: Optional[str] = None, -) -> Dict[str, str]: +) -> dict[str, str]: """Root values for a site. If platform name is not passed current platform name is used instead. @@ -2090,7 +2090,7 @@ def get_addon_settings_schema( addon_name: str, addon_version: str, project_name: Optional[str] = None, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Sudio/Project settings schema of an addon. Project schema may look differently as some enums are based on project @@ -2117,7 +2117,7 @@ def get_addon_settings_schema( def get_addon_site_settings_schema( addon_name: str, addon_version: str, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Site settings schema of an addon. Args: @@ -2139,7 +2139,7 @@ def get_addon_studio_settings( addon_name: str, addon_version: str, variant: Optional[str] = None, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Addon studio settings. Receive studio settings for specific version of an addon. @@ -2169,7 +2169,7 @@ def get_addon_project_settings( variant: Optional[str] = None, site_id: Optional[str] = None, use_site: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Addon project settings. Receive project settings for specific version of an addon. The settings @@ -2214,7 +2214,7 @@ def get_addon_settings( variant: Optional[str] = None, site_id: Optional[str] = None, use_site: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Receive addon settings. Receive addon settings based on project name value. Some arguments may @@ -2254,7 +2254,7 @@ def get_addon_site_settings( addon_name: str, addon_version: str, site_id: Optional[str] = None, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Site settings of an addon. If site id is not available an empty dictionary is returned. @@ -2283,7 +2283,7 @@ def get_bundle_settings( variant: Optional[str] = None, site_id: Optional[str] = None, use_site: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Get complete set of settings for given data. If project is not passed then studio settings are returned. If variant @@ -2331,7 +2331,7 @@ def get_addons_studio_settings( site_id: Optional[str] = None, use_site: bool = True, only_values: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: """All addons settings in one bulk. Warnings: @@ -2373,7 +2373,7 @@ def get_addons_project_settings( site_id: Optional[str] = None, use_site: bool = True, only_values: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Project settings of all addons. Server returns information about used addon versions, so full output @@ -2433,7 +2433,7 @@ def get_addons_settings( site_id: Optional[str] = None, use_site: bool = True, only_values: bool = True, -) -> Dict[str, Any]: +) -> dict[str, Any]: """Universal function to receive all addon settings. Based on 'project_name' will receive studio settings or project @@ -2471,7 +2471,7 @@ def get_addons_settings( ) -def get_secrets() -> List["SecretDict"]: +def get_secrets() -> list["SecretDict"]: """Get all secrets. Example output:: @@ -2582,10 +2582,10 @@ def get_rest_entity_by_id( def send_batch_operations( project_name: str, - operations: List[Dict[str, Any]], + operations: list[dict[str, Any]], can_fail: bool = False, raise_on_fail: bool = True, -) -> List[Dict[str, Any]]: +) -> list[dict[str, Any]]: """Post multiple CRUD operations to server. When multiple changes should be made on server side this is the best From 815eccca9ad77bcc6609971102371227133ebd81 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:21:42 +0200 Subject: [PATCH 29/53] added get_site_id to base --- ayon_api/_base.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ayon_api/_base.py b/ayon_api/_base.py index 95021aec7..29e193e2d 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_base.py @@ -63,6 +63,9 @@ def raw_delete(self, entrypoint: str, **kwargs): def get_default_settings_variant(self) -> str: raise NotImplementedError() + def get_site_id(self) -> Optional[str]: + raise NotImplementedError() + def get_default_fields_for_type(self, entity_type: str) -> set[str]: raise NotImplementedError() From 200574f7e04f22f431d4d9560689076ecd5f7eef Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:22:05 +0200 Subject: [PATCH 30/53] removed unused imports --- ayon_api/server_api.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 32cc9e727..b2a8c17be 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -100,13 +100,7 @@ SecretDict, AnyEntityDict, - TaskDict, - ProductDict, - VersionDict, - RepresentationDict, - WorkfileInfoDict, - ProductTypeDict, StreamType, ) From cf0e691394faa72031e1cd11c2e2075da06441df Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:22:22 +0200 Subject: [PATCH 31/53] import placeholder from base --- automated_api.py | 2 +- ayon_api/server_api.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/automated_api.py b/automated_api.py index e9804adfd..41754e412 100644 --- a/automated_api.py +++ b/automated_api.py @@ -235,7 +235,7 @@ def _add_typehint(param_name, param, api_globals): def _kw_default_to_str(param_name, param, api_globals): - from ayon_api.server_api import _PLACEHOLDER + from ayon_api._base import _PLACEHOLDER from ayon_api.utils import NOT_SET if param.default is inspect.Parameter.empty: diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index b2a8c17be..736630cd6 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -67,7 +67,6 @@ get_machine_name, fill_own_attribs, ) -from ._base import _PLACEHOLDER from ._actions import _ActionsAPI from ._activities import _ActivitiesAPI from ._addons import _AddonsAPI From 013cc8f59f6e2201052561c2419c87cb4f6a4ecf Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:23:16 +0200 Subject: [PATCH 32/53] move all bundles and addons endpoints to single class --- ayon_api/_addons.py | 672 ++++++++++++++++++++++++++++++++++++++++- ayon_api/server_api.py | 663 ---------------------------------------- 2 files changed, 669 insertions(+), 666 deletions(-) diff --git a/ayon_api/_addons.py b/ayon_api/_addons.py index 10337ec50..e09bf2055 100644 --- a/ayon_api/_addons.py +++ b/ayon_api/_addons.py @@ -1,6 +1,6 @@ import os import typing -from typing import Optional +from typing import Optional, Any from .utils import ( RequestTypes, @@ -10,10 +10,246 @@ from ._base import _BaseServerAPI if typing.TYPE_CHECKING: - from .typing import AddonsInfoDict + from .typing import ( + AddonsInfoDict, + BundlesInfoDict, + DevBundleAddonInfoDict, + ) class _AddonsAPI(_BaseServerAPI): + def get_bundles(self) -> "BundlesInfoDict": + """Server bundles with basic information. + + This is 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("bundles") + response.raise_for_status() + return response.data + + def create_bundle( + self, + name: str, + addon_versions: dict[str, str], + installer_version: str, + dependency_packages: Optional[dict[str, str]] = None, + is_production: Optional[bool] = None, + is_staging: Optional[bool] = None, + is_dev: Optional[bool] = None, + dev_active_user: Optional[str] = None, + dev_addons_config: Optional[ + dict[str, "DevBundleAddonInfoDict"]] = None, + ): + """Create bundle on server. + + Bundle cannot be changed once is created. Only isProduction, isStaging + and dependency packages can change after creation. In case dev bundle + is created, it is possible to change anything, but it is not possible + to mark bundle as dev and production or staging at the same time. + + Development addon config can define custom path to client code. It is + used only for dev bundles. + + Example of 'dev_addons_config':: + + ```json + { + "core": { + "enabled": true, + "path": "/path/to/ayon-core/client" + } + } + ``` + + 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. + is_dev (Optional[bool]): Bundle will be marked as dev. + dev_active_user (Optional[str]): Username that will be assigned + to dev bundle. Can be used only if 'is_dev' is set to 'True'. + dev_addons_config (Optional[dict[str, Any]]): Configuration for + dev addons. Can be used only if 'is_dev' is set to 'True'. + + """ + body = { + "name": name, + "installerVersion": installer_version, + "addons": addon_versions, + } + + for key, value in ( + ("dependencyPackages", dependency_packages), + ("isProduction", is_production), + ("isStaging", is_staging), + ("isDev", is_dev), + ("activeUser", dev_active_user), + ("addonDevelopment", dev_addons_config), + ): + if value is not None: + body[key] = value + + response = self.post("bundles", **body) + response.raise_for_status() + + def update_bundle( + self, + bundle_name: str, + addon_versions: Optional[dict[str, str]] = None, + installer_version: Optional[str] = None, + dependency_packages: Optional[dict[str, str]] = None, + is_production: Optional[bool] = None, + is_staging: Optional[bool] = None, + is_dev: Optional[bool] = None, + dev_active_user: Optional[str] = None, + dev_addons_config: Optional[ + dict[str, "DevBundleAddonInfoDict"]] = 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. + addon_versions (Optional[dict[str, str]]): Addon versions, + possible only for dev bundles. + installer_version (Optional[str]): Installer version, possible + only for dev bundles. + 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. + is_dev (Optional[bool]): Bundle will be marked as dev. + dev_active_user (Optional[str]): Username that will be assigned + to dev bundle. Can be used only for dev bundles. + dev_addons_config (Optional[dict[str, Any]]): Configuration for + dev addons. Can be used only for dev bundles. + + """ + body = { + key: value + for key, value in ( + ("installerVersion", installer_version), + ("addons", addon_versions), + ("dependencyPackages", dependency_packages), + ("isProduction", is_production), + ("isStaging", is_staging), + ("isDev", is_dev), + ("activeUser", dev_active_user), + ("addonDevelopment", dev_addons_config), + ) + if value is not None + } + + response = self.patch( + f"bundles/{bundle_name}", + **body + ) + response.raise_for_status() + + def check_bundle_compatibility( + self, + name: str, + addon_versions: dict[str, str], + installer_version: str, + dependency_packages: Optional[dict[str, str]] = None, + is_production: Optional[bool] = None, + is_staging: Optional[bool] = None, + is_dev: Optional[bool] = None, + dev_active_user: Optional[str] = None, + dev_addons_config: Optional[ + dict[str, "DevBundleAddonInfoDict"]] = None, + ) -> dict[str, Any]: + """Check bundle compatibility. + + Can be used as per-flight validation before creating bundle. + + 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. + is_dev (Optional[bool]): Bundle will be marked as dev. + dev_active_user (Optional[str]): Username that will be assigned + to dev bundle. Can be used only if 'is_dev' is set to 'True'. + dev_addons_config (Optional[dict[str, Any]]): Configuration for + dev addons. Can be used only if 'is_dev' is set to 'True'. + + Returns: + dict[str, Any]: Server response, with 'success' and 'issues'. + + """ + body = { + "name": name, + "installerVersion": installer_version, + "addons": addon_versions, + } + + for key, value in ( + ("dependencyPackages", dependency_packages), + ("isProduction", is_production), + ("isStaging", is_staging), + ("isDev", is_dev), + ("activeUser", dev_active_user), + ("addonDevelopment", dev_addons_config), + ): + if value is not None: + body[key] = value + + response = self.post("bundles/check", **body) + response.raise_for_status() + return response.data + + def delete_bundle(self, bundle_name: str): + """Delete bundle from server. + + Args: + bundle_name (str): Name of bundle to delete. + + """ + response = self.delete(f"bundles/{bundle_name}") + response.raise_for_status() + def get_addon_endpoint( self, addon_name: str, @@ -69,7 +305,7 @@ def get_addon_url( """Calculate url to addon route. Examples: - + >>> from ayon_api import ServerAPI >>> api = ServerAPI("https://your.url.com") >>> api.get_addon_url( ... "example", "1.0.0", "private", "my.zip") @@ -215,3 +451,433 @@ def download_addon_private_file( url, dst_filepath, chunk_size=chunk_size, progress=progress ) return dst_filepath + + + def get_addon_settings_schema( + self, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None + ) -> dict[str, Any]: + """Sudio/Project settings schema of an addon. + + Project schema may look differently as some enums are based on project + values. + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + project_name (Optional[str]): Schema for specific project or + default studio schemas. + + Returns: + dict[str, Any]: Schema of studio/project settings. + + """ + args = tuple() + if project_name: + args = (project_name, ) + + endpoint = self.get_addon_endpoint( + addon_name, addon_version, "schema", *args + ) + result = self.get(endpoint) + result.raise_for_status() + return result.data + + def get_addon_site_settings_schema( + self, addon_name: str, addon_version: str + ) -> dict[str, Any]: + """Site settings schema of an addon. + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + + Returns: + dict[str, Any]: Schema of site settings. + + """ + result = self.get( + f"addons/{addon_name}/{addon_version}/siteSettings/schema" + ) + result.raise_for_status() + return result.data + + def get_addon_studio_settings( + self, + addon_name: str, + addon_version: str, + variant: Optional[str] = None, + ) -> dict[str, Any]: + """Addon studio settings. + + Receive studio settings for specific version of an addon. + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + + Returns: + dict[str, Any]: Addon settings. + + """ + if variant is None: + variant = self.get_default_settings_variant() + + query = prepare_query_string({"variant": variant or None}) + + result = self.get( + f"addons/{addon_name}/{addon_version}/settings{query}" + ) + result.raise_for_status() + return result.data + + def get_addon_project_settings( + self, + addon_name: str, + addon_version: str, + project_name: str, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True + ) -> dict[str, Any]: + """Addon project settings. + + Receive project settings for specific version of an addon. The settings + may be with site overrides when enabled. + + Site id is filled with current connection site id if not passed. To + make sure any site id is used set 'use_site' to 'False'. + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + project_name (str): Name of project for which the settings are + received. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Name of site which is used for site + overrides. Is filled with connection 'site_id' attribute + if not passed. + use_site (Optional[bool]): To force disable option of using site + overrides set to 'False'. In that case won't be applied + any site overrides. + + Returns: + dict[str, Any]: Addon settings. + + """ + if not use_site: + site_id = None + elif not site_id: + site_id = self.get_site_id() + + if variant is None: + variant = self.get_default_settings_variant() + + query = prepare_query_string({ + "site": site_id or None, + "variant": variant or None, + }) + result = self.get( + f"addons/{addon_name}/{addon_version}" + f"/settings/{project_name}{query}" + ) + result.raise_for_status() + return result.data + + def get_addon_settings( + self, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True + ) -> dict[str, Any]: + """Receive addon settings. + + Receive addon settings based on project name value. Some arguments may + be ignored if 'project_name' is set to 'None'. + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + project_name (Optional[str]): Name of project for which the + settings are received. A studio settings values are received + if is 'None'. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Name of site which is used for site + overrides. Is filled with connection 'site_id' attribute + if not passed. + use_site (Optional[bool]): To force disable option of using + site overrides set to 'False'. In that case won't be applied + any site overrides. + + Returns: + dict[str, Any]: Addon settings. + + """ + if project_name is None: + return self.get_addon_studio_settings( + addon_name, addon_version, variant + ) + return self.get_addon_project_settings( + addon_name, addon_version, project_name, variant, site_id, use_site + ) + + def get_addon_site_settings( + self, + addon_name: str, + addon_version: str, + site_id: Optional[str] = None, + ) -> dict[str, Any]: + """Site settings of an addon. + + If site id is not available an empty dictionary is returned. + + Args: + addon_name (str): Name of addon. + addon_version (str): Version of addon. + site_id (Optional[str]): Name of site for which should be settings + returned. using 'site_id' attribute if not passed. + + Returns: + dict[str, Any]: Site settings. + + """ + if site_id is None: + site_id = self.get_site_id() + + if not site_id: + return {} + + query = prepare_query_string({"site": site_id}) + result = self.get( + f"addons/{addon_name}/{addon_version}/siteSettings{query}" + ) + result.raise_for_status() + return result.data + + def get_bundle_settings( + self, + bundle_name: Optional[str] = None, + project_name: Optional[str] = None, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True, + ) -> dict[str, Any]: + """Get complete set of settings for given data. + + If project is not passed then studio settings are returned. If variant + is not passed 'default_settings_variant' is used. If bundle name is + not passed then current production/staging bundle is used, based on + variant value. + + Output contains addon settings and site settings in single dictionary. + + Todos: + - test how it behaves if there is not any bundle. + - test how it behaves if there is not any production/staging + bundle. + + Example output:: + + { + "addons": [ + { + "name": "addon-name", + "version": "addon-version", + "settings": {...}, + "siteSettings": {...} + } + ] + } + + Returns: + dict[str, Any]: All settings for single bundle. + + """ + if not use_site: + site_id = None + elif not site_id: + site_id = self.get_site_id() + + query = prepare_query_string({ + "project_name": project_name or None, + "bundle_name": bundle_name or None, + "variant": variant or self.get_default_settings_variant() or None, + "site_id": site_id, + }) + response = self.get(f"settings{query}") + response.raise_for_status() + return response.data + + def get_addons_studio_settings( + self, + bundle_name: Optional[str] = None, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True, + only_values: bool = True, + ) -> dict[str, Any]: + """All addons settings in one bulk. + + Warnings: + Behavior of this function changed with AYON server version 0.3.0. + Structure of output from server changed. If using + 'only_values=True' then output should be same as before. + + Args: + bundle_name (Optional[str]): Name of bundle for which should be + settings received. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Site id for which want to receive + site overrides. + use_site (bool): To force disable option of using site overrides + set to 'False'. In that case won't be applied any site + overrides. + only_values (Optional[bool]): Output will contain only settings + values without metadata about addons. + + Returns: + dict[str, Any]: Settings of all addons on server. + + """ + output = self.get_bundle_settings( + bundle_name=bundle_name, + variant=variant, + site_id=site_id, + use_site=use_site + ) + if only_values: + output = { + addon["name"]: addon["settings"] + for addon in output["addons"] + } + return output + + def get_addons_project_settings( + self, + project_name: str, + bundle_name: Optional[str] = None, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True, + only_values: bool = True, + ) -> dict[str, Any]: + """Project settings of all addons. + + Server returns information about used addon versions, so full output + looks like: + + ```json + { + "settings": {...}, + "addons": {...} + } + ``` + + The output can be limited to only values. To do so is 'only_values' + argument which is by default set to 'True'. In that case output + contains only value of 'settings' key. + + Warnings: + Behavior of this function changed with AYON server version 0.3.0. + Structure of output from server changed. If using + 'only_values=True' then output should be same as before. + + Args: + project_name (str): Name of project for which are settings + received. + bundle_name (Optional[str]): Name of bundle for which should be + settings received. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Site id for which want to receive + site overrides. + use_site (bool): To force disable option of using site overrides + set to 'False'. In that case won't be applied any site + overrides. + only_values (Optional[bool]): Output will contain only settings + values without metadata about addons. + + Returns: + dict[str, Any]: Settings of all addons on server for passed + project. + + """ + if not project_name: + raise ValueError("Project name must be passed.") + + output = self.get_bundle_settings( + project_name=project_name, + bundle_name=bundle_name, + variant=variant, + site_id=site_id, + use_site=use_site + ) + if only_values: + output = { + addon["name"]: addon["settings"] + for addon in output["addons"] + } + return output + + def get_addons_settings( + self, + bundle_name: Optional[str] = None, + project_name: Optional[str] = None, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True, + only_values: bool = True, + ) -> dict[str, Any]: + """Universal function to receive all addon settings. + + Based on 'project_name' will receive studio settings or project + settings. In case project is not passed is 'site_id' ignored. + + Warnings: + Behavior of this function changed with AYON server version 0.3.0. + Structure of output from server changed. If using + 'only_values=True' then output should be same as before. + + Args: + bundle_name (Optional[str]): Name of bundle for which should be + settings received. + project_name (Optional[str]): Name of project for which should be + settings received. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Id of site for which want to receive + site overrides. + use_site (Optional[bool]): To force disable option of using site + overrides set to 'False'. In that case won't be applied + any site overrides. + only_values (Optional[bool]): Only settings values will be + returned. By default, is set to 'True'. + + """ + if project_name is None: + return self.get_addons_studio_settings( + bundle_name=bundle_name, + variant=variant, + site_id=site_id, + use_site=use_site, + only_values=only_values + ) + + return self.get_addons_project_settings( + project_name=project_name, + bundle_name=bundle_name, + variant=variant, + site_id=site_id, + use_site=use_site, + only_values=only_values + ) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 736630cd6..fae685682 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -93,8 +93,6 @@ AttributesSchemaDict, InstallersInfoDict, DependencyPackagesDict, - DevBundleAddonInfoDict, - BundlesInfoDict, AnatomyPresetDict, SecretDict, @@ -2357,238 +2355,6 @@ def upload_dependency_package( route = self._get_dependency_package_route(dst_filename) self.upload_file(route, src_filepath, progress=progress) - def get_bundles(self) -> "BundlesInfoDict": - """Server bundles with basic information. - - This is 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("bundles") - response.raise_for_status() - return response.data - - def create_bundle( - self, - name: str, - addon_versions: dict[str, str], - installer_version: str, - dependency_packages: Optional[dict[str, str]] = None, - is_production: Optional[bool] = None, - is_staging: Optional[bool] = None, - is_dev: Optional[bool] = None, - dev_active_user: Optional[str] = None, - dev_addons_config: Optional[ - dict[str, "DevBundleAddonInfoDict"]] = None, - ): - """Create bundle on server. - - Bundle cannot be changed once is created. Only isProduction, isStaging - and dependency packages can change after creation. In case dev bundle - is created, it is possible to change anything, but it is not possible - to mark bundle as dev and production or staging at the same time. - - Development addon config can define custom path to client code. It is - used only for dev bundles. - - Example of 'dev_addons_config':: - - ```json - { - "core": { - "enabled": true, - "path": "/path/to/ayon-core/client" - } - } - ``` - - 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. - is_dev (Optional[bool]): Bundle will be marked as dev. - dev_active_user (Optional[str]): Username that will be assigned - to dev bundle. Can be used only if 'is_dev' is set to 'True'. - dev_addons_config (Optional[dict[str, Any]]): Configuration for - dev addons. Can be used only if 'is_dev' is set to 'True'. - - """ - body = { - "name": name, - "installerVersion": installer_version, - "addons": addon_versions, - } - - for key, value in ( - ("dependencyPackages", dependency_packages), - ("isProduction", is_production), - ("isStaging", is_staging), - ("isDev", is_dev), - ("activeUser", dev_active_user), - ("addonDevelopment", dev_addons_config), - ): - if value is not None: - body[key] = value - - response = self.post("bundles", **body) - response.raise_for_status() - - def update_bundle( - self, - bundle_name: str, - addon_versions: Optional[dict[str, str]] = None, - installer_version: Optional[str] = None, - dependency_packages: Optional[dict[str, str]] = None, - is_production: Optional[bool] = None, - is_staging: Optional[bool] = None, - is_dev: Optional[bool] = None, - dev_active_user: Optional[str] = None, - dev_addons_config: Optional[ - dict[str, "DevBundleAddonInfoDict"]] = 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. - addon_versions (Optional[dict[str, str]]): Addon versions, - possible only for dev bundles. - installer_version (Optional[str]): Installer version, possible - only for dev bundles. - 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. - is_dev (Optional[bool]): Bundle will be marked as dev. - dev_active_user (Optional[str]): Username that will be assigned - to dev bundle. Can be used only for dev bundles. - dev_addons_config (Optional[dict[str, Any]]): Configuration for - dev addons. Can be used only for dev bundles. - - """ - body = { - key: value - for key, value in ( - ("installerVersion", installer_version), - ("addons", addon_versions), - ("dependencyPackages", dependency_packages), - ("isProduction", is_production), - ("isStaging", is_staging), - ("isDev", is_dev), - ("activeUser", dev_active_user), - ("addonDevelopment", dev_addons_config), - ) - if value is not None - } - - response = self.patch( - f"bundles/{bundle_name}", - **body - ) - response.raise_for_status() - - def check_bundle_compatibility( - self, - name: str, - addon_versions: dict[str, str], - installer_version: str, - dependency_packages: Optional[dict[str, str]] = None, - is_production: Optional[bool] = None, - is_staging: Optional[bool] = None, - is_dev: Optional[bool] = None, - dev_active_user: Optional[str] = None, - dev_addons_config: Optional[ - dict[str, "DevBundleAddonInfoDict"]] = None, - ) -> dict[str, Any]: - """Check bundle compatibility. - - Can be used as per-flight validation before creating bundle. - - 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. - is_dev (Optional[bool]): Bundle will be marked as dev. - dev_active_user (Optional[str]): Username that will be assigned - to dev bundle. Can be used only if 'is_dev' is set to 'True'. - dev_addons_config (Optional[dict[str, Any]]): Configuration for - dev addons. Can be used only if 'is_dev' is set to 'True'. - - Returns: - dict[str, Any]: Server response, with 'success' and 'issues'. - - """ - body = { - "name": name, - "installerVersion": installer_version, - "addons": addon_versions, - } - - for key, value in ( - ("dependencyPackages", dependency_packages), - ("isProduction", is_production), - ("isStaging", is_staging), - ("isDev", is_dev), - ("activeUser", dev_active_user), - ("addonDevelopment", dev_addons_config), - ): - if value is not None: - body[key] = value - - response = self.post("bundles/check", **body) - response.raise_for_status() - return response.data - - def delete_bundle(self, bundle_name: str): - """Delete bundle from server. - - Args: - bundle_name (str): Name of bundle to delete. - - """ - response = self.delete(f"bundles/{bundle_name}") - response.raise_for_status() - # Anatomy presets def get_project_anatomy_presets(self) -> list["AnatomyPresetDict"]: """Anatomy presets available on server. @@ -2878,435 +2644,6 @@ def get_project_roots_by_platform( project_name, platform_name=platform_name ) - def get_addon_settings_schema( - self, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None - ) -> dict[str, Any]: - """Sudio/Project settings schema of an addon. - - Project schema may look differently as some enums are based on project - values. - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - project_name (Optional[str]): Schema for specific project or - default studio schemas. - - Returns: - dict[str, Any]: Schema of studio/project settings. - - """ - args = tuple() - if project_name: - args = (project_name, ) - - endpoint = self.get_addon_endpoint( - addon_name, addon_version, "schema", *args - ) - result = self.get(endpoint) - result.raise_for_status() - return result.data - - def get_addon_site_settings_schema( - self, addon_name: str, addon_version: str - ) -> dict[str, Any]: - """Site settings schema of an addon. - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - - Returns: - dict[str, Any]: Schema of site settings. - - """ - result = self.get( - f"addons/{addon_name}/{addon_version}/siteSettings/schema" - ) - result.raise_for_status() - return result.data - - def get_addon_studio_settings( - self, - addon_name: str, - addon_version: str, - variant: Optional[str] = None, - ) -> dict[str, Any]: - """Addon studio settings. - - Receive studio settings for specific version of an addon. - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - - Returns: - dict[str, Any]: Addon settings. - - """ - if variant is None: - variant = self.default_settings_variant - - query = prepare_query_string({"variant": variant or None}) - - result = self.get( - f"addons/{addon_name}/{addon_version}/settings{query}" - ) - result.raise_for_status() - return result.data - - def get_addon_project_settings( - self, - addon_name: str, - addon_version: str, - project_name: str, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True - ) -> dict[str, Any]: - """Addon project settings. - - Receive project settings for specific version of an addon. The settings - may be with site overrides when enabled. - - Site id is filled with current connection site id if not passed. To - make sure any site id is used set 'use_site' to 'False'. - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - project_name (str): Name of project for which the settings are - received. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Name of site which is used for site - overrides. Is filled with connection 'site_id' attribute - if not passed. - use_site (Optional[bool]): To force disable option of using site - overrides set to 'False'. In that case won't be applied - any site overrides. - - Returns: - dict[str, Any]: Addon settings. - - """ - if not use_site: - site_id = None - elif not site_id: - site_id = self.site_id - - if variant is None: - variant = self.default_settings_variant - - query = prepare_query_string({ - "site": site_id or None, - "variant": variant or None, - }) - result = self.get( - f"addons/{addon_name}/{addon_version}" - f"/settings/{project_name}{query}" - ) - result.raise_for_status() - return result.data - - def get_addon_settings( - self, - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True - ) -> dict[str, Any]: - """Receive addon settings. - - Receive addon settings based on project name value. Some arguments may - be ignored if 'project_name' is set to 'None'. - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - project_name (Optional[str]): Name of project for which the - settings are received. A studio settings values are received - if is 'None'. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Name of site which is used for site - overrides. Is filled with connection 'site_id' attribute - if not passed. - use_site (Optional[bool]): To force disable option of using - site overrides set to 'False'. In that case won't be applied - any site overrides. - - Returns: - dict[str, Any]: Addon settings. - - """ - if project_name is None: - return self.get_addon_studio_settings( - addon_name, addon_version, variant - ) - return self.get_addon_project_settings( - addon_name, addon_version, project_name, variant, site_id, use_site - ) - - def get_addon_site_settings( - self, - addon_name: str, - addon_version: str, - site_id: Optional[str] = None, - ) -> dict[str, Any]: - """Site settings of an addon. - - If site id is not available an empty dictionary is returned. - - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - site_id (Optional[str]): Name of site for which should be settings - returned. using 'site_id' attribute if not passed. - - Returns: - dict[str, Any]: Site settings. - - """ - if site_id is None: - site_id = self.site_id - - if not site_id: - return {} - - query = prepare_query_string({"site": site_id}) - result = self.get( - f"addons/{addon_name}/{addon_version}/siteSettings{query}" - ) - result.raise_for_status() - return result.data - - def get_bundle_settings( - self, - bundle_name: Optional[str] = None, - project_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, - ) -> dict[str, Any]: - """Get complete set of settings for given data. - - If project is not passed then studio settings are returned. If variant - is not passed 'default_settings_variant' is used. If bundle name is - not passed then current production/staging bundle is used, based on - variant value. - - Output contains addon settings and site settings in single dictionary. - - Todos: - - test how it behaves if there is not any bundle. - - test how it behaves if there is not any production/staging - bundle. - - Example output:: - - { - "addons": [ - { - "name": "addon-name", - "version": "addon-version", - "settings": {...}, - "siteSettings": {...} - } - ] - } - - Returns: - dict[str, Any]: All settings for single bundle. - - """ - if not use_site: - site_id = None - elif not site_id: - site_id = self.site_id - - query = prepare_query_string({ - "project_name": project_name or None, - "bundle_name": bundle_name or None, - "variant": variant or self.default_settings_variant or None, - "site_id": site_id, - }) - response = self.get(f"settings{query}") - response.raise_for_status() - return response.data - - def get_addons_studio_settings( - self, - bundle_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, - only_values: bool = True, - ) -> dict[str, Any]: - """All addons settings in one bulk. - - Warnings: - Behavior of this function changed with AYON server version 0.3.0. - Structure of output from server changed. If using - 'only_values=True' then output should be same as before. - - Args: - bundle_name (Optional[str]): Name of bundle for which should be - settings received. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Site id for which want to receive - site overrides. - use_site (bool): To force disable option of using site overrides - set to 'False'. In that case won't be applied any site - overrides. - only_values (Optional[bool]): Output will contain only settings - values without metadata about addons. - - Returns: - dict[str, Any]: Settings of all addons on server. - - """ - output = self.get_bundle_settings( - bundle_name=bundle_name, - variant=variant, - site_id=site_id, - use_site=use_site - ) - if only_values: - output = { - addon["name"]: addon["settings"] - for addon in output["addons"] - } - return output - - def get_addons_project_settings( - self, - project_name: str, - bundle_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, - only_values: bool = True, - ) -> dict[str, Any]: - """Project settings of all addons. - - Server returns information about used addon versions, so full output - looks like: - - ```json - { - "settings": {...}, - "addons": {...} - } - ``` - - The output can be limited to only values. To do so is 'only_values' - argument which is by default set to 'True'. In that case output - contains only value of 'settings' key. - - Warnings: - Behavior of this function changed with AYON server version 0.3.0. - Structure of output from server changed. If using - 'only_values=True' then output should be same as before. - - Args: - project_name (str): Name of project for which are settings - received. - bundle_name (Optional[str]): Name of bundle for which should be - settings received. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Site id for which want to receive - site overrides. - use_site (bool): To force disable option of using site overrides - set to 'False'. In that case won't be applied any site - overrides. - only_values (Optional[bool]): Output will contain only settings - values without metadata about addons. - - Returns: - dict[str, Any]: Settings of all addons on server for passed - project. - - """ - if not project_name: - raise ValueError("Project name must be passed.") - - output = self.get_bundle_settings( - project_name=project_name, - bundle_name=bundle_name, - variant=variant, - site_id=site_id, - use_site=use_site - ) - if only_values: - output = { - addon["name"]: addon["settings"] - for addon in output["addons"] - } - return output - - def get_addons_settings( - self, - bundle_name: Optional[str] = None, - project_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, - only_values: bool = True, - ) -> dict[str, Any]: - """Universal function to receive all addon settings. - - Based on 'project_name' will receive studio settings or project - settings. In case project is not passed is 'site_id' ignored. - - Warnings: - Behavior of this function changed with AYON server version 0.3.0. - Structure of output from server changed. If using - 'only_values=True' then output should be same as before. - - Args: - bundle_name (Optional[str]): Name of bundle for which should be - settings received. - project_name (Optional[str]): Name of project for which should be - settings received. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Id of site for which want to receive - site overrides. - use_site (Optional[bool]): To force disable option of using site - overrides set to 'False'. In that case won't be applied - any site overrides. - only_values (Optional[bool]): Only settings values will be - returned. By default, is set to 'True'. - - """ - if project_name is None: - return self.get_addons_studio_settings( - bundle_name=bundle_name, - variant=variant, - site_id=site_id, - use_site=use_site, - only_values=only_values - ) - - return self.get_addons_project_settings( - project_name=project_name, - bundle_name=bundle_name, - variant=variant, - site_id=site_id, - use_site=use_site, - only_values=only_values - ) - def get_secrets(self) -> list["SecretDict"]: """Get all secrets. From de778903c83ec13124cbf30f6a4ec599f8c0c41f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:23:32 +0200 Subject: [PATCH 33/53] rename the class --- automated_api.py | 4 ++-- ayon_api/_addons.py | 2 +- ayon_api/server_api.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/automated_api.py b/automated_api.py index 41754e412..3dbec8aa5 100644 --- a/automated_api.py +++ b/automated_api.py @@ -337,7 +337,7 @@ def prepare_api_functions(api_globals): ServerAPI, _ActionsAPI, _ActivitiesAPI, - _AddonsAPI, + _BundlesAddonsAPI, _EventsAPI, _FoldersAPI, _TasksAPI, @@ -355,7 +355,7 @@ def prepare_api_functions(api_globals): _items = list(ServerAPI.__dict__.items()) _items.extend(_ActionsAPI.__dict__.items()) _items.extend(_ActivitiesAPI.__dict__.items()) - _items.extend(_AddonsAPI.__dict__.items()) + _items.extend(_BundlesAddonsAPI.__dict__.items()) _items.extend(_EventsAPI.__dict__.items()) _items.extend(_LinksAPI.__dict__.items()) _items.extend(_ListsAPI.__dict__.items()) diff --git a/ayon_api/_addons.py b/ayon_api/_addons.py index e09bf2055..1b3dea11b 100644 --- a/ayon_api/_addons.py +++ b/ayon_api/_addons.py @@ -17,7 +17,7 @@ ) -class _AddonsAPI(_BaseServerAPI): +class _BundlesAddonsAPI(_BaseServerAPI): def get_bundles(self) -> "BundlesInfoDict": """Server bundles with basic information. diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index fae685682..3c70302fe 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -69,7 +69,7 @@ ) from ._actions import _ActionsAPI from ._activities import _ActivitiesAPI -from ._addons import _AddonsAPI +from ._addons import _BundlesAddonsAPI from ._events import _EventsAPI from ._links import _LinksAPI from ._lists import _ListsAPI @@ -216,7 +216,7 @@ def as_user(self, username): class ServerAPI( _ActionsAPI, _ActivitiesAPI, - _AddonsAPI, + _BundlesAddonsAPI, _EventsAPI, _ProjectsAPI, _FoldersAPI, From 6ba7388cad816eb8fe8df933d7a24fca861d264c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 10:52:18 +0200 Subject: [PATCH 34/53] rename file --- ayon_api/{_addons.py => _bundles_addons.py} | 0 ayon_api/server_api.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename ayon_api/{_addons.py => _bundles_addons.py} (100%) diff --git a/ayon_api/_addons.py b/ayon_api/_bundles_addons.py similarity index 100% rename from ayon_api/_addons.py rename to ayon_api/_bundles_addons.py diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 3c70302fe..448ba1150 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -69,7 +69,7 @@ ) from ._actions import _ActionsAPI from ._activities import _ActivitiesAPI -from ._addons import _BundlesAddonsAPI +from ._bundles_addons import _BundlesAddonsAPI from ._events import _EventsAPI from ._links import _LinksAPI from ._lists import _ListsAPI From 74c65d3a464ff002604907d1045804b05a7c19d9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:04:05 +0200 Subject: [PATCH 35/53] move apis to subfolder --- automated_api.py | 58 +++++++++---------- ayon_api/_api_helpers/__init__.py | 34 +++++++++++ .../{_actions.py => _api_helpers/actions.py} | 8 +-- .../activities.py} | 11 ++-- ayon_api/{_base.py => _api_helpers/base.py} | 6 +- .../bundles_addons.py} | 9 +-- .../{_events.py => _api_helpers/events.py} | 12 ++-- .../{_folders.py => _api_helpers/folders.py} | 13 +++-- ayon_api/{_links.py => _api_helpers/links.py} | 9 +-- ayon_api/{_lists.py => _api_helpers/lists.py} | 11 ++-- .../products.py} | 11 ++-- .../projects.py} | 13 +++-- .../representations.py} | 13 +++-- ayon_api/{_tasks.py => _api_helpers/tasks.py} | 11 ++-- .../thumbnails.py} | 7 ++- .../versions.py} | 13 +++-- .../workfiles.py} | 9 +-- 17 files changed, 148 insertions(+), 100 deletions(-) create mode 100644 ayon_api/_api_helpers/__init__.py rename ayon_api/{_actions.py => _api_helpers/actions.py} (98%) rename ayon_api/{_activities.py => _api_helpers/activities.py} (98%) rename ayon_api/{_base.py => _api_helpers/base.py} (96%) rename ayon_api/{_bundles_addons.py => _api_helpers/bundles_addons.py} (99%) rename ayon_api/{_events.py => _api_helpers/events.py} (98%) rename ayon_api/{_folders.py => _api_helpers/folders.py} (98%) rename ayon_api/{_links.py => _api_helpers/links.py} (99%) rename ayon_api/{_lists.py => _api_helpers/lists.py} (98%) rename ayon_api/{_products.py => _api_helpers/products.py} (98%) rename ayon_api/{_projects.py => _api_helpers/projects.py} (98%) rename ayon_api/{_representations.py => _api_helpers/representations.py} (99%) rename ayon_api/{_tasks.py => _api_helpers/tasks.py} (99%) rename ayon_api/{_thumbnails.py => _api_helpers/thumbnails.py} (99%) rename ayon_api/{_versions.py => _api_helpers/versions.py} (98%) rename ayon_api/{_workfiles.py => _api_helpers/workfiles.py} (96%) diff --git a/automated_api.py b/automated_api.py index 3dbec8aa5..d6a24b95f 100644 --- a/automated_api.py +++ b/automated_api.py @@ -235,7 +235,7 @@ def _add_typehint(param_name, param, api_globals): def _kw_default_to_str(param_name, param, api_globals): - from ayon_api._base import _PLACEHOLDER + from ayon_api._api_helpers.base import _PLACEHOLDER from ayon_api.utils import NOT_SET if param.default is inspect.Parameter.empty: @@ -335,38 +335,38 @@ def sig_params_to_str(sig, param_names, api_globals, indent=0): def prepare_api_functions(api_globals): from ayon_api.server_api import ( # noqa: E402 ServerAPI, - _ActionsAPI, - _ActivitiesAPI, - _BundlesAddonsAPI, - _EventsAPI, - _FoldersAPI, - _TasksAPI, - _ProductsAPI, - _VersionsAPI, - _LinksAPI, - _ListsAPI, - _ProjectsAPI, - _ThumbnailsAPI, - _WorkfilesAPI, - _RepresentationsAPI, + ActionsAPI, + ActivitiesAPI, + BundlesAddonsAPI, + EventsAPI, + FoldersAPI, + TasksAPI, + ProductsAPI, + VersionsAPI, + LinksAPI, + ListsAPI, + ProjectsAPI, + ThumbnailsAPI, + WorkfilesAPI, + RepresentationsAPI, ) functions = [] _items = list(ServerAPI.__dict__.items()) - _items.extend(_ActionsAPI.__dict__.items()) - _items.extend(_ActivitiesAPI.__dict__.items()) - _items.extend(_BundlesAddonsAPI.__dict__.items()) - _items.extend(_EventsAPI.__dict__.items()) - _items.extend(_LinksAPI.__dict__.items()) - _items.extend(_ListsAPI.__dict__.items()) - _items.extend(_ProjectsAPI.__dict__.items()) - _items.extend(_FoldersAPI.__dict__.items()) - _items.extend(_TasksAPI.__dict__.items()) - _items.extend(_ProductsAPI.__dict__.items()) - _items.extend(_VersionsAPI.__dict__.items()) - _items.extend(_ThumbnailsAPI.__dict__.items()) - _items.extend(_WorkfilesAPI.__dict__.items()) - _items.extend(_RepresentationsAPI.__dict__.items()) + _items.extend(ActionsAPI.__dict__.items()) + _items.extend(ActivitiesAPI.__dict__.items()) + _items.extend(BundlesAddonsAPI.__dict__.items()) + _items.extend(EventsAPI.__dict__.items()) + _items.extend(LinksAPI.__dict__.items()) + _items.extend(ListsAPI.__dict__.items()) + _items.extend(ProjectsAPI.__dict__.items()) + _items.extend(FoldersAPI.__dict__.items()) + _items.extend(TasksAPI.__dict__.items()) + _items.extend(ProductsAPI.__dict__.items()) + _items.extend(VersionsAPI.__dict__.items()) + _items.extend(ThumbnailsAPI.__dict__.items()) + _items.extend(WorkfilesAPI.__dict__.items()) + _items.extend(RepresentationsAPI.__dict__.items()) processed = set() for attr_name, attr in _items: diff --git a/ayon_api/_api_helpers/__init__.py b/ayon_api/_api_helpers/__init__.py new file mode 100644 index 000000000..538a416d6 --- /dev/null +++ b/ayon_api/_api_helpers/__init__.py @@ -0,0 +1,34 @@ +from .base import BaseServerAPI +from .actions import ActionsAPI +from .activities import ActivitiesAPI +from .bundles_addons import BundlesAddonsAPI +from .events import EventsAPI +from .folders import FoldersAPI +from .links import LinksAPI +from .lists import ListsAPI +from .products import ProductsAPI +from .projects import ProjectsAPI +from .representations import RepresentationsAPI +from .tasks import TasksAPI +from .thumbnails import ThumbnailsAPI +from .versions import VersionsAPI +from .workfiles import WorkfilesAPI + + +__all__ = ( + "BaseServerAPI", + "ActionsAPI", + "ActivitiesAPI", + "BundlesAddonsAPI", + "EventsAPI", + "FoldersAPI", + "LinksAPI", + "ListsAPI", + "ProductsAPI", + "ProjectsAPI", + "RepresentationsAPI", + "TasksAPI", + "ThumbnailsAPI", + "VersionsAPI", + "WorkfilesAPI", +) diff --git a/ayon_api/_actions.py b/ayon_api/_api_helpers/actions.py similarity index 98% rename from ayon_api/_actions.py rename to ayon_api/_api_helpers/actions.py index b575189dd..fcac238c4 100644 --- a/ayon_api/_actions.py +++ b/ayon_api/_api_helpers/actions.py @@ -1,11 +1,11 @@ import typing from typing import Optional, Dict, List, Any -from .utils import prepare_query_string -from ._base import _BaseServerAPI +from ayon_api.utils import prepare_query_string +from .base import BaseServerAPI if typing.TYPE_CHECKING: - from .typing import ( + from ayon_api.typing import ( ActionEntityTypes, ActionManifestDict, ActionTriggerResponse, @@ -15,7 +15,7 @@ ) -class _ActionsAPI(_BaseServerAPI): +class ActionsAPI(BaseServerAPI): """Implementation of actions API for ServerAPI.""" def get_actions( self, diff --git a/ayon_api/_activities.py b/ayon_api/_api_helpers/activities.py similarity index 98% rename from ayon_api/_activities.py rename to ayon_api/_api_helpers/activities.py index f9d7796eb..18ed4f411 100644 --- a/ayon_api/_activities.py +++ b/ayon_api/_api_helpers/activities.py @@ -2,21 +2,22 @@ import typing from typing import Optional, Iterable, Generator, Any -from ._base import _BaseServerAPI -from .utils import ( +from ayon_api.utils import ( SortOrder, prepare_list_filters, ) -from .graphql_queries import activities_graphql_query +from ayon_api.graphql_queries import activities_graphql_query + +from .base import BaseServerAPI if typing.TYPE_CHECKING: - from .typing import ( + from ayon_api.typing import ( ActivityType, ActivityReferenceType, ) -class _ActivitiesAPI(_BaseServerAPI): +class ActivitiesAPI(BaseServerAPI): def get_activities( self, project_name: str, diff --git a/ayon_api/_base.py b/ayon_api/_api_helpers/base.py similarity index 96% rename from ayon_api/_base.py rename to ayon_api/_api_helpers/base.py index 29e193e2d..f785080ab 100644 --- a/ayon_api/_base.py +++ b/ayon_api/_api_helpers/base.py @@ -5,10 +5,10 @@ import requests -from .utils import TransferProgress, RequestType +from ayon_api.utils import TransferProgress, RequestType if typing.TYPE_CHECKING: - from .typing import ( + from ayon_api.typing import ( AnyEntityDict, ServerVersion, ProjectDict, @@ -17,7 +17,7 @@ _PLACEHOLDER = object() -class _BaseServerAPI: +class BaseServerAPI: def get_server_version(self) -> str: raise NotImplementedError() diff --git a/ayon_api/_bundles_addons.py b/ayon_api/_api_helpers/bundles_addons.py similarity index 99% rename from ayon_api/_bundles_addons.py rename to ayon_api/_api_helpers/bundles_addons.py index 1b3dea11b..1b465f0bc 100644 --- a/ayon_api/_bundles_addons.py +++ b/ayon_api/_api_helpers/bundles_addons.py @@ -2,22 +2,23 @@ import typing from typing import Optional, Any -from .utils import ( +from ayon_api.utils import ( RequestTypes, prepare_query_string, TransferProgress, ) -from ._base import _BaseServerAPI + +from .base import BaseServerAPI if typing.TYPE_CHECKING: - from .typing import ( + from ayon_api.typing import ( AddonsInfoDict, BundlesInfoDict, DevBundleAddonInfoDict, ) -class _BundlesAddonsAPI(_BaseServerAPI): +class BundlesAddonsAPI(BaseServerAPI): def get_bundles(self) -> "BundlesInfoDict": """Server bundles with basic information. diff --git a/ayon_api/_events.py b/ayon_api/_api_helpers/events.py similarity index 98% rename from ayon_api/_events.py rename to ayon_api/_api_helpers/events.py index d91aebb07..1c4e0e1cd 100644 --- a/ayon_api/_events.py +++ b/ayon_api/_api_helpers/events.py @@ -2,16 +2,18 @@ import typing from typing import Optional, Any, Iterable, Generator -from ._base import _BaseServerAPI -from .utils import SortOrder, prepare_list_filters -from .graphql_queries import events_graphql_query +from ayon_api.utils import SortOrder, prepare_list_filters +from ayon_api.graphql_queries import events_graphql_query + +from .base import BaseServerAPI if typing.TYPE_CHECKING: from typing import Union - from .typing import EventFilter + + from ayon_api.typing import EventFilter -class _EventsAPI(_BaseServerAPI): +class EventsAPI(BaseServerAPI): def get_event(self, event_id: str) -> Optional[dict[str, Any]]: """Query full event data by id. diff --git a/ayon_api/_folders.py b/ayon_api/_api_helpers/folders.py similarity index 98% rename from ayon_api/_folders.py rename to ayon_api/_api_helpers/folders.py index 76181c8f9..e43e798c3 100644 --- a/ayon_api/_folders.py +++ b/ayon_api/_api_helpers/folders.py @@ -4,26 +4,27 @@ import typing from typing import Optional, Iterable, Generator, Any -from ._base import _BaseServerAPI -from .exceptions import UnsupportedServerVersion -from .utils import ( +from ayon_api.exceptions import UnsupportedServerVersion +from ayon_api.utils import ( prepare_query_string, prepare_list_filters, fill_own_attribs, create_entity_id, NOT_SET, ) -from .graphql_queries import folders_graphql_query +from ayon_api.graphql_queries import folders_graphql_query + +from .base import BaseServerAPI if typing.TYPE_CHECKING: - from .typing import ( + from ayon_api.typing import ( FolderDict, FlatFolderDict, ProjectHierarchyDict, ) -class _FoldersAPI(_BaseServerAPI): +class FoldersAPI(BaseServerAPI): def get_rest_folder( self, project_name: str, folder_id: str ) -> Optional["FolderDict"]: diff --git a/ayon_api/_links.py b/ayon_api/_api_helpers/links.py similarity index 99% rename from ayon_api/_links.py rename to ayon_api/_api_helpers/links.py index 4c7a82237..7e75db966 100644 --- a/ayon_api/_links.py +++ b/ayon_api/_api_helpers/links.py @@ -4,20 +4,21 @@ import typing from typing import Optional, Any, Iterable -from .graphql_queries import ( +from ayon_api.graphql_queries import ( folders_graphql_query, tasks_graphql_query, products_graphql_query, versions_graphql_query, representations_graphql_query, ) -from ._base import _BaseServerAPI + +from .base import BaseServerAPI if typing.TYPE_CHECKING: - from .typing import LinkDirection + from ayon_api.typing import LinkDirection -class _LinksAPI(_BaseServerAPI): +class LinksAPI(BaseServerAPI): def get_full_link_type_name( self, link_type_name: str, input_type: str, output_type: str ) -> str: diff --git a/ayon_api/_lists.py b/ayon_api/_api_helpers/lists.py similarity index 98% rename from ayon_api/_lists.py rename to ayon_api/_api_helpers/lists.py index 480c1e613..d52c99e7e 100644 --- a/ayon_api/_lists.py +++ b/ayon_api/_api_helpers/lists.py @@ -2,19 +2,20 @@ import typing from typing import Optional, Iterable, Any, Dict, List, Generator -from ._base import _BaseServerAPI -from .utils import create_entity_id -from .graphql_queries import entity_lists_graphql_query +from ayon_api.utils import create_entity_id +from ayon_api.graphql_queries import entity_lists_graphql_query + +from .base import BaseServerAPI if typing.TYPE_CHECKING: - from .typing import ( + from ayon_api.typing import ( EntityListEntityType, EntityListAttributeDefinitionDict, EntityListItemMode, ) -class _ListsAPI(_BaseServerAPI): +class ListsAPI(BaseServerAPI): def get_entity_lists( self, project_name: str, diff --git a/ayon_api/_products.py b/ayon_api/_api_helpers/products.py similarity index 98% rename from ayon_api/_products.py rename to ayon_api/_api_helpers/products.py index eff1384f5..5ce78f97c 100644 --- a/ayon_api/_products.py +++ b/ayon_api/_api_helpers/products.py @@ -5,21 +5,22 @@ import typing from typing import Optional, Iterable, Generator, Any -from ._base import _BaseServerAPI, _PLACEHOLDER -from .utils import ( +from ayon_api.utils import ( prepare_list_filters, create_entity_id, ) -from .graphql_queries import ( +from ayon_api.graphql_queries import ( products_graphql_query, product_types_query, ) +from .base import BaseServerAPI, _PLACEHOLDER + if typing.TYPE_CHECKING: - from .typing import ProductDict, ProductTypeDict + from ayon_api.typing import ProductDict, ProductTypeDict -class _ProductsAPI(_BaseServerAPI): +class ProductsAPI(BaseServerAPI): def get_rest_product( self, project_name: str, product_id: str ) -> Optional["ProductDict"]: diff --git a/ayon_api/_projects.py b/ayon_api/_api_helpers/projects.py similarity index 98% rename from ayon_api/_projects.py rename to ayon_api/_api_helpers/projects.py index 9edc5948f..eb895f265 100644 --- a/ayon_api/_projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -4,16 +4,17 @@ import typing from typing import Optional, Generator, Iterable, Any -from ._base import _BaseServerAPI -from .constants import PROJECT_NAME_REGEX -from .utils import prepare_query_string, fill_own_attribs -from .graphql_queries import projects_graphql_query +from ayon_api.constants import PROJECT_NAME_REGEX +from ayon_api.utils import prepare_query_string, fill_own_attribs +from ayon_api.graphql_queries import projects_graphql_query + +from .base import BaseServerAPI if typing.TYPE_CHECKING: - from .typing import ProjectDict + from ayon_api.typing import ProjectDict -class _ProjectsAPI(_BaseServerAPI): +class ProjectsAPI(BaseServerAPI): def get_rest_project( self, project_name: str ) -> Optional["ProjectDict"]: diff --git a/ayon_api/_representations.py b/ayon_api/_api_helpers/representations.py similarity index 99% rename from ayon_api/_representations.py rename to ayon_api/_api_helpers/representations.py index a1d7d2ad1..e46353d09 100644 --- a/ayon_api/_representations.py +++ b/ayon_api/_api_helpers/representations.py @@ -5,24 +5,25 @@ import typing from typing import Optional, Iterable, Generator, Any -from ._base import _BaseServerAPI, _PLACEHOLDER -from .constants import REPRESENTATION_FILES_FIELDS -from .utils import ( +from ayon_api.constants import REPRESENTATION_FILES_FIELDS +from ayon_api.utils import ( RepresentationHierarchy, RepresentationParents, PatternType, create_entity_id, ) -from .graphql_queries import ( +from ayon_api.graphql_queries import ( representations_graphql_query, representations_hierarchy_qraphql_query, ) +from .base import BaseServerAPI, _PLACEHOLDER + if typing.TYPE_CHECKING: - from .typing import RepresentationDict + from ayon_api.typing import RepresentationDict -class _RepresentationsAPI(_BaseServerAPI): +class RepresentationsAPI(BaseServerAPI): def get_rest_representation( self, project_name: str, representation_id: str ) -> Optional["RepresentationDict"]: diff --git a/ayon_api/_tasks.py b/ayon_api/_api_helpers/tasks.py similarity index 99% rename from ayon_api/_tasks.py rename to ayon_api/_api_helpers/tasks.py index ed5f22fec..0e825de01 100644 --- a/ayon_api/_tasks.py +++ b/ayon_api/_api_helpers/tasks.py @@ -3,23 +3,24 @@ import typing from typing import Optional, Iterable, Generator, Any -from ._base import _BaseServerAPI -from .utils import ( +from ayon_api.utils import ( prepare_list_filters, fill_own_attribs, create_entity_id, NOT_SET, ) -from .graphql_queries import ( +from ayon_api.graphql_queries import ( tasks_graphql_query, tasks_by_folder_paths_graphql_query, ) +from .base import BaseServerAPI + if typing.TYPE_CHECKING: - from .typing import TaskDict + from ayon_api.typing import TaskDict -class _TasksAPI(_BaseServerAPI): +class TasksAPI(BaseServerAPI): def get_rest_task( self, project_name: str, task_id: str ) -> Optional["TaskDict"]: diff --git a/ayon_api/_thumbnails.py b/ayon_api/_api_helpers/thumbnails.py similarity index 99% rename from ayon_api/_thumbnails.py rename to ayon_api/_api_helpers/thumbnails.py index 39fb86602..3265dd777 100644 --- a/ayon_api/_thumbnails.py +++ b/ayon_api/_api_helpers/thumbnails.py @@ -2,16 +2,17 @@ import warnings from typing import Optional -from ._base import _BaseServerAPI -from .utils import ( +from ayon_api.utils import ( get_media_mime_type, ThumbnailContent, RequestTypes, RestApiResponse, ) +from .base import BaseServerAPI -class _ThumbnailsAPI(_BaseServerAPI): + +class ThumbnailsAPI(BaseServerAPI): def get_thumbnail_by_id( self, project_name: str, thumbnail_id: str ) -> ThumbnailContent: diff --git a/ayon_api/_versions.py b/ayon_api/_api_helpers/versions.py similarity index 98% rename from ayon_api/_versions.py rename to ayon_api/_api_helpers/versions.py index e805505a7..1302ed84b 100644 --- a/ayon_api/_versions.py +++ b/ayon_api/_api_helpers/versions.py @@ -4,20 +4,21 @@ import typing from typing import Optional, Iterable, Generator, Any -from ._base import _BaseServerAPI, _PLACEHOLDER -from .utils import ( +from ayon_api.utils import ( NOT_SET, create_entity_id, prepare_list_filters, ) -from .graphql import GraphQlQuery -from .graphql_queries import versions_graphql_query +from ayon_api.graphql import GraphQlQuery +from ayon_api.graphql_queries import versions_graphql_query + +from .base import BaseServerAPI, _PLACEHOLDER if typing.TYPE_CHECKING: - from .typing import VersionDict + from ayon_api.typing import VersionDict -class _VersionsAPI(_BaseServerAPI): +class VersionsAPI(BaseServerAPI): def get_rest_version( self, project_name: str, version_id: str ) -> Optional["VersionDict"]: diff --git a/ayon_api/_workfiles.py b/ayon_api/_api_helpers/workfiles.py similarity index 96% rename from ayon_api/_workfiles.py rename to ayon_api/_api_helpers/workfiles.py index 2a624f607..8be0af6e3 100644 --- a/ayon_api/_workfiles.py +++ b/ayon_api/_api_helpers/workfiles.py @@ -2,14 +2,15 @@ import typing from typing import Optional, Iterable, Generator -from ._base import _BaseServerAPI, _PLACEHOLDER -from .graphql_queries import workfiles_info_graphql_query +from ayon_api.graphql_queries import workfiles_info_graphql_query + +from .base import BaseServerAPI, _PLACEHOLDER if typing.TYPE_CHECKING: - from .typing import WorkfileInfoDict + from ayon_api.typing import WorkfileInfoDict -class _WorkfilesAPI(_BaseServerAPI): +class WorkfilesAPI(BaseServerAPI): def get_workfiles_info( self, project_name: str, From af7b9091573e3b9fb9f69074455021bbce984099 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:04:19 +0200 Subject: [PATCH 36/53] use new imports --- ayon_api/server_api.py | 59 +++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 448ba1150..5eea9860e 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -67,21 +67,22 @@ get_machine_name, fill_own_attribs, ) -from ._actions import _ActionsAPI -from ._activities import _ActivitiesAPI -from ._bundles_addons import _BundlesAddonsAPI -from ._events import _EventsAPI -from ._links import _LinksAPI -from ._lists import _ListsAPI -from ._projects import _ProjectsAPI -from ._folders import _FoldersAPI -from ._tasks import _TasksAPI -from ._products import _ProductsAPI -from ._versions import _VersionsAPI -from ._thumbnails import _ThumbnailsAPI -from ._workfiles import _WorkfilesAPI -from ._representations import _RepresentationsAPI - +from ._api_helpers import ( + ActionsAPI, + ActivitiesAPI, + BundlesAddonsAPI, + EventsAPI, + LinksAPI, + ListsAPI, + ProjectsAPI, + FoldersAPI, + TasksAPI, + ProductsAPI, + VersionsAPI, + ThumbnailsAPI, + WorkfilesAPI, + RepresentationsAPI, +) if typing.TYPE_CHECKING: from typing import Union @@ -214,20 +215,20 @@ def as_user(self, username): class ServerAPI( - _ActionsAPI, - _ActivitiesAPI, - _BundlesAddonsAPI, - _EventsAPI, - _ProjectsAPI, - _FoldersAPI, - _TasksAPI, - _ProductsAPI, - _VersionsAPI, - _RepresentationsAPI, - _WorkfilesAPI, - _LinksAPI, - _ListsAPI, - _ThumbnailsAPI, + ActionsAPI, + ActivitiesAPI, + BundlesAddonsAPI, + EventsAPI, + ProjectsAPI, + FoldersAPI, + TasksAPI, + ProductsAPI, + VersionsAPI, + RepresentationsAPI, + WorkfilesAPI, + LinksAPI, + ListsAPI, + ThumbnailsAPI, ): """Base handler of connection to server. From 9fe3b1c6638534d1a6e3d4aa15f6e3b838b0762b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:09:23 +0200 Subject: [PATCH 37/53] future annotations --- ayon_api/_api_helpers/actions.py | 71 +++++++++++++------------ ayon_api/_api_helpers/activities.py | 2 + ayon_api/_api_helpers/bundles_addons.py | 2 + ayon_api/_api_helpers/events.py | 2 + ayon_api/_api_helpers/folders.py | 4 +- ayon_api/_api_helpers/lists.py | 62 ++++++++++----------- ayon_api/_api_helpers/thumbnails.py | 2 + ayon_api/_api_helpers/workfiles.py | 2 + 8 files changed, 81 insertions(+), 66 deletions(-) diff --git a/ayon_api/_api_helpers/actions.py b/ayon_api/_api_helpers/actions.py index fcac238c4..6cbe9b7bd 100644 --- a/ayon_api/_api_helpers/actions.py +++ b/ayon_api/_api_helpers/actions.py @@ -1,13 +1,16 @@ +from __future__ import annotations + import typing -from typing import Optional, Dict, List, Any +from typing import Optional, Any from ayon_api.utils import prepare_query_string + from .base import BaseServerAPI if typing.TYPE_CHECKING: from ayon_api.typing import ( ActionEntityTypes, - ActionManifestDict, + ActionManifestdict, ActionTriggerResponse, ActionTakeResponse, ActionConfigResponse, @@ -21,13 +24,13 @@ def get_actions( self, project_name: Optional[str] = None, entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, + entity_ids: Optional[list[str]] = None, + entity_subtypes: Optional[list[str]] = None, + form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, mode: Optional["ActionModeType"] = None, - ) -> List["ActionManifestDict"]: + ) -> list["ActionManifestdict"]: """Get actions for a context. Args: @@ -35,16 +38,16 @@ def get_actions( actions. entity_type (Optional[ActionEntityTypes]): Entity type where the action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the + entity_ids (Optional[list[str]]): list of entity ids where the action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes + entity_subtypes (Optional[list[str]]): list of entity subtypes folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. + form_data (Optional[dict[str, Any]]): Form data of the action. variant (Optional[str]): Settings variant. mode (Optional[ActionModeType]): Action modes. Returns: - List[ActionManifestDict]: List of action manifests. + list[ActionManifestdict]: list of action manifests. """ if variant is None: @@ -75,9 +78,9 @@ def trigger_action( addon_version: str, project_name: Optional[str] = None, entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, + entity_ids: Optional[list[str]] = None, + entity_subtypes: Optional[list[str]] = None, + form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, ) -> "ActionTriggerResponse": @@ -91,11 +94,11 @@ def trigger_action( actions. entity_type (Optional[ActionEntityTypes]): Entity type where the action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the + entity_ids (Optional[list[str]]): list of entity ids where the action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes + entity_subtypes (Optional[list[str]]): list of entity subtypes folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. + form_data (Optional[dict[str, Any]]): Form data of the action. variant (Optional[str]): Settings variant. """ @@ -132,9 +135,9 @@ def get_action_config( addon_version: str, project_name: Optional[str] = None, entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, + entity_ids: Optional[list[str]] = None, + entity_subtypes: Optional[list[str]] = None, + form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, ) -> "ActionConfigResponse": @@ -148,11 +151,11 @@ def get_action_config( actions. entity_type (Optional[ActionEntityTypes]): Entity type where the action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the + entity_ids (Optional[list[str]]): list of entity ids where the action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes + entity_subtypes (Optional[list[str]]): list of entity subtypes folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. + form_data (Optional[dict[str, Any]]): Form data of the action. variant (Optional[str]): Settings variant. Returns: @@ -177,12 +180,12 @@ def set_action_config( identifier: str, addon_name: str, addon_version: str, - value: Dict[str, Any], + value: dict[str, Any], project_name: Optional[str] = None, entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, + entity_ids: Optional[list[str]] = None, + entity_subtypes: Optional[list[str]] = None, + form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, ) -> "ActionConfigResponse": @@ -192,17 +195,17 @@ def set_action_config( identifier (str): Identifier of the action. addon_name (str): Name of the addon. addon_version (str): Version of the addon. - value (Optional[Dict[str, Any]]): Value of the action + value (Optional[dict[str, Any]]): Value of the action configuration. project_name (Optional[str]): Name of the project. None for global actions. entity_type (Optional[ActionEntityTypes]): Entity type where the action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the + entity_ids (Optional[list[str]]): list of entity ids where the action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes + entity_subtypes (Optional[list[str]]): list of entity subtypes folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. + form_data (Optional[dict[str, Any]]): Form data of the action. variant (Optional[str]): Settings variant. Returns: @@ -262,12 +265,12 @@ def _send_config_request( identifier: str, addon_name: str, addon_version: str, - value: Optional[Dict[str, Any]], + value: Optional[dict[str, Any]], project_name: Optional[str], entity_type: Optional["ActionEntityTypes"], - entity_ids: Optional[List[str]], - entity_subtypes: Optional[List[str]], - form_data: Optional[Dict[str, Any]], + entity_ids: Optional[list[str]], + entity_subtypes: Optional[list[str]], + form_data: Optional[dict[str, Any]], variant: Optional[str], ) -> "ActionConfigResponse": """Set and get action configuration.""" diff --git a/ayon_api/_api_helpers/activities.py b/ayon_api/_api_helpers/activities.py index 18ed4f411..c0c58406d 100644 --- a/ayon_api/_api_helpers/activities.py +++ b/ayon_api/_api_helpers/activities.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json import typing from typing import Optional, Iterable, Generator, Any diff --git a/ayon_api/_api_helpers/bundles_addons.py b/ayon_api/_api_helpers/bundles_addons.py index 1b465f0bc..dfdd3b591 100644 --- a/ayon_api/_api_helpers/bundles_addons.py +++ b/ayon_api/_api_helpers/bundles_addons.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import typing from typing import Optional, Any diff --git a/ayon_api/_api_helpers/events.py b/ayon_api/_api_helpers/events.py index 1c4e0e1cd..3c7d61273 100644 --- a/ayon_api/_api_helpers/events.py +++ b/ayon_api/_api_helpers/events.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import warnings import typing from typing import Optional, Any, Iterable, Generator diff --git a/ayon_api/_api_helpers/folders.py b/ayon_api/_api_helpers/folders.py index e43e798c3..7f6b3ca94 100644 --- a/ayon_api/_api_helpers/folders.py +++ b/ayon_api/_api_helpers/folders.py @@ -72,7 +72,7 @@ def get_rest_folders( in output. Slower to query. Returns: - List[FlatFolderDict]: List of folder entities. + list[FlatFolderDict]: List of folder entities. """ major, minor, patch, _, _ = self.get_server_version_tuple() @@ -187,7 +187,7 @@ def get_folders_rest( in output. Slower to query. Returns: - List[FlatFolderDict]: List of folder entities. + list[FlatFolderDict]: List of folder entities. """ warnings.warn( diff --git a/ayon_api/_api_helpers/lists.py b/ayon_api/_api_helpers/lists.py index d52c99e7e..f796723fd 100644 --- a/ayon_api/_api_helpers/lists.py +++ b/ayon_api/_api_helpers/lists.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import json import typing -from typing import Optional, Iterable, Any, Dict, List, Generator +from typing import Optional, Iterable, Any, Generator from ayon_api.utils import create_entity_id from ayon_api.graphql_queries import entity_lists_graphql_query @@ -23,7 +25,7 @@ def get_entity_lists( list_ids: Optional[Iterable[str]] = None, active: Optional[bool] = None, fields: Optional[Iterable[str]] = None, - ) -> Generator[Dict[str, Any], None, None]: + ) -> Generator[dict[str, Any], None, None]: """Fetch entity lists from server. Args: @@ -34,7 +36,7 @@ def get_entity_lists( fields (Optional[Iterable[str]]): Fields to fetch from server. Returns: - Generator[Dict[str, Any], None, None]: Entity list entities + Generator[dict[str, Any], None, None]: Entity list entities matching defined filters. """ @@ -45,7 +47,7 @@ def get_entity_lists( if active is not None: fields.add("active") - filters: Dict[str, Any] = {"projectName": project_name} + filters: dict[str, Any] = {"projectName": project_name} if list_ids is not None: if not list_ids: return @@ -70,7 +72,7 @@ def get_entity_lists( def get_entity_list_rest( self, project_name: str, list_id: str - ) -> Optional[Dict[str, Any]]: + ) -> Optional[dict[str, Any]]: """Get entity list by id using REST API. Args: @@ -78,7 +80,7 @@ def get_entity_list_rest( list_id (str): Entity list id. Returns: - Optional[Dict[str, Any]]: Entity list data or None if not found. + Optional[dict[str, Any]]: Entity list data or None if not found. """ response = self.get(f"projects/{project_name}/lists/{list_id}") @@ -90,7 +92,7 @@ def get_entity_list_by_id( project_name: str, list_id: str, fields: Optional[Iterable[str]] = None, - ) -> Optional[Dict[str, Any]]: + ) -> Optional[dict[str, Any]]: """Get entity list by id using GraphQl. Args: @@ -99,7 +101,7 @@ def get_entity_list_by_id( fields (Optional[Iterable[str]]): Fields to fetch from server. Returns: - Optional[Dict[str, Any]]: Entity list data or None if not found. + Optional[dict[str, Any]]: Entity list data or None if not found. """ for entity_list in self.get_entity_lists( @@ -115,14 +117,14 @@ def create_entity_list( label: str, *, list_type: Optional[str] = None, - access: Optional[Dict[str, Any]] = None, - attrib: Optional[List[Dict[str, Any]]] = None, - data: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[str]] = None, - template: Optional[Dict[str, Any]] = None, + access: Optional[dict[str, Any]] = None, + attrib: Optional[list[dict[str, Any]]] = None, + data: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[str]] = None, + template: Optional[dict[str, Any]] = None, owner: Optional[str] = None, active: Optional[bool] = None, - items: Optional[List[Dict[str, Any]]] = None, + items: Optional[list[dict[str, Any]]] = None, list_id: Optional[str] = None, ) -> str: """Create entity list. @@ -181,10 +183,10 @@ def update_entity_list( list_id: str, *, label: Optional[str] = None, - access: Optional[Dict[str, Any]] = None, - attrib: Optional[List[Dict[str, Any]]] = None, - data: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[str]] = None, + access: Optional[dict[str, Any]] = None, + attrib: Optional[list[dict[str, Any]]] = None, + data: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[str]] = None, owner: Optional[str] = None, active: Optional[bool] = None, ) -> None: @@ -235,7 +237,7 @@ def delete_entity_list(self, project_name: str, list_id: str) -> None: def get_entity_list_attribute_definitions( self, project_name: str, list_id: str - ) -> List["EntityListAttributeDefinitionDict"]: + ) -> list["EntityListAttributeDefinitionDict"]: """Get attribute definitioins on entity list. Args: @@ -243,7 +245,7 @@ def get_entity_list_attribute_definitions( list_id (str): Entity list id. Returns: - List[EntityListAttributeDefinitionDict]: List of attribute + list[EntityListAttributeDefinitionDict]: List of attribute definitions. """ @@ -257,14 +259,14 @@ def set_entity_list_attribute_definitions( self, project_name: str, list_id: str, - attribute_definitions: List["EntityListAttributeDefinitionDict"], + attribute_definitions: list["EntityListAttributeDefinitionDict"], ) -> None: """Set attribute definitioins on entity list. Args: project_name (str): Project name. list_id (str): Entity list id. - attribute_definitions (List[EntityListAttributeDefinitionDict]): + attribute_definitions (list[EntityListAttributeDefinitionDict]): List of attribute definitions. """ @@ -281,9 +283,9 @@ def create_entity_list_item( *, position: Optional[int] = None, label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, item_id: Optional[str] = None, ) -> str: """Create entity list item. @@ -329,7 +331,7 @@ def update_entity_list_items( self, project_name: str, list_id: str, - items: List[Dict[str, Any]], + items: list[dict[str, Any]], mode: "EntityListItemMode", ) -> None: """Update items in entity list. @@ -337,7 +339,7 @@ def update_entity_list_items( Args: project_name (str): Project name where entity list live. list_id (str): Entity list id. - items (List[Dict[str, Any]]): Entity list items. + items (list[dict[str, Any]]): Entity list items. mode (EntityListItemMode): Mode of items update. """ @@ -357,9 +359,9 @@ def update_entity_list_item( new_list_id: Optional[str], position: Optional[int] = None, label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, ) -> None: """Update item in entity list. diff --git a/ayon_api/_api_helpers/thumbnails.py b/ayon_api/_api_helpers/thumbnails.py index 3265dd777..4e2242f2e 100644 --- a/ayon_api/_api_helpers/thumbnails.py +++ b/ayon_api/_api_helpers/thumbnails.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import warnings from typing import Optional diff --git a/ayon_api/_api_helpers/workfiles.py b/ayon_api/_api_helpers/workfiles.py index 8be0af6e3..1be1c37a5 100644 --- a/ayon_api/_api_helpers/workfiles.py +++ b/ayon_api/_api_helpers/workfiles.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import warnings import typing from typing import Optional, Iterable, Generator From 1448ffba77817527159fb06127d908c26fd9e6b3 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:11:59 +0200 Subject: [PATCH 38/53] reorder imports --- ayon_api/_api_helpers/__init__.py | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ayon_api/_api_helpers/__init__.py b/ayon_api/_api_helpers/__init__.py index 538a416d6..e102d82d4 100644 --- a/ayon_api/_api_helpers/__init__.py +++ b/ayon_api/_api_helpers/__init__.py @@ -1,34 +1,34 @@ from .base import BaseServerAPI -from .actions import ActionsAPI -from .activities import ActivitiesAPI from .bundles_addons import BundlesAddonsAPI from .events import EventsAPI -from .folders import FoldersAPI -from .links import LinksAPI -from .lists import ListsAPI -from .products import ProductsAPI from .projects import ProjectsAPI -from .representations import RepresentationsAPI +from .folders import FoldersAPI from .tasks import TasksAPI -from .thumbnails import ThumbnailsAPI +from .products import ProductsAPI from .versions import VersionsAPI +from .representations import RepresentationsAPI from .workfiles import WorkfilesAPI +from .thumbnails import ThumbnailsAPI +from .activities import ActivitiesAPI +from .actions import ActionsAPI +from .links import LinksAPI +from .lists import ListsAPI __all__ = ( "BaseServerAPI", - "ActionsAPI", - "ActivitiesAPI", "BundlesAddonsAPI", "EventsAPI", - "FoldersAPI", - "LinksAPI", - "ListsAPI", - "ProductsAPI", "ProjectsAPI", - "RepresentationsAPI", + "FoldersAPI", "TasksAPI", - "ThumbnailsAPI", + "ProductsAPI", "VersionsAPI", + "RepresentationsAPI", "WorkfilesAPI", + "ThumbnailsAPI", + "ActivitiesAPI", + "ActionsAPI", + "LinksAPI", + "ListsAPI", ) From 32bbda92aaaa2689c419c24911fe9b0e1e2bb907 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:42:17 +0200 Subject: [PATCH 39/53] move anatomy and roots methods to project api --- ayon_api/_api_helpers/projects.py | 292 +++++++++++++++++++++++++++++- ayon_api/server_api.py | 291 ----------------------------- ayon_api/typing.py | 3 + 3 files changed, 294 insertions(+), 292 deletions(-) diff --git a/ayon_api/_api_helpers/projects.py b/ayon_api/_api_helpers/projects.py index eb895f265..f24a67f3c 100644 --- a/ayon_api/_api_helpers/projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -1,6 +1,8 @@ from __future__ import annotations import json +import platform +import warnings import typing from typing import Optional, Generator, Iterable, Any @@ -11,10 +13,99 @@ from .base import BaseServerAPI if typing.TYPE_CHECKING: - from ayon_api.typing import ProjectDict + from ayon_api.typing import ProjectDict, AnatomyPresetDict class ProjectsAPI(BaseServerAPI): + def get_project_anatomy_presets(self) -> list["AnatomyPresetDict"]: + """Anatomy presets available on server. + + Content has basic information about presets. Example output:: + + [ + { + "name": "netflix_VFX", + "primary": false, + "version": "1.0.0" + }, + { + ... + }, + ... + ] + + Returns: + list[dict[str, str]]: Anatomy presets available on server. + + """ + result = self.get("anatomy/presets") + result.raise_for_status() + return result.data.get("presets") or [] + + def get_default_anatomy_preset_name(self) -> str: + """Name of default anatomy preset. + + Primary preset is used as default preset. But when primary preset is + not set a built-in is used instead. Built-in preset is named '_'. + + Returns: + str: Name of preset that can be used by + 'get_project_anatomy_preset'. + + """ + for preset in self.get_project_anatomy_presets(): + if preset.get("primary"): + return preset["name"] + return "_" + + def get_project_anatomy_preset( + self, preset_name: Optional[str] = None + ) -> "AnatomyPresetDict": + """Anatomy preset values by name. + + Get anatomy preset values by preset name. Primary preset is returned + if preset name is set to 'None'. + + Args: + preset_name (Optional[str]): Preset name. + + Returns: + AnatomyPresetDict: Anatomy preset values. + + """ + if preset_name is None: + preset_name = "__primary__" + major, minor, patch, _, _ = self.get_server_version_tuple() + if (major, minor, patch) < (1, 0, 8): + preset_name = self.get_default_anatomy_preset_name() + + result = self.get(f"anatomy/presets/{preset_name}") + result.raise_for_status() + return result.data + + def get_built_in_anatomy_preset(self) -> "AnatomyPresetDict": + """Get built-in anatomy preset. + + Returns: + AnatomyPresetDict: Built-in anatomy preset. + + """ + preset_name = "__builtin__" + major, minor, patch, _, _ = self.get_server_version_tuple() + if (major, minor, patch) < (1, 0, 8): + preset_name = "_" + return self.get_project_anatomy_preset(preset_name) + + def get_build_in_anatomy_preset(self) -> "AnatomyPresetDict": + warnings.warn( + ( + "Used deprecated 'get_build_in_anatomy_preset' use" + " 'get_built_in_anatomy_preset' instead." + ), + DeprecationWarning + ) + return self.get_built_in_anatomy_preset() + def get_rest_project( self, project_name: str ) -> Optional["ProjectDict"]: @@ -338,6 +429,160 @@ def delete_project(self, project_name: str): f"Failed to delete project \"{project_name}\". {detail}" ) + def get_project_root_overrides( + self, project_name: str + ) -> dict[str, dict[str, str]]: + """Root overrides per site name. + + Method is based on logged user and can't be received for any other + user on server. + + Output will contain only roots per site id used by logged user. + + Args: + project_name (str): Name of project. + + Returns: + dict[str, dict[str, str]]: Root values by root name by site id. + + """ + result = self.get(f"projects/{project_name}/roots") + result.raise_for_status() + return result.data + + def get_project_roots_by_site( + self, project_name: str + ) -> dict[str, dict[str, str]]: + """Root overrides per site name. + + Method is based on logged user and can't be received for any other + user on server. + + Output will contain only roots per site id used by logged user. + + Deprecated: + Use 'get_project_root_overrides' instead. Function + deprecated since 1.0.6 + + Args: + project_name (str): Name of project. + + Returns: + dict[str, dict[str, str]]: Root values by root name by site id. + + """ + warnings.warn( + ( + "Method 'get_project_roots_by_site' is deprecated." + " Please use 'get_project_root_overrides' instead." + ), + DeprecationWarning + ) + return self.get_project_root_overrides(project_name) + + def get_project_root_overrides_by_site_id( + self, project_name: str, site_id: Optional[str] = None + ) -> dict[str, str]: + """Root overrides for site. + + If site id is not passed a site set in current api object is used + instead. + + Args: + project_name (str): Name of project. + site_id (Optional[str]): Site id for which want to receive + site overrides. + + Returns: + dict[str, str]: Root values by root name or None if + site does not have overrides. + + """ + if site_id is None: + site_id = self.get_site_id() + + if site_id is None: + return {} + roots = self.get_project_root_overrides(project_name) + return roots.get(site_id, {}) + + def get_project_roots_for_site( + self, project_name: str, site_id: Optional[str] = None + ) -> dict[str, str]: + """Root overrides for site. + + If site id is not passed a site set in current api object is used + instead. + + Deprecated: + Use 'get_project_root_overrides_by_site_id' instead. Function + deprecated since 1.0.6 + Args: + project_name (str): Name of project. + site_id (Optional[str]): Site id for which want to receive + site overrides. + + Returns: + dict[str, str]: Root values by root name, root name is not + available if it does not have overrides. + + """ + warnings.warn( + ( + "Method 'get_project_roots_for_site' is deprecated." + " Please use 'get_project_root_overrides_by_site_id' instead." + ), + DeprecationWarning + ) + return self.get_project_root_overrides_by_site_id(project_name) + + def get_project_roots_by_site_id( + self, project_name: str, site_id: Optional[str] = None + ) -> dict[str, str]: + """Root values for a site. + + If site id is not passed a site set in current api object is used + instead. If site id is not available, default roots are returned + for current platform. + + Args: + project_name (str): Name of project. + site_id (Optional[str]): Site id for which want to receive + root values. + + Returns: + dict[str, str]: Root values. + + """ + if site_id is None: + site_id = self.get_site_id() + + return self._get_project_roots_values(project_name, site_id=site_id) + + def get_project_roots_by_platform( + self, project_name: str, platform_name: Optional[str] = None + ) -> dict[str, str]: + """Root values for a site. + + If platform name is not passed current platform name is used instead. + + This function does return root values without site overrides. It is + possible to use the function to receive default root values. + + Args: + project_name (str): Name of project. + platform_name (Optional[Literal["windows", "linux", "darwin"]]): + Platform name for which want to receive root values. Current + platform name is used if not passed. + + Returns: + dict[str, str]: Root values. + + """ + return self._get_project_roots_values( + project_name, platform_name=platform_name + ) + def _get_project_graphql_fields( self, fields: Optional[set[str]] ) -> tuple[set[str], bool]: @@ -445,3 +690,48 @@ def _get_graphql_projects( fill_own_attribs(project) self._fill_project_entity_data(project) yield project + + def _get_project_roots_values( + self, + project_name: str, + site_id: Optional[str] = None, + platform_name: Optional[str] = None, + ) -> dict[str, str]: + """Root values for site or platform. + + Helper function that treats 'siteRoots' endpoint. The endpoint + requires to pass exactly one query value of site id + or platform name. + + When using platform name, it does return default project roots without + any site overrides. + + Output should contain all project roots with all filled values. If + value does not have override on a site, it should be filled with + project default value. + + Args: + project_name (str): Project name. + site_id (Optional[str]): Site id for which want to receive + site overrides. + platform_name (Optional[str]): Platform for which want to receive + roots. + + Returns: + dict[str, str]: Root values. + + """ + query_data = {} + if site_id is not None: + query_data["site_id"] = site_id + else: + if platform_name is None: + platform_name = platform.system() + query_data["platform"] = platform_name.lower() + + query = prepare_query_string(query_data) + response = self.get( + f"projects/{project_name}/siteRoots{query}" + ) + response.raise_for_status() + return response.data \ No newline at end of file diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 5eea9860e..a8c819865 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -12,7 +12,6 @@ import time import logging import platform -import copy import uuid import warnings from contextlib import contextmanager @@ -94,7 +93,6 @@ AttributesSchemaDict, InstallersInfoDict, DependencyPackagesDict, - AnatomyPresetDict, SecretDict, AnyEntityDict, @@ -2356,295 +2354,6 @@ def upload_dependency_package( route = self._get_dependency_package_route(dst_filename) self.upload_file(route, src_filepath, progress=progress) - # Anatomy presets - def get_project_anatomy_presets(self) -> list["AnatomyPresetDict"]: - """Anatomy presets available on server. - - Content has basic information about presets. Example output:: - - [ - { - "name": "netflix_VFX", - "primary": false, - "version": "1.0.0" - }, - { - ... - }, - ... - ] - - Returns: - list[dict[str, str]]: Anatomy presets available on server. - - """ - result = self.get("anatomy/presets") - result.raise_for_status() - return result.data.get("presets") or [] - - def get_default_anatomy_preset_name(self) -> str: - """Name of default anatomy preset. - - Primary preset is used as default preset. But when primary preset is - not set a built-in is used instead. Built-in preset is named '_'. - - Returns: - str: Name of preset that can be used by - 'get_project_anatomy_preset'. - - """ - for preset in self.get_project_anatomy_presets(): - if preset.get("primary"): - return preset["name"] - return "_" - - def get_project_anatomy_preset( - self, preset_name: Optional[str] = None - ) -> "AnatomyPresetDict": - """Anatomy preset values by name. - - Get anatomy preset values by preset name. Primary preset is returned - if preset name is set to 'None'. - - Args: - preset_name (Optional[str]): Preset name. - - Returns: - AnatomyPresetDict: Anatomy preset values. - - """ - if preset_name is None: - preset_name = "__primary__" - major, minor, patch, _, _ = self.server_version_tuple - if (major, minor, patch) < (1, 0, 8): - preset_name = self.get_default_anatomy_preset_name() - - result = self.get(f"anatomy/presets/{preset_name}") - result.raise_for_status() - return result.data - - def get_built_in_anatomy_preset(self) -> "AnatomyPresetDict": - """Get built-in anatomy preset. - - Returns: - AnatomyPresetDict: Built-in anatomy preset. - - """ - preset_name = "__builtin__" - major, minor, patch, _, _ = self.server_version_tuple - if (major, minor, patch) < (1, 0, 8): - preset_name = "_" - return self.get_project_anatomy_preset(preset_name) - - def get_build_in_anatomy_preset(self) -> "AnatomyPresetDict": - warnings.warn( - ( - "Used deprecated 'get_build_in_anatomy_preset' use" - " 'get_built_in_anatomy_preset' instead." - ), - DeprecationWarning - ) - return self.get_built_in_anatomy_preset() - - def get_project_root_overrides( - self, project_name: str - ) -> dict[str, dict[str, str]]: - """Root overrides per site name. - - Method is based on logged user and can't be received for any other - user on server. - - Output will contain only roots per site id used by logged user. - - Args: - project_name (str): Name of project. - - Returns: - dict[str, dict[str, str]]: Root values by root name by site id. - - """ - result = self.get(f"projects/{project_name}/roots") - result.raise_for_status() - return result.data - - def get_project_roots_by_site( - self, project_name: str - ) -> dict[str, dict[str, str]]: - """Root overrides per site name. - - Method is based on logged user and can't be received for any other - user on server. - - Output will contain only roots per site id used by logged user. - - Deprecated: - Use 'get_project_root_overrides' instead. Function - deprecated since 1.0.6 - - Args: - project_name (str): Name of project. - - Returns: - dict[str, dict[str, str]]: Root values by root name by site id. - - """ - warnings.warn( - ( - "Method 'get_project_roots_by_site' is deprecated." - " Please use 'get_project_root_overrides' instead." - ), - DeprecationWarning - ) - return self.get_project_root_overrides(project_name) - - def get_project_root_overrides_by_site_id( - self, project_name: str, site_id: Optional[str] = None - ) -> dict[str, str]: - """Root overrides for site. - - If site id is not passed a site set in current api object is used - instead. - - Args: - project_name (str): Name of project. - site_id (Optional[str]): Site id for which want to receive - site overrides. - - Returns: - dict[str, str]: Root values by root name or None if - site does not have overrides. - - """ - if site_id is None: - site_id = self.site_id - - if site_id is None: - return {} - roots = self.get_project_root_overrides(project_name) - return roots.get(site_id, {}) - - def get_project_roots_for_site( - self, project_name: str, site_id: Optional[str] = None - ) -> dict[str, str]: - """Root overrides for site. - - If site id is not passed a site set in current api object is used - instead. - - Deprecated: - Use 'get_project_root_overrides_by_site_id' instead. Function - deprecated since 1.0.6 - Args: - project_name (str): Name of project. - site_id (Optional[str]): Site id for which want to receive - site overrides. - - Returns: - dict[str, str]: Root values by root name, root name is not - available if it does not have overrides. - - """ - warnings.warn( - ( - "Method 'get_project_roots_for_site' is deprecated." - " Please use 'get_project_root_overrides_by_site_id' instead." - ), - DeprecationWarning - ) - return self.get_project_root_overrides_by_site_id(project_name) - - def _get_project_roots_values( - self, - project_name: str, - site_id: Optional[str] = None, - platform_name: Optional[str] = None, - ) -> dict[str, str]: - """Root values for site or platform. - - Helper function that treats 'siteRoots' endpoint. The endpoint - requires to pass exactly one query value of site id - or platform name. - - When using platform name, it does return default project roots without - any site overrides. - - Output should contain all project roots with all filled values. If - value does not have override on a site, it should be filled with - project default value. - - Args: - project_name (str): Project name. - site_id (Optional[str]): Site id for which want to receive - site overrides. - platform_name (Optional[str]): Platform for which want to receive - roots. - - Returns: - dict[str, str]: Root values. - - """ - query_data = {} - if site_id is not None: - query_data["site_id"] = site_id - else: - if platform_name is None: - platform_name = platform.system() - query_data["platform"] = platform_name.lower() - - query = prepare_query_string(query_data) - response = self.get( - f"projects/{project_name}/siteRoots{query}" - ) - response.raise_for_status() - return response.data - - def get_project_roots_by_site_id( - self, project_name: str, site_id: Optional[str] = None - ) -> dict[str, str]: - """Root values for a site. - - If site id is not passed a site set in current api object is used - instead. If site id is not available, default roots are returned - for current platform. - - Args: - project_name (str): Name of project. - site_id (Optional[str]): Site id for which want to receive - root values. - - Returns: - dict[str, str]: Root values. - - """ - if site_id is None: - site_id = self.site_id - - return self._get_project_roots_values(project_name, site_id=site_id) - - def get_project_roots_by_platform( - self, project_name: str, platform_name: Optional[str] = None - ) -> dict[str, str]: - """Root values for a site. - - If platform name is not passed current platform name is used instead. - - This function does return root values without site overrides. It is - possible to use the function to receive default root values. - - Args: - project_name (str): Name of project. - platform_name (Optional[Literal["windows", "linux", "darwin"]]): - Platform name for which want to receive root values. Current - platform name is used if not passed. - - Returns: - dict[str, str]: Root values. - - """ - return self._get_project_roots_values( - project_name, platform_name=platform_name - ) - def get_secrets(self) -> list["SecretDict"]: """Get all secrets. diff --git a/ayon_api/typing.py b/ayon_api/typing.py index 038372f0c..9f45a5120 100644 --- a/ayon_api/typing.py +++ b/ayon_api/typing.py @@ -312,12 +312,15 @@ class AnatomyPresetDict(TypedDict): link_types: list[AnatomyPresetLinkTypeDict] statuses: list[AnatomyPresetStatusDict] tags: list[AnatomyPresetTagDict] + primary: bool + name: str class SecretDict(TypedDict): name: str value: str + ProjectDict = dict[str, Any] FolderDict = dict[str, Any] TaskDict = dict[str, Any] From ea0632a4dd6bc7118a47a262543585bfd965c245 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:44:33 +0200 Subject: [PATCH 40/53] separated attributes methods --- automated_api.py | 16 +-- ayon_api/_api_helpers/__init__.py | 2 + ayon_api/_api_helpers/attributes.py | 159 ++++++++++++++++++++++++++++ ayon_api/server_api.py | 145 +------------------------ 4 files changed, 172 insertions(+), 150 deletions(-) create mode 100644 ayon_api/_api_helpers/attributes.py diff --git a/automated_api.py b/automated_api.py index d6a24b95f..e0514a6ed 100644 --- a/automated_api.py +++ b/automated_api.py @@ -339,16 +339,17 @@ def prepare_api_functions(api_globals): ActivitiesAPI, BundlesAddonsAPI, EventsAPI, + AttributesAPI, + ProjectsAPI, FoldersAPI, TasksAPI, ProductsAPI, VersionsAPI, + RepresentationsAPI, + WorkfilesAPI, LinksAPI, ListsAPI, - ProjectsAPI, ThumbnailsAPI, - WorkfilesAPI, - RepresentationsAPI, ) functions = [] @@ -357,16 +358,17 @@ def prepare_api_functions(api_globals): _items.extend(ActivitiesAPI.__dict__.items()) _items.extend(BundlesAddonsAPI.__dict__.items()) _items.extend(EventsAPI.__dict__.items()) - _items.extend(LinksAPI.__dict__.items()) - _items.extend(ListsAPI.__dict__.items()) + _items.extend(AttributesAPI.__dict__.items()) _items.extend(ProjectsAPI.__dict__.items()) _items.extend(FoldersAPI.__dict__.items()) _items.extend(TasksAPI.__dict__.items()) _items.extend(ProductsAPI.__dict__.items()) _items.extend(VersionsAPI.__dict__.items()) - _items.extend(ThumbnailsAPI.__dict__.items()) - _items.extend(WorkfilesAPI.__dict__.items()) _items.extend(RepresentationsAPI.__dict__.items()) + _items.extend(WorkfilesAPI.__dict__.items()) + _items.extend(LinksAPI.__dict__.items()) + _items.extend(ListsAPI.__dict__.items()) + _items.extend(ThumbnailsAPI.__dict__.items()) processed = set() for attr_name, attr in _items: diff --git a/ayon_api/_api_helpers/__init__.py b/ayon_api/_api_helpers/__init__.py index e102d82d4..42b834ee7 100644 --- a/ayon_api/_api_helpers/__init__.py +++ b/ayon_api/_api_helpers/__init__.py @@ -1,6 +1,7 @@ from .base import BaseServerAPI from .bundles_addons import BundlesAddonsAPI from .events import EventsAPI +from .attributes import AttributesAPI from .projects import ProjectsAPI from .folders import FoldersAPI from .tasks import TasksAPI @@ -19,6 +20,7 @@ "BaseServerAPI", "BundlesAddonsAPI", "EventsAPI", + "AttributesAPI", "ProjectsAPI", "FoldersAPI", "TasksAPI", diff --git a/ayon_api/_api_helpers/attributes.py b/ayon_api/_api_helpers/attributes.py new file mode 100644 index 000000000..9a9216d0f --- /dev/null +++ b/ayon_api/_api_helpers/attributes.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +import typing +from typing import Optional +import copy + +from .base import BaseServerAPI + +if typing.TYPE_CHECKING: + from ayon_api.typing import ( + AttributeSchemaDataDict, + AttributeSchemaDict, + AttributesSchemaDict, + AttributeScope, + ) + + +class AttributesAPI(BaseServerAPI): + _attributes_schema = None + _entity_type_attributes_cache = {} + + def get_attributes_schema( + self, use_cache: bool = True + ) -> "AttributesSchemaDict": + if not use_cache: + self.reset_attributes_schema() + + if self._attributes_schema is None: + result = self.get("attributes") + result.raise_for_status() + self._attributes_schema = result.data + return copy.deepcopy(self._attributes_schema) + + def reset_attributes_schema(self): + self._attributes_schema = None + self._entity_type_attributes_cache = {} + + def set_attribute_config( + self, + attribute_name: str, + data: "AttributeSchemaDataDict", + scope: list["AttributeScope"], + position: Optional[int] = None, + builtin: bool = False, + ): + if position is None: + attributes = self.get("attributes").data["attributes"] + origin_attr = next( + ( + attr for attr in attributes + if attr["name"] == attribute_name + ), + None + ) + if origin_attr: + position = origin_attr["position"] + else: + position = len(attributes) + + response = self.put( + f"attributes/{attribute_name}", + data=data, + scope=scope, + position=position, + builtin=builtin + ) + if response.status_code != 204: + # TODO raise different exception + raise ValueError( + f"Attribute \"{attribute_name}\" was not created/updated." + f" {response.detail}" + ) + + self.reset_attributes_schema() + + def remove_attribute_config(self, attribute_name: str): + """Remove attribute from server. + + This can't be un-done, please use carefully. + + Args: + attribute_name (str): Name of attribute to remove. + + """ + response = self.delete(f"attributes/{attribute_name}") + response.raise_for_status( + f"Attribute \"{attribute_name}\" was not created/updated." + f" {response.detail}" + ) + + self.reset_attributes_schema() + + def get_attributes_for_type( + self, entity_type: "AttributeScope" + ) -> dict[str, "AttributeSchemaDict"]: + """Get attribute schemas available for an entity type. + + Example:: + + ``` + # Example attribute schema + { + # Common + "type": "integer", + "title": "Clip Out", + "description": null, + "example": 1, + "default": 1, + # These can be filled based on value of 'type' + "gt": null, + "ge": null, + "lt": null, + "le": null, + "minLength": null, + "maxLength": null, + "minItems": null, + "maxItems": null, + "regex": null, + "enum": null + } + ``` + + Args: + entity_type (str): Entity type for which should be attributes + received. + + Returns: + dict[str, dict[str, Any]]: Attribute schemas that are available + for entered entity type. + + """ + attributes = self._entity_type_attributes_cache.get(entity_type) + if attributes is None: + attributes_schema = self.get_attributes_schema() + attributes = {} + for attr in attributes_schema["attributes"]: + if entity_type not in attr["scope"]: + continue + attr_name = attr["name"] + attributes[attr_name] = attr["data"] + + self._entity_type_attributes_cache[entity_type] = attributes + + return copy.deepcopy(attributes) + + def get_attributes_fields_for_type( + self, entity_type: "AttributeScope" + ) -> set[str]: + """Prepare attribute fields for entity type. + + Returns: + set[str]: Attributes fields for entity type. + + """ + attributes = self.get_attributes_for_type(entity_type) + return { + f"attrib.{attr}" + for attr in attributes + } diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index a8c819865..bc84fbf6e 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -71,6 +71,7 @@ ActivitiesAPI, BundlesAddonsAPI, EventsAPI, + AttributesAPI, LinksAPI, ListsAPI, ProjectsAPI, @@ -87,10 +88,6 @@ from typing import Union from .typing import ( ServerVersion, - AttributeScope, - AttributeSchemaDataDict, - AttributeSchemaDict, - AttributesSchemaDict, InstallersInfoDict, DependencyPackagesDict, SecretDict, @@ -217,6 +214,7 @@ class ServerAPI( ActivitiesAPI, BundlesAddonsAPI, EventsAPI, + AttributesAPI, ProjectsAPI, FoldersAPI, TasksAPI, @@ -1775,145 +1773,6 @@ def get_schemas(self) -> dict[str, Any]: server_schema = self.get_server_schema() return server_schema["components"]["schemas"] - def get_attributes_schema( - self, use_cache: bool = True - ) -> "AttributesSchemaDict": - if not use_cache: - self.reset_attributes_schema() - - if self._attributes_schema is None: - result = self.get("attributes") - result.raise_for_status() - self._attributes_schema = result.data - return copy.deepcopy(self._attributes_schema) - - def reset_attributes_schema(self): - self._attributes_schema = None - self._entity_type_attributes_cache = {} - - def set_attribute_config( - self, - attribute_name: str, - data: "AttributeSchemaDataDict", - scope: list["AttributeScope"], - position: Optional[int] = None, - builtin: bool = False, - ): - if position is None: - attributes = self.get("attributes").data["attributes"] - origin_attr = next( - ( - attr for attr in attributes - if attr["name"] == attribute_name - ), - None - ) - if origin_attr: - position = origin_attr["position"] - else: - position = len(attributes) - - response = self.put( - f"attributes/{attribute_name}", - data=data, - scope=scope, - position=position, - builtin=builtin - ) - if response.status_code != 204: - # TODO raise different exception - raise ValueError( - f"Attribute \"{attribute_name}\" was not created/updated." - f" {response.detail}" - ) - - self.reset_attributes_schema() - - def remove_attribute_config(self, attribute_name: str): - """Remove attribute from server. - - This can't be un-done, please use carefully. - - Args: - attribute_name (str): Name of attribute to remove. - - """ - response = self.delete(f"attributes/{attribute_name}") - response.raise_for_status( - f"Attribute \"{attribute_name}\" was not created/updated." - f" {response.detail}" - ) - - self.reset_attributes_schema() - - def get_attributes_for_type( - self, entity_type: "AttributeScope" - ) -> dict[str, "AttributeSchemaDict"]: - """Get attribute schemas available for an entity type. - - Example:: - - ``` - # Example attribute schema - { - # Common - "type": "integer", - "title": "Clip Out", - "description": null, - "example": 1, - "default": 1, - # These can be filled based on value of 'type' - "gt": null, - "ge": null, - "lt": null, - "le": null, - "minLength": null, - "maxLength": null, - "minItems": null, - "maxItems": null, - "regex": null, - "enum": null - } - ``` - - Args: - entity_type (str): Entity type for which should be attributes - received. - - Returns: - dict[str, dict[str, Any]]: Attribute schemas that are available - for entered entity type. - - """ - attributes = self._entity_type_attributes_cache.get(entity_type) - if attributes is None: - attributes_schema = self.get_attributes_schema() - attributes = {} - for attr in attributes_schema["attributes"]: - if entity_type not in attr["scope"]: - continue - attr_name = attr["name"] - attributes[attr_name] = attr["data"] - - self._entity_type_attributes_cache[entity_type] = attributes - - return copy.deepcopy(attributes) - - def get_attributes_fields_for_type( - self, entity_type: "AttributeScope" - ) -> set[str]: - """Prepare attribute fields for entity type. - - Returns: - set[str]: Attributes fields for entity type. - - """ - attributes = self.get_attributes_for_type(entity_type) - return { - f"attrib.{attr}" - for attr in attributes - } - def get_default_fields_for_type(self, entity_type: str) -> set[str]: """Default fields for entity type. From 2387765ec1982046e7324bd3654330b5e896c6c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:58:02 +0200 Subject: [PATCH 41/53] moved dependency packages and installers endpoints to separate files --- automated_api.py | 10 +- ayon_api/_api_helpers/__init__.py | 4 + ayon_api/_api_helpers/dependency_packages.py | 237 +++++++++++ ayon_api/_api_helpers/installers.py | 168 ++++++++ ayon_api/server_api.py | 402 +------------------ 5 files changed, 429 insertions(+), 392 deletions(-) create mode 100644 ayon_api/_api_helpers/dependency_packages.py create mode 100644 ayon_api/_api_helpers/installers.py diff --git a/automated_api.py b/automated_api.py index e0514a6ed..112e904a1 100644 --- a/automated_api.py +++ b/automated_api.py @@ -335,8 +335,8 @@ def sig_params_to_str(sig, param_names, api_globals, indent=0): def prepare_api_functions(api_globals): from ayon_api.server_api import ( # noqa: E402 ServerAPI, - ActionsAPI, - ActivitiesAPI, + InstallersAPI, + DependencyPackagesAPI, BundlesAddonsAPI, EventsAPI, AttributesAPI, @@ -347,13 +347,17 @@ def prepare_api_functions(api_globals): VersionsAPI, RepresentationsAPI, WorkfilesAPI, + ThumbnailsAPI, + ActivitiesAPI, + ActionsAPI, LinksAPI, ListsAPI, - ThumbnailsAPI, ) functions = [] _items = list(ServerAPI.__dict__.items()) + _items.extend(InstallersAPI.__dict__.items()) + _items.extend(DependencyPackagesAPI.__dict__.items()) _items.extend(ActionsAPI.__dict__.items()) _items.extend(ActivitiesAPI.__dict__.items()) _items.extend(BundlesAddonsAPI.__dict__.items()) diff --git a/ayon_api/_api_helpers/__init__.py b/ayon_api/_api_helpers/__init__.py index 42b834ee7..0938461be 100644 --- a/ayon_api/_api_helpers/__init__.py +++ b/ayon_api/_api_helpers/__init__.py @@ -1,4 +1,6 @@ from .base import BaseServerAPI +from .installers import InstallersAPI +from .dependency_packages import DependencyPackagesAPI from .bundles_addons import BundlesAddonsAPI from .events import EventsAPI from .attributes import AttributesAPI @@ -18,6 +20,8 @@ __all__ = ( "BaseServerAPI", + "InstallersAPI", + "DependencyPackagesAPI", "BundlesAddonsAPI", "EventsAPI", "AttributesAPI", diff --git a/ayon_api/_api_helpers/dependency_packages.py b/ayon_api/_api_helpers/dependency_packages.py new file mode 100644 index 000000000..7268d8a77 --- /dev/null +++ b/ayon_api/_api_helpers/dependency_packages.py @@ -0,0 +1,237 @@ +from __future__ import annotations + +import os +import warnings +import platform +import typing +from typing import Optional, Any + +from ayon_api.utils import TransferProgress + +from .base import BaseServerAPI + +if typing.TYPE_CHECKING: + from ayon_api.typing import DependencyPackagesDict + + +class DependencyPackagesAPI(BaseServerAPI): + def get_dependency_packages(self) -> "DependencyPackagesDict": + """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: + DependencyPackagesDict: Information about dependency packages + known for server. + + """ + endpoint = self._get_dependency_package_route() + result = self.get(endpoint) + result.raise_for_status() + return result.data + + def create_dependency_package( + self, + filename: str, + python_modules: dict[str, str], + source_addons: dict[str, str], + installer_version: str, + checksum: str, + checksum_algorithm: str, + file_size: int, + sources: Optional[list[dict[str, Any]]] = None, + platform_name: Optional[str] = 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 :meth:`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 + + route = self._get_dependency_package_route() + response = self.post(route, **post_body) + response.raise_for_status() + + def update_dependency_package( + self, filename: str, sources: list[dict[str, Any]] + ): + """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( + self._get_dependency_package_route(filename), + sources=sources + ) + response.raise_for_status() + + def delete_dependency_package( + self, filename: str, platform_name: Optional[str] = None + ): + """Remove dependency package for specific platform. + + Args: + filename (str): Filename of dependency package. + platform_name (Optional[str]): Deprecated. + + """ + if platform_name is not None: + warnings.warn( + ( + "Argument 'platform_name' is deprecated in" + " 'delete_dependency_package'. The argument will be" + " removed, please modify your code accordingly." + ), + DeprecationWarning + ) + + route = self._get_dependency_package_route(filename) + response = self.delete(route) + response.raise_for_status("Failed to delete dependency file") + return response.data + + def download_dependency_package( + self, + src_filename: str, + dst_directory: str, + dst_filename: str, + platform_name: Optional[str] = None, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, + ) -> str: + """Download dependency package from server. + + This method requires to have authorized token available. The package + is only downloaded. + + Args: + 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. + dst_filename (str): Name of destination filename. + platform_name (Optional[str]): Deprecated. + chunk_size (Optional[int]): Download chunk size. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + + Returns: + str: Filepath to downloaded file. + + """ + if platform_name is not None: + warnings.warn( + ( + "Argument 'platform_name' is deprecated in" + " 'download_dependency_package'. The argument will be" + " removed, please modify your code accordingly." + ), + DeprecationWarning + ) + route = self._get_dependency_package_route(src_filename) + package_filepath = os.path.join(dst_directory, dst_filename) + self.download_file( + route, + package_filepath, + chunk_size=chunk_size, + progress=progress + ) + return package_filepath + + def upload_dependency_package( + self, + src_filepath: str, + dst_filename: str, + platform_name: Optional[str] = None, + progress: Optional[TransferProgress] = None, + ): + """Upload dependency package to server. + + Args: + 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]): Deprecated. + progress (Optional[TransferProgress]): Object to keep track about + upload state. + + """ + if platform_name is not None: + warnings.warn( + ( + "Argument 'platform_name' is deprecated in" + " 'upload_dependency_package'. The argument will be" + " removed, please modify your code accordingly." + ), + DeprecationWarning + ) + + route = self._get_dependency_package_route(dst_filename) + self.upload_file(route, src_filepath, progress=progress) + + def _get_dependency_package_route( + self, filename: Optional[str] = None + ) -> str: + endpoint = "desktop/dependencyPackages" + if filename: + return f"{endpoint}/{filename}" + return endpoint diff --git a/ayon_api/_api_helpers/installers.py b/ayon_api/_api_helpers/installers.py new file mode 100644 index 000000000..f6d7ec7f5 --- /dev/null +++ b/ayon_api/_api_helpers/installers.py @@ -0,0 +1,168 @@ +from __future__ import annotations + + +import typing +from typing import Optional, Any + +from ayon_api.utils import prepare_query_string, TransferProgress + +from .base import BaseServerAPI + +if typing.TYPE_CHECKING: + from ayon_api.typing import InstallersInfoDict + + +class InstallersAPI(BaseServerAPI): + def get_installers( + self, + version: Optional[str] = None, + platform_name: Optional[str] = None, + ) -> "InstallersInfoDict": + """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: + InstallersInfoDict: Information about installers known for server. + + """ + query = prepare_query_string({ + "version": version or None, + "platform": platform_name or None, + }) + response = self.get(f"desktop/installers{query}") + response.raise_for_status() + return response.data + + def create_installer( + self, + filename: str, + version: str, + python_version: str, + platform_name: str, + python_modules: dict[str, str], + runtime_python_modules: dict[str, str], + checksum: str, + checksum_algorithm: str, + file_size: int, + sources: Optional[list[dict[str, Any]]] = 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. + + Runtime python modules are modules that are required to run AYON + desktop application, but are not added to PYTHONPATH for any + subprocess. + + 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. + runtime_python_modules (dict[str, str]): Runtime python modules + that are available in installer. + checksum (str): Installer file checksum. + checksum_algorithm (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, + "runtimePythonModules": runtime_python_modules, + "checksum": checksum, + "checksumAlgorithm": checksum_algorithm, + "size": file_size, + } + if sources: + body["sources"] = sources + + response = self.post("desktop/installers", **body) + response.raise_for_status() + + def update_installer(self, filename: str, sources: list[dict[str, Any]]): + """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.patch( + f"desktop/installers/{filename}", + sources=sources + ) + response.raise_for_status() + + def delete_installer(self, filename: str): + """Delete installer from server. + + Args: + filename (str): Installer filename. + + """ + response = self.delete(f"desktop/installers/{filename}") + response.raise_for_status() + + def download_installer( + self, + filename: str, + dst_filepath: str, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = 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( + f"desktop/installers/{filename}", + dst_filepath, + chunk_size=chunk_size, + progress=progress + ) + + def upload_installer( + self, + src_filepath: str, + dst_filename: str, + progress: Optional[TransferProgress] = 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. + + Returns: + requests.Response: Response object. + + """ + return self.upload_file( + f"desktop/installers/{dst_filename}", + src_filepath, + progress=progress + ) diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index bc84fbf6e..7bb26484a 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -13,7 +13,6 @@ import logging import platform import uuid -import warnings from contextlib import contextmanager import typing from typing import Optional, Iterable, Tuple, Generator, Any @@ -67,33 +66,31 @@ fill_own_attribs, ) from ._api_helpers import ( - ActionsAPI, - ActivitiesAPI, + InstallersAPI, + DependencyPackagesAPI, BundlesAddonsAPI, EventsAPI, AttributesAPI, - LinksAPI, - ListsAPI, ProjectsAPI, FoldersAPI, TasksAPI, ProductsAPI, VersionsAPI, - ThumbnailsAPI, - WorkfilesAPI, RepresentationsAPI, + WorkfilesAPI, + ThumbnailsAPI, + ActivitiesAPI, + ActionsAPI, + LinksAPI, + ListsAPI, ) if typing.TYPE_CHECKING: from typing import Union from .typing import ( ServerVersion, - InstallersInfoDict, - DependencyPackagesDict, SecretDict, - AnyEntityDict, - StreamType, ) @@ -210,8 +207,8 @@ def as_user(self, username): class ServerAPI( - ActionsAPI, - ActivitiesAPI, + InstallersAPI, + DependencyPackagesAPI, BundlesAddonsAPI, EventsAPI, AttributesAPI, @@ -222,9 +219,11 @@ class ServerAPI( VersionsAPI, RepresentationsAPI, WorkfilesAPI, + ThumbnailsAPI, + ActivitiesAPI, + ActionsAPI, LinksAPI, ListsAPI, - ThumbnailsAPI, ): """Base handler of connection to server. @@ -1838,381 +1837,6 @@ def get_default_fields_for_type(self, entity_type: str) -> set[str]: | self.get_attributes_fields_for_type(entity_type) ) - def get_installers( - self, - version: Optional[str] = None, - platform_name: Optional[str] = None, - ) -> "InstallersInfoDict": - """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: - InstallersInfoDict: Information about installers known for server. - - """ - query = prepare_query_string({ - "version": version or None, - "platform": platform_name or None, - }) - response = self.get(f"desktop/installers{query}") - response.raise_for_status() - return response.data - - def create_installer( - self, - filename: str, - version: str, - python_version: str, - platform_name: str, - python_modules: dict[str, str], - runtime_python_modules: dict[str, str], - checksum: str, - checksum_algorithm: str, - file_size: int, - sources: Optional[list[dict[str, Any]]] = 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. - - Runtime python modules are modules that are required to run AYON - desktop application, but are not added to PYTHONPATH for any - subprocess. - - 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. - runtime_python_modules (dict[str, str]): Runtime python modules - that are available in installer. - checksum (str): Installer file checksum. - checksum_algorithm (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, - "runtimePythonModules": runtime_python_modules, - "checksum": checksum, - "checksumAlgorithm": checksum_algorithm, - "size": file_size, - } - if sources: - body["sources"] = sources - - response = self.post("desktop/installers", **body) - response.raise_for_status() - - def update_installer(self, filename: str, sources: list[dict[str, Any]]): - """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.patch( - f"desktop/installers/{filename}", - sources=sources - ) - response.raise_for_status() - - def delete_installer(self, filename: str): - """Delete installer from server. - - Args: - filename (str): Installer filename. - - """ - response = self.delete(f"desktop/installers/{filename}") - response.raise_for_status() - - def download_installer( - self, - filename: str, - dst_filepath: str, - chunk_size: Optional[int] = None, - progress: Optional[TransferProgress] = 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( - f"desktop/installers/{filename}", - dst_filepath, - chunk_size=chunk_size, - progress=progress - ) - - def upload_installer( - self, - src_filepath: str, - dst_filename: str, - progress: Optional[TransferProgress] = 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. - - Returns: - requests.Response: Response object. - - """ - return self.upload_file( - f"desktop/installers/{dst_filename}", - src_filepath, - progress=progress - ) - - def _get_dependency_package_route( - self, filename: Optional[str] = None - ) -> str: - endpoint = "desktop/dependencyPackages" - if filename: - return f"{endpoint}/{filename}" - return endpoint - - def get_dependency_packages(self) -> "DependencyPackagesDict": - """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: - DependencyPackagesDict: Information about dependency packages - known for server. - - """ - endpoint = self._get_dependency_package_route() - result = self.get(endpoint) - result.raise_for_status() - return result.data - - def create_dependency_package( - self, - filename: str, - python_modules: dict[str, str], - source_addons: dict[str, str], - installer_version: str, - checksum: str, - checksum_algorithm: str, - file_size: int, - sources: Optional[list[dict[str, Any]]] = None, - platform_name: Optional[str] = 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 :meth:`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 - - route = self._get_dependency_package_route() - response = self.post(route, **post_body) - response.raise_for_status() - - def update_dependency_package( - self, filename: str, sources: list[dict[str, Any]] - ): - """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( - self._get_dependency_package_route(filename), - sources=sources - ) - response.raise_for_status() - - def delete_dependency_package( - self, filename: str, platform_name: Optional[str] = None - ): - """Remove dependency package for specific platform. - - Args: - filename (str): Filename of dependency package. - platform_name (Optional[str]): Deprecated. - - """ - if platform_name is not None: - warnings.warn( - ( - "Argument 'platform_name' is deprecated in" - " 'delete_dependency_package'. The argument will be" - " removed, please modify your code accordingly." - ), - DeprecationWarning - ) - - route = self._get_dependency_package_route(filename) - response = self.delete(route) - response.raise_for_status("Failed to delete dependency file") - return response.data - - def download_dependency_package( - self, - src_filename: str, - dst_directory: str, - dst_filename: str, - platform_name: Optional[str] = None, - chunk_size: Optional[int] = None, - progress: Optional[TransferProgress] = None, - ) -> str: - """Download dependency package from server. - - This method requires to have authorized token available. The package - is only downloaded. - - Args: - 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. - dst_filename (str): Name of destination filename. - platform_name (Optional[str]): Deprecated. - chunk_size (Optional[int]): Download chunk size. - progress (Optional[TransferProgress]): Object that gives ability - to track download progress. - - Returns: - str: Filepath to downloaded file. - - """ - if platform_name is not None: - warnings.warn( - ( - "Argument 'platform_name' is deprecated in" - " 'download_dependency_package'. The argument will be" - " removed, please modify your code accordingly." - ), - DeprecationWarning - ) - route = self._get_dependency_package_route(src_filename) - package_filepath = os.path.join(dst_directory, dst_filename) - self.download_file( - route, - package_filepath, - chunk_size=chunk_size, - progress=progress - ) - return package_filepath - - def upload_dependency_package( - self, - src_filepath: str, - dst_filename: str, - platform_name: Optional[str] = None, - progress: Optional[TransferProgress] = None, - ): - """Upload dependency package to server. - - Args: - 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]): Deprecated. - progress (Optional[TransferProgress]): Object to keep track about - upload state. - - """ - if platform_name is not None: - warnings.warn( - ( - "Argument 'platform_name' is deprecated in" - " 'upload_dependency_package'. The argument will be" - " removed, please modify your code accordingly." - ), - DeprecationWarning - ) - - route = self._get_dependency_package_route(dst_filename) - self.upload_file(route, src_filepath, progress=progress) - def get_secrets(self) -> list["SecretDict"]: """Get all secrets. From 27bf88ba61cff269cfbdc4ca04e1ded7b88e5dc8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:02:14 +0200 Subject: [PATCH 42/53] moved secrets to separate file --- automated_api.py | 2 + ayon_api/_api_helpers/__init__.py | 2 + ayon_api/_api_helpers/secrets.py | 83 ++++++++++++++++++++++++++++++ ayon_api/server_api.py | 84 ++----------------------------- 4 files changed, 91 insertions(+), 80 deletions(-) create mode 100644 ayon_api/_api_helpers/secrets.py diff --git a/automated_api.py b/automated_api.py index 112e904a1..b32863cd0 100644 --- a/automated_api.py +++ b/automated_api.py @@ -337,6 +337,7 @@ def prepare_api_functions(api_globals): ServerAPI, InstallersAPI, DependencyPackagesAPI, + SecretsAPI, BundlesAddonsAPI, EventsAPI, AttributesAPI, @@ -358,6 +359,7 @@ def prepare_api_functions(api_globals): _items = list(ServerAPI.__dict__.items()) _items.extend(InstallersAPI.__dict__.items()) _items.extend(DependencyPackagesAPI.__dict__.items()) + _items.extend(SecretsAPI.__dict__.items()) _items.extend(ActionsAPI.__dict__.items()) _items.extend(ActivitiesAPI.__dict__.items()) _items.extend(BundlesAddonsAPI.__dict__.items()) diff --git a/ayon_api/_api_helpers/__init__.py b/ayon_api/_api_helpers/__init__.py index 0938461be..6e81904a8 100644 --- a/ayon_api/_api_helpers/__init__.py +++ b/ayon_api/_api_helpers/__init__.py @@ -1,6 +1,7 @@ from .base import BaseServerAPI from .installers import InstallersAPI from .dependency_packages import DependencyPackagesAPI +from .secrets import SecretsAPI from .bundles_addons import BundlesAddonsAPI from .events import EventsAPI from .attributes import AttributesAPI @@ -22,6 +23,7 @@ "BaseServerAPI", "InstallersAPI", "DependencyPackagesAPI", + "SecretsAPI", "BundlesAddonsAPI", "EventsAPI", "AttributesAPI", diff --git a/ayon_api/_api_helpers/secrets.py b/ayon_api/_api_helpers/secrets.py new file mode 100644 index 000000000..f02649fef --- /dev/null +++ b/ayon_api/_api_helpers/secrets.py @@ -0,0 +1,83 @@ +from __future__ import annotations + +import typing + +from .base import BaseServerAPI +if typing.TYPE_CHECKING: + from ayon_api.typing import SecretDict + + +class SecretsAPI(BaseServerAPI): + def get_secrets(self) -> list["SecretDict"]: + """Get all secrets. + + Example output:: + + [ + { + "name": "secret_1", + "value": "secret_value_1", + }, + { + "name": "secret_2", + "value": "secret_value_2", + } + ] + + Returns: + list[SecretDict]: List of secret entities. + + """ + response = self.get("secrets") + response.raise_for_status() + return response.data + + def get_secret(self, secret_name: str) -> "SecretDict": + """Get secret by name. + + Example output:: + + { + "name": "secret_name", + "value": "secret_value", + } + + Args: + secret_name (str): Name of secret. + + Returns: + dict[str, str]: Secret entity data. + + """ + response = self.get(f"secrets/{secret_name}") + response.raise_for_status() + return response.data + + def save_secret(self, secret_name: str, secret_value: str): + """Save secret. + + This endpoint can create and update secret. + + Args: + secret_name (str): Name of secret. + secret_value (str): Value of secret. + + """ + response = self.put( + f"secrets/{secret_name}", + name=secret_name, + value=secret_value, + ) + response.raise_for_status() + return response.data + + def delete_secret(self, secret_name: str): + """Delete secret by name. + + Args: + secret_name (str): Name of secret to delete. + + """ + response = self.delete(f"secrets/{secret_name}") + response.raise_for_status() + return response.data diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index 7bb26484a..f016c0b1f 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -15,7 +15,7 @@ import uuid from contextlib import contextmanager import typing -from typing import Optional, Iterable, Tuple, Generator, Any +from typing import Optional, Iterable, Generator, Any import requests @@ -68,6 +68,7 @@ from ._api_helpers import ( InstallersAPI, DependencyPackagesAPI, + SecretsAPI, BundlesAddonsAPI, EventsAPI, AttributesAPI, @@ -89,7 +90,6 @@ from typing import Union from .typing import ( ServerVersion, - SecretDict, AnyEntityDict, StreamType, ) @@ -209,6 +209,7 @@ def as_user(self, username): class ServerAPI( InstallersAPI, DependencyPackagesAPI, + SecretsAPI, BundlesAddonsAPI, EventsAPI, AttributesAPI, @@ -880,8 +881,7 @@ def get_server_version_tuple(self) -> "ServerVersion": This function only returns first three numbers of version. Returns: - Tuple[int, int, int, Union[str, None], Union[str, None]]: Server - version. + ServerVersion: Server version. """ if self._server_version_tuple is None: @@ -1837,82 +1837,6 @@ def get_default_fields_for_type(self, entity_type: str) -> set[str]: | self.get_attributes_fields_for_type(entity_type) ) - def get_secrets(self) -> list["SecretDict"]: - """Get all secrets. - - Example output:: - - [ - { - "name": "secret_1", - "value": "secret_value_1", - }, - { - "name": "secret_2", - "value": "secret_value_2", - } - ] - - Returns: - list[SecretDict]: List of secret entities. - - """ - response = self.get("secrets") - response.raise_for_status() - return response.data - - def get_secret(self, secret_name: str) -> "SecretDict": - """Get secret by name. - - Example output:: - - { - "name": "secret_name", - "value": "secret_value", - } - - Args: - secret_name (str): Name of secret. - - Returns: - dict[str, str]: Secret entity data. - - """ - response = self.get(f"secrets/{secret_name}") - response.raise_for_status() - return response.data - - def save_secret(self, secret_name: str, secret_value: str): - """Save secret. - - This endpoint can create and update secret. - - Args: - secret_name (str): Name of secret. - secret_value (str): Value of secret. - - """ - response = self.put( - f"secrets/{secret_name}", - name=secret_name, - value=secret_value, - ) - response.raise_for_status() - return response.data - - def delete_secret(self, secret_name: str): - """Delete secret by name. - - Args: - secret_name (str): Name of secret to delete. - - """ - response = self.delete(f"secrets/{secret_name}") - response.raise_for_status() - return response.data - - # Entity getters - def get_rest_entity_by_id( self, project_name: str, From 2957f8c539e8ed1682520eb150291d9a822975c1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:04:51 +0200 Subject: [PATCH 43/53] update public api --- ayon_api/__init__.py | 304 +- ayon_api/_api.py | 7941 +++++++++++++++++++++--------------------- 2 files changed, 4122 insertions(+), 4123 deletions(-) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index a12e27624..1d28e6736 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -77,13 +77,9 @@ get_graphql_schema, get_server_schema, get_schemas, - get_attributes_schema, - reset_attributes_schema, - set_attribute_config, - remove_attribute_config, - get_attributes_for_type, - get_attributes_fields_for_type, get_default_fields_for_type, + get_rest_entity_by_id, + send_batch_operations, get_installers, create_installer, update_installer, @@ -96,38 +92,10 @@ delete_dependency_package, download_dependency_package, upload_dependency_package, - get_bundles, - create_bundle, - update_bundle, - check_bundle_compatibility, - delete_bundle, - get_project_anatomy_presets, - get_default_anatomy_preset_name, - get_project_anatomy_preset, - get_built_in_anatomy_preset, - get_build_in_anatomy_preset, - get_project_root_overrides, - get_project_roots_by_site, - get_project_root_overrides_by_site_id, - get_project_roots_for_site, - get_project_roots_by_site_id, - get_project_roots_by_platform, - get_addon_settings_schema, - get_addon_site_settings_schema, - get_addon_studio_settings, - get_addon_project_settings, - get_addon_settings, - get_addon_site_settings, - get_bundle_settings, - get_addons_studio_settings, - get_addons_project_settings, - get_addons_settings, get_secrets, get_secret, save_secret, delete_secret, - get_rest_entity_by_id, - send_batch_operations, get_actions, trigger_action, get_action_config, @@ -140,6 +108,11 @@ update_activity, delete_activity, send_activities_batch_operations, + get_bundles, + create_bundle, + update_bundle, + check_bundle_compatibility, + delete_bundle, get_addon_endpoint, get_addons_info, get_addon_url, @@ -147,43 +120,33 @@ delete_addon_version, upload_addon_zip, download_addon_private_file, + get_addon_settings_schema, + get_addon_site_settings_schema, + get_addon_studio_settings, + get_addon_project_settings, + get_addon_settings, + get_addon_site_settings, + get_bundle_settings, + get_addons_studio_settings, + get_addons_project_settings, + get_addons_settings, get_event, get_events, update_event, dispatch_event, delete_event, enroll_event_job, - get_full_link_type_name, - get_link_types, - get_link_type, - create_link_type, - delete_link_type, - make_sure_link_type_exists, - create_link, - delete_link, - get_entities_links, - get_folders_links, - get_folder_links, - get_tasks_links, - get_task_links, - get_products_links, - get_product_links, - get_versions_links, - get_version_links, - get_representations_links, - get_representation_links, - get_entity_lists, - get_entity_list_rest, - get_entity_list_by_id, - create_entity_list, - update_entity_list, - delete_entity_list, - get_entity_list_attribute_definitions, - set_entity_list_attribute_definitions, - create_entity_list_item, - update_entity_list_items, - update_entity_list_item, - delete_entity_list_item, + get_attributes_schema, + reset_attributes_schema, + set_attribute_config, + remove_attribute_config, + get_attributes_for_type, + get_attributes_fields_for_type, + get_project_anatomy_presets, + get_default_anatomy_preset_name, + get_project_anatomy_preset, + get_built_in_anatomy_preset, + get_build_in_anatomy_preset, get_rest_project, get_rest_projects, get_project_names, @@ -192,6 +155,12 @@ create_project, update_project, delete_project, + get_project_root_overrides, + get_project_roots_by_site, + get_project_root_overrides_by_site_id, + get_project_roots_for_site, + get_project_roots_by_site_id, + get_project_roots_by_platform, get_rest_folder, get_rest_folders, get_folders_hierarchy, @@ -238,17 +207,6 @@ create_version, update_version, delete_version, - get_thumbnail_by_id, - get_thumbnail, - get_folder_thumbnail, - get_task_thumbnail, - get_version_thumbnail, - get_workfile_thumbnail, - create_thumbnail, - update_thumbnail, - get_workfiles_info, - get_workfile_info, - get_workfile_info_by_id, get_rest_representation, get_representations, get_representation_by_id, @@ -261,6 +219,48 @@ create_representation, update_representation, delete_representation, + get_workfiles_info, + get_workfile_info, + get_workfile_info_by_id, + get_full_link_type_name, + get_link_types, + get_link_type, + create_link_type, + delete_link_type, + make_sure_link_type_exists, + create_link, + delete_link, + get_entities_links, + get_folders_links, + get_folder_links, + get_tasks_links, + get_task_links, + get_products_links, + get_product_links, + get_versions_links, + get_version_links, + get_representations_links, + get_representation_links, + get_entity_lists, + get_entity_list_rest, + get_entity_list_by_id, + create_entity_list, + update_entity_list, + delete_entity_list, + get_entity_list_attribute_definitions, + set_entity_list_attribute_definitions, + create_entity_list_item, + update_entity_list_items, + update_entity_list_item, + delete_entity_list_item, + get_thumbnail_by_id, + get_thumbnail, + get_folder_thumbnail, + get_task_thumbnail, + get_version_thumbnail, + get_workfile_thumbnail, + create_thumbnail, + update_thumbnail, ) @@ -341,13 +341,9 @@ "get_graphql_schema", "get_server_schema", "get_schemas", - "get_attributes_schema", - "reset_attributes_schema", - "set_attribute_config", - "remove_attribute_config", - "get_attributes_for_type", - "get_attributes_fields_for_type", "get_default_fields_for_type", + "get_rest_entity_by_id", + "send_batch_operations", "get_installers", "create_installer", "update_installer", @@ -360,38 +356,10 @@ "delete_dependency_package", "download_dependency_package", "upload_dependency_package", - "get_bundles", - "create_bundle", - "update_bundle", - "check_bundle_compatibility", - "delete_bundle", - "get_project_anatomy_presets", - "get_default_anatomy_preset_name", - "get_project_anatomy_preset", - "get_built_in_anatomy_preset", - "get_build_in_anatomy_preset", - "get_project_root_overrides", - "get_project_roots_by_site", - "get_project_root_overrides_by_site_id", - "get_project_roots_for_site", - "get_project_roots_by_site_id", - "get_project_roots_by_platform", - "get_addon_settings_schema", - "get_addon_site_settings_schema", - "get_addon_studio_settings", - "get_addon_project_settings", - "get_addon_settings", - "get_addon_site_settings", - "get_bundle_settings", - "get_addons_studio_settings", - "get_addons_project_settings", - "get_addons_settings", "get_secrets", "get_secret", "save_secret", "delete_secret", - "get_rest_entity_by_id", - "send_batch_operations", "get_actions", "trigger_action", "get_action_config", @@ -404,6 +372,11 @@ "update_activity", "delete_activity", "send_activities_batch_operations", + "get_bundles", + "create_bundle", + "update_bundle", + "check_bundle_compatibility", + "delete_bundle", "get_addon_endpoint", "get_addons_info", "get_addon_url", @@ -411,43 +384,33 @@ "delete_addon_version", "upload_addon_zip", "download_addon_private_file", + "get_addon_settings_schema", + "get_addon_site_settings_schema", + "get_addon_studio_settings", + "get_addon_project_settings", + "get_addon_settings", + "get_addon_site_settings", + "get_bundle_settings", + "get_addons_studio_settings", + "get_addons_project_settings", + "get_addons_settings", "get_event", "get_events", "update_event", "dispatch_event", "delete_event", "enroll_event_job", - "get_full_link_type_name", - "get_link_types", - "get_link_type", - "create_link_type", - "delete_link_type", - "make_sure_link_type_exists", - "create_link", - "delete_link", - "get_entities_links", - "get_folders_links", - "get_folder_links", - "get_tasks_links", - "get_task_links", - "get_products_links", - "get_product_links", - "get_versions_links", - "get_version_links", - "get_representations_links", - "get_representation_links", - "get_entity_lists", - "get_entity_list_rest", - "get_entity_list_by_id", - "create_entity_list", - "update_entity_list", - "delete_entity_list", - "get_entity_list_attribute_definitions", - "set_entity_list_attribute_definitions", - "create_entity_list_item", - "update_entity_list_items", - "update_entity_list_item", - "delete_entity_list_item", + "get_attributes_schema", + "reset_attributes_schema", + "set_attribute_config", + "remove_attribute_config", + "get_attributes_for_type", + "get_attributes_fields_for_type", + "get_project_anatomy_presets", + "get_default_anatomy_preset_name", + "get_project_anatomy_preset", + "get_built_in_anatomy_preset", + "get_build_in_anatomy_preset", "get_rest_project", "get_rest_projects", "get_project_names", @@ -456,6 +419,12 @@ "create_project", "update_project", "delete_project", + "get_project_root_overrides", + "get_project_roots_by_site", + "get_project_root_overrides_by_site_id", + "get_project_roots_for_site", + "get_project_roots_by_site_id", + "get_project_roots_by_platform", "get_rest_folder", "get_rest_folders", "get_folders_hierarchy", @@ -502,17 +471,6 @@ "create_version", "update_version", "delete_version", - "get_thumbnail_by_id", - "get_thumbnail", - "get_folder_thumbnail", - "get_task_thumbnail", - "get_version_thumbnail", - "get_workfile_thumbnail", - "create_thumbnail", - "update_thumbnail", - "get_workfiles_info", - "get_workfile_info", - "get_workfile_info_by_id", "get_rest_representation", "get_representations", "get_representation_by_id", @@ -525,4 +483,46 @@ "create_representation", "update_representation", "delete_representation", + "get_workfiles_info", + "get_workfile_info", + "get_workfile_info_by_id", + "get_full_link_type_name", + "get_link_types", + "get_link_type", + "create_link_type", + "delete_link_type", + "make_sure_link_type_exists", + "create_link", + "delete_link", + "get_entities_links", + "get_folders_links", + "get_folder_links", + "get_tasks_links", + "get_task_links", + "get_products_links", + "get_product_links", + "get_versions_links", + "get_version_links", + "get_representations_links", + "get_representation_links", + "get_entity_lists", + "get_entity_list_rest", + "get_entity_list_by_id", + "create_entity_list", + "update_entity_list", + "delete_entity_list", + "get_entity_list_attribute_definitions", + "set_entity_list_attribute_definitions", + "create_entity_list_item", + "update_entity_list_items", + "update_entity_list_item", + "delete_entity_list_item", + "get_thumbnail_by_id", + "get_thumbnail", + "get_folder_thumbnail", + "get_task_thumbnail", + "get_version_thumbnail", + "get_workfile_thumbnail", + "create_thumbnail", + "update_thumbnail", ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index e30357663..475396d29 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -19,13 +19,8 @@ SERVER_URL_ENV_KEY, SERVER_API_ENV_KEY, ) -from .server_api import ( - ServerAPI, - RequestType, - GraphQlResponse, - _PLACEHOLDER, -) from .exceptions import FailedServiceInit +from ._api_helpers.base import _PLACEHOLDER from .utils import ( NOT_SET, SortOrder, @@ -35,6 +30,11 @@ RepresentationParents, RepresentationHierarchy, ) +from .server_api import ( + ServerAPI, + RequestType, + GraphQlResponse, +) if typing.TYPE_CHECKING: from typing import Union @@ -705,8 +705,7 @@ def get_server_version_tuple() -> "ServerVersion": This function only returns first three numbers of version. Returns: - Tuple[int, int, int, Union[str, None], Union[str, None]]: Server - version. + ServerVersion: Server version. """ con = get_server_api_connection() @@ -1162,131 +1161,88 @@ def get_schemas() -> dict[str, Any]: return con.get_schemas() -def get_attributes_schema( - use_cache: bool = True, -) -> "AttributesSchemaDict": - con = get_server_api_connection() - return con.get_attributes_schema( - use_cache=use_cache, - ) - - -def reset_attributes_schema(): - con = get_server_api_connection() - return con.reset_attributes_schema() - - -def set_attribute_config( - attribute_name: str, - data: "AttributeSchemaDataDict", - scope: list["AttributeScope"], - position: Optional[int] = None, - builtin: bool = False, -): - con = get_server_api_connection() - return con.set_attribute_config( - attribute_name=attribute_name, - data=data, - scope=scope, - position=position, - builtin=builtin, - ) - - -def remove_attribute_config( - attribute_name: str, -): - """Remove attribute from server. - - This can't be un-done, please use carefully. - - Args: - attribute_name (str): Name of attribute to remove. - - """ - con = get_server_api_connection() - return con.remove_attribute_config( - attribute_name=attribute_name, - ) - - -def get_attributes_for_type( - entity_type: "AttributeScope", -) -> dict[str, "AttributeSchemaDict"]: - """Get attribute schemas available for an entity type. - - Example:: +def get_default_fields_for_type( + entity_type: str, +) -> set[str]: + """Default fields for entity type. - ``` - # Example attribute schema - { - # Common - "type": "integer", - "title": "Clip Out", - "description": null, - "example": 1, - "default": 1, - # These can be filled based on value of 'type' - "gt": null, - "ge": null, - "lt": null, - "le": null, - "minLength": null, - "maxLength": null, - "minItems": null, - "maxItems": null, - "regex": null, - "enum": null - } - ``` + Returns most of commonly used fields from server. Args: - entity_type (str): Entity type for which should be attributes - received. + entity_type (str): Name of entity type. Returns: - dict[str, dict[str, Any]]: Attribute schemas that are available - for entered entity type. + set[str]: Fields that should be queried from server. """ con = get_server_api_connection() - return con.get_attributes_for_type( + return con.get_default_fields_for_type( entity_type=entity_type, ) -def get_attributes_fields_for_type( - entity_type: "AttributeScope", -) -> set[str]: - """Prepare attribute fields for entity type. +def get_rest_entity_by_id( + project_name: str, + entity_type: str, + entity_id: str, +) -> Optional["AnyEntityDict"]: + """Get entity using REST on a project by its id. + + Args: + project_name (str): Name of project where entity is. + entity_type (Literal["folder", "task", "product", "version"]): The + entity type which should be received. + entity_id (str): Id of entity. Returns: - set[str]: Attributes fields for entity type. + Optional[AnyEntityDict]: Received entity data. """ con = get_server_api_connection() - return con.get_attributes_fields_for_type( + return con.get_rest_entity_by_id( + project_name=project_name, entity_type=entity_type, + entity_id=entity_id, ) -def get_default_fields_for_type( - entity_type: str, -) -> set[str]: - """Default fields for entity type. +def send_batch_operations( + project_name: str, + operations: list[dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True, +) -> list[dict[str, Any]]: + """Post multiple CRUD operations to server. - Returns most of commonly used fields from server. + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. Args: - entity_type (str): Name of entity type. + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. Returns: - set[str]: Fields that should be queried from server. + list[dict[str, Any]]: Operations result with process details. """ con = get_server_api_connection() - return con.get_default_fields_for_type( - entity_type=entity_type, + return con.send_batch_operations( + project_name=project_name, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) @@ -1639,3552 +1595,3883 @@ def upload_dependency_package( ) -def get_bundles() -> "BundlesInfoDict": - """Server bundles with basic information. +def get_secrets() -> list["SecretDict"]: + """Get all secrets. - This is example output:: + 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" - } + [ + { + "name": "secret_1", + "value": "secret_value_1", + }, + { + "name": "secret_2", + "value": "secret_value_2", + } + ] Returns: - dict[str, Any]: Server bundles with basic information. + list[SecretDict]: List of secret entities. """ con = get_server_api_connection() - return con.get_bundles() - - -def create_bundle( - name: str, - addon_versions: dict[str, str], - installer_version: str, - dependency_packages: Optional[dict[str, str]] = None, - is_production: Optional[bool] = None, - is_staging: Optional[bool] = None, - is_dev: Optional[bool] = None, - dev_active_user: Optional[str] = None, - dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, -): - """Create bundle on server. + return con.get_secrets() - Bundle cannot be changed once is created. Only isProduction, isStaging - and dependency packages can change after creation. In case dev bundle - is created, it is possible to change anything, but it is not possible - to mark bundle as dev and production or staging at the same time. - Development addon config can define custom path to client code. It is - used only for dev bundles. +def get_secret( + secret_name: str, +) -> "SecretDict": + """Get secret by name. - Example of 'dev_addons_config':: + Example output:: - ```json { - "core": { - "enabled": true, - "path": "/path/to/ayon-core/client" - } + "name": "secret_name", + "value": "secret_value", } - ``` 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. - is_dev (Optional[bool]): Bundle will be marked as dev. - dev_active_user (Optional[str]): Username that will be assigned - to dev bundle. Can be used only if 'is_dev' is set to 'True'. - dev_addons_config (Optional[dict[str, Any]]): Configuration for - dev addons. Can be used only if 'is_dev' is set to 'True'. + secret_name (str): Name of secret. + + Returns: + dict[str, str]: Secret entity data. """ con = get_server_api_connection() - return con.create_bundle( - name=name, - addon_versions=addon_versions, - installer_version=installer_version, - dependency_packages=dependency_packages, - is_production=is_production, - is_staging=is_staging, - is_dev=is_dev, - dev_active_user=dev_active_user, - dev_addons_config=dev_addons_config, + return con.get_secret( + secret_name=secret_name, ) -def update_bundle( - bundle_name: str, - addon_versions: Optional[dict[str, str]] = None, - installer_version: Optional[str] = None, - dependency_packages: Optional[dict[str, str]] = None, - is_production: Optional[bool] = None, - is_staging: Optional[bool] = None, - is_dev: Optional[bool] = None, - dev_active_user: Optional[str] = None, - dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, +def save_secret( + secret_name: str, + secret_value: str, ): - """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. - addon_versions (Optional[dict[str, str]]): Addon versions, - possible only for dev bundles. - installer_version (Optional[str]): Installer version, possible - only for dev bundles. - 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. - is_dev (Optional[bool]): Bundle will be marked as dev. - dev_active_user (Optional[str]): Username that will be assigned - to dev bundle. Can be used only for dev bundles. - dev_addons_config (Optional[dict[str, Any]]): Configuration for - dev addons. Can be used only for dev bundles. - - """ - con = get_server_api_connection() - return con.update_bundle( - bundle_name=bundle_name, - addon_versions=addon_versions, - installer_version=installer_version, - dependency_packages=dependency_packages, - is_production=is_production, - is_staging=is_staging, - is_dev=is_dev, - dev_active_user=dev_active_user, - dev_addons_config=dev_addons_config, - ) - - -def check_bundle_compatibility( - name: str, - addon_versions: dict[str, str], - installer_version: str, - dependency_packages: Optional[dict[str, str]] = None, - is_production: Optional[bool] = None, - is_staging: Optional[bool] = None, - is_dev: Optional[bool] = None, - dev_active_user: Optional[str] = None, - dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, -) -> dict[str, Any]: - """Check bundle compatibility. + """Save secret. - Can be used as per-flight validation before creating bundle. + This endpoint can create and update secret. 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. - is_dev (Optional[bool]): Bundle will be marked as dev. - dev_active_user (Optional[str]): Username that will be assigned - to dev bundle. Can be used only if 'is_dev' is set to 'True'. - dev_addons_config (Optional[dict[str, Any]]): Configuration for - dev addons. Can be used only if 'is_dev' is set to 'True'. - - Returns: - dict[str, Any]: Server response, with 'success' and 'issues'. + secret_name (str): Name of secret. + secret_value (str): Value of secret. """ con = get_server_api_connection() - return con.check_bundle_compatibility( - name=name, - addon_versions=addon_versions, - installer_version=installer_version, - dependency_packages=dependency_packages, - is_production=is_production, - is_staging=is_staging, - is_dev=is_dev, - dev_active_user=dev_active_user, - dev_addons_config=dev_addons_config, + return con.save_secret( + secret_name=secret_name, + secret_value=secret_value, ) -def delete_bundle( - bundle_name: str, +def delete_secret( + secret_name: str, ): - """Delete bundle from server. + """Delete secret by name. Args: - bundle_name (str): Name of bundle to delete. + secret_name (str): Name of secret to delete. """ con = get_server_api_connection() - return con.delete_bundle( - bundle_name=bundle_name, + return con.delete_secret( + secret_name=secret_name, ) -def get_project_anatomy_presets() -> list["AnatomyPresetDict"]: - """Anatomy presets available on server. - - Content has basic information about presets. Example output:: +def get_actions( + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[list[str]] = None, + entity_subtypes: Optional[list[str]] = None, + form_data: Optional[dict[str, Any]] = None, + *, + variant: Optional[str] = None, + mode: Optional["ActionModeType"] = None, +) -> list["ActionManifestdict"]: + """Get actions for a context. - [ - { - "name": "netflix_VFX", - "primary": false, - "version": "1.0.0" - }, - { - ... - }, - ... - ] + Args: + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[list[str]]): list of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[list[str]]): list of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + mode (Optional[ActionModeType]): Action modes. Returns: - list[dict[str, str]]: Anatomy presets available on server. + list[ActionManifestdict]: list of action manifests. """ con = get_server_api_connection() - return con.get_project_anatomy_presets() - + return con.get_actions( + project_name=project_name, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + mode=mode, + ) -def get_default_anatomy_preset_name() -> str: - """Name of default anatomy preset. - Primary preset is used as default preset. But when primary preset is - not set a built-in is used instead. Built-in preset is named '_'. +def trigger_action( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[list[str]] = None, + entity_subtypes: Optional[list[str]] = None, + form_data: Optional[dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionTriggerResponse": + """Trigger action. - Returns: - str: Name of preset that can be used by - 'get_project_anatomy_preset'. + Args: + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[list[str]]): list of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[list[str]]): list of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. """ con = get_server_api_connection() - return con.get_default_anatomy_preset_name() - + return con.trigger_action( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, + project_name=project_name, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + ) -def get_project_anatomy_preset( - preset_name: Optional[str] = None, -) -> "AnatomyPresetDict": - """Anatomy preset values by name. - Get anatomy preset values by preset name. Primary preset is returned - if preset name is set to 'None'. +def get_action_config( + identifier: str, + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[list[str]] = None, + entity_subtypes: Optional[list[str]] = None, + form_data: Optional[dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Get action configuration. Args: - preset_name (Optional[str]): Preset name. + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[list[str]]): list of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[list[str]]): list of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. Returns: - AnatomyPresetDict: Anatomy preset values. + ActionConfigResponse: Action configuration data. """ con = get_server_api_connection() - return con.get_project_anatomy_preset( - preset_name=preset_name, + return con.get_action_config( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, + project_name=project_name, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, ) -def get_built_in_anatomy_preset() -> "AnatomyPresetDict": - """Get built-in anatomy preset. +def set_action_config( + identifier: str, + addon_name: str, + addon_version: str, + value: dict[str, Any], + project_name: Optional[str] = None, + entity_type: Optional["ActionEntityTypes"] = None, + entity_ids: Optional[list[str]] = None, + entity_subtypes: Optional[list[str]] = None, + form_data: Optional[dict[str, Any]] = None, + *, + variant: Optional[str] = None, +) -> "ActionConfigResponse": + """Set action configuration. - Returns: - AnatomyPresetDict: Built-in anatomy preset. - - """ - con = get_server_api_connection() - return con.get_built_in_anatomy_preset() + Args: + identifier (str): Identifier of the action. + addon_name (str): Name of the addon. + addon_version (str): Version of the addon. + value (Optional[dict[str, Any]]): Value of the action + configuration. + project_name (Optional[str]): Name of the project. None for global + actions. + entity_type (Optional[ActionEntityTypes]): Entity type where the + action is triggered. None for global actions. + entity_ids (Optional[list[str]]): list of entity ids where the + action is triggered. None for global actions. + entity_subtypes (Optional[list[str]]): list of entity subtypes + folder types for folder ids, task types for tasks ids. + form_data (Optional[dict[str, Any]]): Form data of the action. + variant (Optional[str]): Settings variant. + Returns: + ActionConfigResponse: New action configuration data. -def get_build_in_anatomy_preset() -> "AnatomyPresetDict": + """ con = get_server_api_connection() - return con.get_build_in_anatomy_preset() - - -def get_project_root_overrides( - project_name: str, -) -> dict[str, dict[str, str]]: - """Root overrides per site name. + return con.set_action_config( + identifier=identifier, + addon_name=addon_name, + addon_version=addon_version, + value=value, + project_name=project_name, + entity_type=entity_type, + entity_ids=entity_ids, + entity_subtypes=entity_subtypes, + form_data=form_data, + variant=variant, + ) - Method is based on logged user and can't be received for any other - user on server. - Output will contain only roots per site id used by logged user. +def take_action( + action_token: str, +) -> "ActionTakeResponse": + """Take action metadata using an action token. Args: - project_name (str): Name of project. + action_token (str): AYON launcher action token. Returns: - dict[str, dict[str, str]]: Root values by root name by site id. + ActionTakeResponse: Action metadata describing how to launch + action. """ con = get_server_api_connection() - return con.get_project_root_overrides( - project_name=project_name, + return con.take_action( + action_token=action_token, ) -def get_project_roots_by_site( - project_name: str, -) -> dict[str, dict[str, str]]: - """Root overrides per site name. - - Method is based on logged user and can't be received for any other - user on server. - - Output will contain only roots per site id used by logged user. - - Deprecated: - Use 'get_project_root_overrides' instead. Function - deprecated since 1.0.6 +def abort_action( + action_token: str, + message: Optional[str] = None, +) -> None: + """Abort action using an action token. Args: - project_name (str): Name of project. - - Returns: - dict[str, dict[str, str]]: Root values by root name by site id. + action_token (str): AYON launcher action token. + message (Optional[str]): Message to display in the UI. """ con = get_server_api_connection() - return con.get_project_roots_by_site( - project_name=project_name, + return con.abort_action( + action_token=action_token, + message=message, ) -def get_project_root_overrides_by_site_id( +def get_activities( project_name: str, - site_id: Optional[str] = None, -) -> dict[str, str]: - """Root overrides for site. - - If site id is not passed a site set in current api object is used - instead. + activity_ids: Optional[Iterable[str]] = None, + activity_types: Optional[Iterable["ActivityType"]] = None, + entity_ids: Optional[Iterable[str]] = None, + entity_names: Optional[Iterable[str]] = None, + entity_type: Optional[str] = None, + changed_after: Optional[str] = None, + changed_before: Optional[str] = None, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + fields: Optional[Iterable[str]] = None, + limit: Optional[int] = None, + order: Optional[SortOrder] = None, +) -> Generator[dict[str, Any], None, None]: + """Get activities from server with filtering options. Args: - project_name (str): Name of project. - site_id (Optional[str]): Site id for which want to receive - site overrides. + project_name (str): Project on which activities happened. + activity_ids (Optional[Iterable[str]]): Activity ids. + activity_types (Optional[Iterable[ActivityType]]): Activity types. + entity_ids (Optional[Iterable[str]]): Entity ids. + entity_names (Optional[Iterable[str]]): Entity names. + entity_type (Optional[str]): Entity type. + changed_after (Optional[str]): Return only activities changed + after given iso datetime string. + changed_before (Optional[str]): Return only activities changed + before given iso datetime string. + reference_types (Optional[Iterable[ActivityReferenceType]]): + Reference types filter. Defaults to `['origin']`. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. + limit (Optional[int]): Limit number of activities to be fetched. + order (Optional[SortOrder]): Order activities in ascending + or descending order. It is recommended to set 'limit' + when used descending. Returns: - dict[str, str]: Root values by root name or None if - site does not have overrides. + Generator[dict[str, Any]]: Available activities matching filters. """ con = get_server_api_connection() - return con.get_project_root_overrides_by_site_id( + return con.get_activities( project_name=project_name, - site_id=site_id, + activity_ids=activity_ids, + activity_types=activity_types, + entity_ids=entity_ids, + entity_names=entity_names, + entity_type=entity_type, + changed_after=changed_after, + changed_before=changed_before, + reference_types=reference_types, + fields=fields, + limit=limit, + order=order, ) -def get_project_roots_for_site( +def get_activity_by_id( project_name: str, - site_id: Optional[str] = None, -) -> dict[str, str]: - """Root overrides for site. - - If site id is not passed a site set in current api object is used - instead. + activity_id: str, + reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + fields: Optional[Iterable[str]] = None, +) -> Optional[dict[str, Any]]: + """Get activity by id. - Deprecated: - Use 'get_project_root_overrides_by_site_id' instead. Function - deprecated since 1.0.6 Args: - project_name (str): Name of project. - site_id (Optional[str]): Site id for which want to receive - site overrides. + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + reference_types: Optional[Iterable[ActivityReferenceType]]: Filter + by reference types. + fields (Optional[Iterable[str]]): Fields that should be received + for each activity. Returns: - dict[str, str]: Root values by root name, root name is not - available if it does not have overrides. + Optional[dict[str, Any]]: Activity data or None if activity is not + found. """ con = get_server_api_connection() - return con.get_project_roots_for_site( + return con.get_activity_by_id( project_name=project_name, - site_id=site_id, + activity_id=activity_id, + reference_types=reference_types, + fields=fields, ) -def get_project_roots_by_site_id( +def create_activity( project_name: str, - site_id: Optional[str] = None, -) -> dict[str, str]: - """Root values for a site. - - If site id is not passed a site set in current api object is used - instead. If site id is not available, default roots are returned - for current platform. + entity_id: str, + entity_type: str, + activity_type: "ActivityType", + activity_id: Optional[str] = None, + body: Optional[str] = None, + file_ids: Optional[list[str]] = None, + timestamp: Optional[str] = None, + data: Optional[dict[str, Any]] = None, +) -> str: + """Create activity on a project. Args: - project_name (str): Name of project. - site_id (Optional[str]): Site id for which want to receive - root values. + project_name (str): Project on which activity happened. + entity_id (str): Entity id. + entity_type (str): Entity type. + activity_type (ActivityType): Activity type. + activity_id (Optional[str]): Activity id. + body (Optional[str]): Activity body. + file_ids (Optional[list[str]]): List of file ids attached + to activity. + timestamp (Optional[str]): Activity timestamp. + data (Optional[dict[str, Any]]): Additional data. Returns: - dict[str, str]: Root values. + str: Activity id. """ con = get_server_api_connection() - return con.get_project_roots_by_site_id( + return con.create_activity( project_name=project_name, - site_id=site_id, + entity_id=entity_id, + entity_type=entity_type, + activity_type=activity_type, + activity_id=activity_id, + body=body, + file_ids=file_ids, + timestamp=timestamp, + data=data, ) -def get_project_roots_by_platform( +def update_activity( project_name: str, - platform_name: Optional[str] = None, -) -> dict[str, str]: - """Root values for a site. - - If platform name is not passed current platform name is used instead. - - This function does return root values without site overrides. It is - possible to use the function to receive default root values. + activity_id: str, + body: Optional[str] = None, + file_ids: Optional[list[str]] = None, + append_file_ids: Optional[bool] = False, + data: Optional[dict[str, Any]] = None, +): + """Update activity by id. Args: - project_name (str): Name of project. - platform_name (Optional[Literal["windows", "linux", "darwin"]]): - Platform name for which want to receive root values. Current - platform name is used if not passed. - - Returns: - dict[str, str]: Root values. + project_name (str): Project on which activity happened. + activity_id (str): Activity id. + body (str): Activity body. + file_ids (Optional[list[str]]): List of file ids attached + to activity. + append_file_ids (Optional[bool]): Append file ids to existing + list of file ids. + data (Optional[dict[str, Any]]): Update data in activity. """ con = get_server_api_connection() - return con.get_project_roots_by_platform( + return con.update_activity( project_name=project_name, - platform_name=platform_name, + activity_id=activity_id, + body=body, + file_ids=file_ids, + append_file_ids=append_file_ids, + data=data, ) -def get_addon_settings_schema( - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, -) -> dict[str, Any]: - """Sudio/Project settings schema of an addon. - - Project schema may look differently as some enums are based on project - values. +def delete_activity( + project_name: str, + activity_id: str, +): + """Delete activity by id. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - project_name (Optional[str]): Schema for specific project or - default studio schemas. - - Returns: - dict[str, Any]: Schema of studio/project settings. + project_name (str): Project on which activity happened. + activity_id (str): Activity id to remove. """ con = get_server_api_connection() - return con.get_addon_settings_schema( - addon_name=addon_name, - addon_version=addon_version, + return con.delete_activity( project_name=project_name, + activity_id=activity_id, ) -def get_addon_site_settings_schema( - addon_name: str, - addon_version: str, -) -> dict[str, Any]: - """Site settings schema of an addon. +def send_activities_batch_operations( + project_name: str, + operations: list[dict[str, Any]], + can_fail: bool = False, + raise_on_fail: bool = True, +) -> list[dict[str, Any]]: + """Post multiple CRUD activities operations to server. + + When multiple changes should be made on server side this is the best + way to go. It is possible to pass multiple operations to process on a + server side and do the changes in a transaction. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. + project_name (str): On which project should be operations + processed. + operations (list[dict[str, Any]]): Operations to be processed. + can_fail (Optional[bool]): Server will try to process all + operations even if one of them fails. + raise_on_fail (Optional[bool]): Raise exception if an operation + fails. You can handle failed operations on your own + when set to 'False'. + + Raises: + ValueError: Operations can't be converted to json string. + FailedOperations: When output does not contain server operations + or 'raise_on_fail' is enabled and any operation fails. Returns: - dict[str, Any]: Schema of site settings. + list[dict[str, Any]]: Operations result with process details. """ con = get_server_api_connection() - return con.get_addon_site_settings_schema( - addon_name=addon_name, - addon_version=addon_version, + return con.send_activities_batch_operations( + project_name=project_name, + operations=operations, + can_fail=can_fail, + raise_on_fail=raise_on_fail, ) -def get_addon_studio_settings( - addon_name: str, - addon_version: str, - variant: Optional[str] = None, -) -> dict[str, Any]: - """Addon studio settings. +def get_bundles() -> "BundlesInfoDict": + """Server bundles with basic information. - Receive studio settings for specific version of an addon. + This is example output:: - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. + { + "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]: Addon settings. + dict[str, Any]: Server bundles with basic information. """ con = get_server_api_connection() - return con.get_addon_studio_settings( - addon_name=addon_name, - addon_version=addon_version, - variant=variant, - ) + return con.get_bundles() -def get_addon_project_settings( - addon_name: str, - addon_version: str, - project_name: str, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, -) -> dict[str, Any]: - """Addon project settings. +def create_bundle( + name: str, + addon_versions: dict[str, str], + installer_version: str, + dependency_packages: Optional[dict[str, str]] = None, + is_production: Optional[bool] = None, + is_staging: Optional[bool] = None, + is_dev: Optional[bool] = None, + dev_active_user: Optional[str] = None, + dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, +): + """Create bundle on server. - Receive project settings for specific version of an addon. The settings - may be with site overrides when enabled. + Bundle cannot be changed once is created. Only isProduction, isStaging + and dependency packages can change after creation. In case dev bundle + is created, it is possible to change anything, but it is not possible + to mark bundle as dev and production or staging at the same time. - Site id is filled with current connection site id if not passed. To - make sure any site id is used set 'use_site' to 'False'. + Development addon config can define custom path to client code. It is + used only for dev bundles. - Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - project_name (str): Name of project for which the settings are - received. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Name of site which is used for site - overrides. Is filled with connection 'site_id' attribute - if not passed. - use_site (Optional[bool]): To force disable option of using site - overrides set to 'False'. In that case won't be applied - any site overrides. + Example of 'dev_addons_config':: - Returns: - dict[str, Any]: Addon settings. + ```json + { + "core": { + "enabled": true, + "path": "/path/to/ayon-core/client" + } + } + ``` + + 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. + is_dev (Optional[bool]): Bundle will be marked as dev. + dev_active_user (Optional[str]): Username that will be assigned + to dev bundle. Can be used only if 'is_dev' is set to 'True'. + dev_addons_config (Optional[dict[str, Any]]): Configuration for + dev addons. Can be used only if 'is_dev' is set to 'True'. """ con = get_server_api_connection() - return con.get_addon_project_settings( - addon_name=addon_name, - addon_version=addon_version, - project_name=project_name, - variant=variant, - site_id=site_id, - use_site=use_site, + return con.create_bundle( + name=name, + addon_versions=addon_versions, + installer_version=installer_version, + dependency_packages=dependency_packages, + is_production=is_production, + is_staging=is_staging, + is_dev=is_dev, + dev_active_user=dev_active_user, + dev_addons_config=dev_addons_config, ) -def get_addon_settings( - addon_name: str, - addon_version: str, - project_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, -) -> dict[str, Any]: - """Receive addon settings. +def update_bundle( + bundle_name: str, + addon_versions: Optional[dict[str, str]] = None, + installer_version: Optional[str] = None, + dependency_packages: Optional[dict[str, str]] = None, + is_production: Optional[bool] = None, + is_staging: Optional[bool] = None, + is_dev: Optional[bool] = None, + dev_active_user: Optional[str] = None, + dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, +): + """Update bundle on server. - Receive addon settings based on project name value. Some arguments may - be ignored if 'project_name' is set to 'None'. + Dependency packages can be update only for single platform. Others + will be left untouched. Use 'None' value to unset dependency package + from bundle. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - project_name (Optional[str]): Name of project for which the - settings are received. A studio settings values are received - if is 'None'. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Name of site which is used for site - overrides. Is filled with connection 'site_id' attribute - if not passed. - use_site (Optional[bool]): To force disable option of using - site overrides set to 'False'. In that case won't be applied - any site overrides. - - Returns: - dict[str, Any]: Addon settings. - - """ - con = get_server_api_connection() - return con.get_addon_settings( - addon_name=addon_name, - addon_version=addon_version, - project_name=project_name, - variant=variant, - site_id=site_id, - use_site=use_site, + bundle_name (str): Name of bundle. + addon_versions (Optional[dict[str, str]]): Addon versions, + possible only for dev bundles. + installer_version (Optional[str]): Installer version, possible + only for dev bundles. + 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. + is_dev (Optional[bool]): Bundle will be marked as dev. + dev_active_user (Optional[str]): Username that will be assigned + to dev bundle. Can be used only for dev bundles. + dev_addons_config (Optional[dict[str, Any]]): Configuration for + dev addons. Can be used only for dev bundles. + + """ + con = get_server_api_connection() + return con.update_bundle( + bundle_name=bundle_name, + addon_versions=addon_versions, + installer_version=installer_version, + dependency_packages=dependency_packages, + is_production=is_production, + is_staging=is_staging, + is_dev=is_dev, + dev_active_user=dev_active_user, + dev_addons_config=dev_addons_config, ) -def get_addon_site_settings( - addon_name: str, - addon_version: str, - site_id: Optional[str] = None, +def check_bundle_compatibility( + name: str, + addon_versions: dict[str, str], + installer_version: str, + dependency_packages: Optional[dict[str, str]] = None, + is_production: Optional[bool] = None, + is_staging: Optional[bool] = None, + is_dev: Optional[bool] = None, + dev_active_user: Optional[str] = None, + dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, ) -> dict[str, Any]: - """Site settings of an addon. + """Check bundle compatibility. - If site id is not available an empty dictionary is returned. + Can be used as per-flight validation before creating bundle. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - site_id (Optional[str]): Name of site for which should be settings - returned. using 'site_id' attribute if not passed. + 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. + is_dev (Optional[bool]): Bundle will be marked as dev. + dev_active_user (Optional[str]): Username that will be assigned + to dev bundle. Can be used only if 'is_dev' is set to 'True'. + dev_addons_config (Optional[dict[str, Any]]): Configuration for + dev addons. Can be used only if 'is_dev' is set to 'True'. Returns: - dict[str, Any]: Site settings. + dict[str, Any]: Server response, with 'success' and 'issues'. """ con = get_server_api_connection() - return con.get_addon_site_settings( - addon_name=addon_name, - addon_version=addon_version, - site_id=site_id, + return con.check_bundle_compatibility( + name=name, + addon_versions=addon_versions, + installer_version=installer_version, + dependency_packages=dependency_packages, + is_production=is_production, + is_staging=is_staging, + is_dev=is_dev, + dev_active_user=dev_active_user, + dev_addons_config=dev_addons_config, ) -def get_bundle_settings( - bundle_name: Optional[str] = None, - project_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, -) -> dict[str, Any]: - """Get complete set of settings for given data. - - If project is not passed then studio settings are returned. If variant - is not passed 'default_settings_variant' is used. If bundle name is - not passed then current production/staging bundle is used, based on - variant value. - - Output contains addon settings and site settings in single dictionary. - - Todos: - - test how it behaves if there is not any bundle. - - test how it behaves if there is not any production/staging - bundle. - - Example output:: - - { - "addons": [ - { - "name": "addon-name", - "version": "addon-version", - "settings": {...}, - "siteSettings": {...} - } - ] - } +def delete_bundle( + bundle_name: str, +): + """Delete bundle from server. - Returns: - dict[str, Any]: All settings for single bundle. + Args: + bundle_name (str): Name of bundle to delete. """ con = get_server_api_connection() - return con.get_bundle_settings( + return con.delete_bundle( bundle_name=bundle_name, - project_name=project_name, - variant=variant, - site_id=site_id, - use_site=use_site, ) -def get_addons_studio_settings( - bundle_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, - only_values: bool = True, -) -> dict[str, Any]: - """All addons settings in one bulk. +def get_addon_endpoint( + addon_name: str, + addon_version: str, + *subpaths, +) -> str: + """Calculate endpoint to addon route. - Warnings: - Behavior of this function changed with AYON server version 0.3.0. - Structure of output from server changed. If using - 'only_values=True' then output should be same as before. + Examples: + >>> from ayon_api import ServerAPI + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'addons/example/1.0.0/private/my.zip' Args: - bundle_name (Optional[str]): Name of bundle for which should be - settings received. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Site id for which want to receive - site overrides. - use_site (bool): To force disable option of using site overrides - set to 'False'. In that case won't be applied any site - overrides. - only_values (Optional[bool]): Output will contain only settings - values without metadata about addons. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. Returns: - dict[str, Any]: Settings of all addons on server. + str: Final url. """ con = get_server_api_connection() - return con.get_addons_studio_settings( - bundle_name=bundle_name, - variant=variant, - site_id=site_id, - use_site=use_site, - only_values=only_values, + return con.get_addon_endpoint( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, ) -def get_addons_project_settings( - project_name: str, - bundle_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, - only_values: bool = True, -) -> dict[str, Any]: - """Project settings of all addons. +def get_addons_info( + details: bool = True, +) -> "AddonsInfoDict": + """Get information about addons available on server. - Server returns information about used addon versions, so full output - looks like: + Args: + details (Optional[bool]): Detailed data with information how + to get client code. - ```json - { - "settings": {...}, - "addons": {...} - } - ``` + """ + con = get_server_api_connection() + return con.get_addons_info( + details=details, + ) - The output can be limited to only values. To do so is 'only_values' - argument which is by default set to 'True'. In that case output - contains only value of 'settings' key. - Warnings: - Behavior of this function changed with AYON server version 0.3.0. - Structure of output from server changed. If using - 'only_values=True' then output should be same as before. +def get_addon_url( + addon_name: str, + addon_version: str, + *subpaths, + use_rest: bool = True, +) -> str: + """Calculate url to addon route. + + Examples: + >>> from ayon_api import ServerAPI + >>> api = ServerAPI("https://your.url.com") + >>> api.get_addon_url( + ... "example", "1.0.0", "private", "my.zip") + 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' Args: - project_name (str): Name of project for which are settings - received. - bundle_name (Optional[str]): Name of bundle for which should be - settings received. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Site id for which want to receive - site overrides. - use_site (bool): To force disable option of using site overrides - set to 'False'. In that case won't be applied any site - overrides. - only_values (Optional[bool]): Output will contain only settings - values without metadata about addons. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + *subpaths (str): Any amount of subpaths that are added to + addon url. + use_rest (Optional[bool]): Use rest endpoint. Returns: - dict[str, Any]: Settings of all addons on server for passed - project. + str: Final url. """ con = get_server_api_connection() - return con.get_addons_project_settings( - project_name=project_name, - bundle_name=bundle_name, - variant=variant, - site_id=site_id, - use_site=use_site, - only_values=only_values, + return con.get_addon_url( + addon_name=addon_name, + addon_version=addon_version, + *subpaths, + use_rest=use_rest, ) -def get_addons_settings( - bundle_name: Optional[str] = None, - project_name: Optional[str] = None, - variant: Optional[str] = None, - site_id: Optional[str] = None, - use_site: bool = True, - only_values: bool = True, -) -> dict[str, Any]: - """Universal function to receive all addon settings. - - Based on 'project_name' will receive studio settings or project - settings. In case project is not passed is 'site_id' ignored. +def delete_addon( + addon_name: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon from server. - Warnings: - Behavior of this function changed with AYON server version 0.3.0. - Structure of output from server changed. If using - 'only_values=True' then output should be same as before. + Delete all versions of addon from server. Args: - bundle_name (Optional[str]): Name of bundle for which should be - settings received. - project_name (Optional[str]): Name of project for which should be - settings received. - variant (Optional[Literal['production', 'staging']]): Name of - settings variant. Used 'default_settings_variant' by default. - site_id (Optional[str]): Id of site for which want to receive - site overrides. - use_site (Optional[bool]): To force disable option of using site - overrides set to 'False'. In that case won't be applied - any site overrides. - only_values (Optional[bool]): Only settings values will be - returned. By default, is set to 'True'. + addon_name (str): Addon name. + purge (Optional[bool]): Purge all data related to the addon. """ con = get_server_api_connection() - return con.get_addons_settings( - bundle_name=bundle_name, - project_name=project_name, - variant=variant, - site_id=site_id, - use_site=use_site, - only_values=only_values, + return con.delete_addon( + addon_name=addon_name, + purge=purge, ) -def get_secrets() -> list["SecretDict"]: - """Get all secrets. - - Example output:: +def delete_addon_version( + addon_name: str, + addon_version: str, + purge: Optional[bool] = None, +) -> None: + """Delete addon version from server. - [ - { - "name": "secret_1", - "value": "secret_value_1", - }, - { - "name": "secret_2", - "value": "secret_value_2", - } - ] + Delete all versions of addon from server. - Returns: - list[SecretDict]: List of secret entities. + Args: + addon_name (str): Addon name. + addon_version (str): Addon version. + purge (Optional[bool]): Purge all data related to the addon. """ con = get_server_api_connection() - return con.get_secrets() + return con.delete_addon_version( + addon_name=addon_name, + addon_version=addon_version, + purge=purge, + ) -def get_secret( - secret_name: str, -) -> "SecretDict": - """Get secret by name. +def upload_addon_zip( + src_filepath: str, + progress: Optional[TransferProgress] = None, +): + """Upload addon zip file to server. + + File is validated on server. If it is valid, it is installed. It will + create an event job which can be tracked (tracking part is not + implemented yet). Example output:: - { - "name": "secret_name", - "value": "secret_value", - } + {'eventId': 'a1bfbdee27c611eea7580242ac120003'} Args: - secret_name (str): Name of secret. + src_filepath (str): Path to a zip file. + progress (Optional[TransferProgress]): Object to keep track about + upload state. Returns: - dict[str, str]: Secret entity data. + dict[str, Any]: Response data from server. """ con = get_server_api_connection() - return con.get_secret( - secret_name=secret_name, + return con.upload_addon_zip( + src_filepath=src_filepath, + progress=progress, ) -def save_secret( - secret_name: str, - secret_value: str, -): - """Save secret. +def download_addon_private_file( + addon_name: str, + addon_version: str, + filename: str, + destination_dir: str, + destination_filename: Optional[str] = None, + chunk_size: Optional[int] = None, + progress: Optional[TransferProgress] = None, +) -> str: + """Download a file from addon private files. - This endpoint can create and update secret. + This method requires to have authorized token available. Private files + are not under '/api' restpoint. Args: - secret_name (str): Name of secret. - secret_value (str): Value of secret. + addon_name (str): Addon name. + addon_version (str): Addon version. + filename (str): Filename in private folder on server. + destination_dir (str): Where the file should be downloaded. + destination_filename (Optional[str]): Name of destination + filename. Source filename is used if not passed. + chunk_size (Optional[int]): Download chunk size. + progress (Optional[TransferProgress]): Object that gives ability + to track download progress. + + Returns: + str: Filepath to downloaded file. """ con = get_server_api_connection() - return con.save_secret( - secret_name=secret_name, - secret_value=secret_value, + return con.download_addon_private_file( + addon_name=addon_name, + addon_version=addon_version, + filename=filename, + destination_dir=destination_dir, + destination_filename=destination_filename, + chunk_size=chunk_size, + progress=progress, ) -def delete_secret( - secret_name: str, -): - """Delete secret by name. +def get_addon_settings_schema( + addon_name: str, + addon_version: str, + project_name: Optional[str] = None, +) -> dict[str, Any]: + """Sudio/Project settings schema of an addon. + + Project schema may look differently as some enums are based on project + values. Args: - secret_name (str): Name of secret to delete. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + project_name (Optional[str]): Schema for specific project or + default studio schemas. + + Returns: + dict[str, Any]: Schema of studio/project settings. """ con = get_server_api_connection() - return con.delete_secret( - secret_name=secret_name, + return con.get_addon_settings_schema( + addon_name=addon_name, + addon_version=addon_version, + project_name=project_name, ) -def get_rest_entity_by_id( - project_name: str, - entity_type: str, - entity_id: str, -) -> Optional["AnyEntityDict"]: - """Get entity using REST on a project by its id. +def get_addon_site_settings_schema( + addon_name: str, + addon_version: str, +) -> dict[str, Any]: + """Site settings schema of an addon. Args: - project_name (str): Name of project where entity is. - entity_type (Literal["folder", "task", "product", "version"]): The - entity type which should be received. - entity_id (str): Id of entity. + addon_name (str): Name of addon. + addon_version (str): Version of addon. Returns: - Optional[AnyEntityDict]: Received entity data. + dict[str, Any]: Schema of site settings. """ con = get_server_api_connection() - return con.get_rest_entity_by_id( - project_name=project_name, - entity_type=entity_type, - entity_id=entity_id, + return con.get_addon_site_settings_schema( + addon_name=addon_name, + addon_version=addon_version, ) -def send_batch_operations( - project_name: str, - operations: list[dict[str, Any]], - can_fail: bool = False, - raise_on_fail: bool = True, -) -> list[dict[str, Any]]: - """Post multiple CRUD operations to server. +def get_addon_studio_settings( + addon_name: str, + addon_version: str, + variant: Optional[str] = None, +) -> dict[str, Any]: + """Addon studio settings. - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. + Receive studio settings for specific version of an addon. Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. - - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. Returns: - list[dict[str, Any]]: Operations result with process details. + dict[str, Any]: Addon settings. """ con = get_server_api_connection() - return con.send_batch_operations( - project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, + return con.get_addon_studio_settings( + addon_name=addon_name, + addon_version=addon_version, + variant=variant, ) -def get_actions( - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, +def get_addon_project_settings( + addon_name: str, + addon_version: str, + project_name: str, variant: Optional[str] = None, - mode: Optional["ActionModeType"] = None, -) -> List["ActionManifestDict"]: - """Get actions for a context. + site_id: Optional[str] = None, + use_site: bool = True, +) -> dict[str, Any]: + """Addon project settings. + + Receive project settings for specific version of an addon. The settings + may be with site overrides when enabled. + + Site id is filled with current connection site id if not passed. To + make sure any site id is used set 'use_site' to 'False'. Args: - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. - mode (Optional[ActionModeType]): Action modes. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + project_name (str): Name of project for which the settings are + received. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Name of site which is used for site + overrides. Is filled with connection 'site_id' attribute + if not passed. + use_site (Optional[bool]): To force disable option of using site + overrides set to 'False'. In that case won't be applied + any site overrides. Returns: - List[ActionManifestDict]: List of action manifests. + dict[str, Any]: Addon settings. """ con = get_server_api_connection() - return con.get_actions( + return con.get_addon_project_settings( + addon_name=addon_name, + addon_version=addon_version, project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, variant=variant, - mode=mode, + site_id=site_id, + use_site=use_site, ) -def trigger_action( - identifier: str, +def get_addon_settings( addon_name: str, addon_version: str, project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, variant: Optional[str] = None, -) -> "ActionTriggerResponse": - """Trigger action. + site_id: Optional[str] = None, + use_site: bool = True, +) -> dict[str, Any]: + """Receive addon settings. + + Receive addon settings based on project name value. Some arguments may + be ignored if 'project_name' is set to 'None'. Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + project_name (Optional[str]): Name of project for which the + settings are received. A studio settings values are received + if is 'None'. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Name of site which is used for site + overrides. Is filled with connection 'site_id' attribute + if not passed. + use_site (Optional[bool]): To force disable option of using + site overrides set to 'False'. In that case won't be applied + any site overrides. + + Returns: + dict[str, Any]: Addon settings. """ con = get_server_api_connection() - return con.trigger_action( - identifier=identifier, + return con.get_addon_settings( addon_name=addon_name, addon_version=addon_version, project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, variant=variant, + site_id=site_id, + use_site=use_site, ) -def get_action_config( - identifier: str, +def get_addon_site_settings( addon_name: str, addon_version: str, - project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, - variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Get action configuration. + site_id: Optional[str] = None, +) -> dict[str, Any]: + """Site settings of an addon. + + If site id is not available an empty dictionary is returned. Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + addon_name (str): Name of addon. + addon_version (str): Version of addon. + site_id (Optional[str]): Name of site for which should be settings + returned. using 'site_id' attribute if not passed. Returns: - ActionConfigResponse: Action configuration data. + dict[str, Any]: Site settings. """ con = get_server_api_connection() - return con.get_action_config( - identifier=identifier, + return con.get_addon_site_settings( addon_name=addon_name, addon_version=addon_version, - project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, - variant=variant, + site_id=site_id, ) -def set_action_config( - identifier: str, - addon_name: str, - addon_version: str, - value: Dict[str, Any], +def get_bundle_settings( + bundle_name: Optional[str] = None, project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, - entity_ids: Optional[List[str]] = None, - entity_subtypes: Optional[List[str]] = None, - form_data: Optional[Dict[str, Any]] = None, - *, variant: Optional[str] = None, -) -> "ActionConfigResponse": - """Set action configuration. + site_id: Optional[str] = None, + use_site: bool = True, +) -> dict[str, Any]: + """Get complete set of settings for given data. - Args: - identifier (str): Identifier of the action. - addon_name (str): Name of the addon. - addon_version (str): Version of the addon. - value (Optional[Dict[str, Any]]): Value of the action - configuration. - project_name (Optional[str]): Name of the project. None for global - actions. - entity_type (Optional[ActionEntityTypes]): Entity type where the - action is triggered. None for global actions. - entity_ids (Optional[List[str]]): List of entity ids where the - action is triggered. None for global actions. - entity_subtypes (Optional[List[str]]): List of entity subtypes - folder types for folder ids, task types for tasks ids. - form_data (Optional[Dict[str, Any]]): Form data of the action. - variant (Optional[str]): Settings variant. + If project is not passed then studio settings are returned. If variant + is not passed 'default_settings_variant' is used. If bundle name is + not passed then current production/staging bundle is used, based on + variant value. + + Output contains addon settings and site settings in single dictionary. + + Todos: + - test how it behaves if there is not any bundle. + - test how it behaves if there is not any production/staging + bundle. + + Example output:: + + { + "addons": [ + { + "name": "addon-name", + "version": "addon-version", + "settings": {...}, + "siteSettings": {...} + } + ] + } Returns: - ActionConfigResponse: New action configuration data. + dict[str, Any]: All settings for single bundle. """ con = get_server_api_connection() - return con.set_action_config( - identifier=identifier, - addon_name=addon_name, - addon_version=addon_version, - value=value, + return con.get_bundle_settings( + bundle_name=bundle_name, project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - entity_subtypes=entity_subtypes, - form_data=form_data, variant=variant, + site_id=site_id, + use_site=use_site, ) -def take_action( - action_token: str, -) -> "ActionTakeResponse": - """Take action metadata using an action token. +def get_addons_studio_settings( + bundle_name: Optional[str] = None, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True, + only_values: bool = True, +) -> dict[str, Any]: + """All addons settings in one bulk. + + Warnings: + Behavior of this function changed with AYON server version 0.3.0. + Structure of output from server changed. If using + 'only_values=True' then output should be same as before. Args: - action_token (str): AYON launcher action token. + bundle_name (Optional[str]): Name of bundle for which should be + settings received. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Site id for which want to receive + site overrides. + use_site (bool): To force disable option of using site overrides + set to 'False'. In that case won't be applied any site + overrides. + only_values (Optional[bool]): Output will contain only settings + values without metadata about addons. Returns: - ActionTakeResponse: Action metadata describing how to launch - action. + dict[str, Any]: Settings of all addons on server. """ con = get_server_api_connection() - return con.take_action( - action_token=action_token, + return con.get_addons_studio_settings( + bundle_name=bundle_name, + variant=variant, + site_id=site_id, + use_site=use_site, + only_values=only_values, ) -def abort_action( - action_token: str, - message: Optional[str] = None, -) -> None: - """Abort action using an action token. - - Args: - action_token (str): AYON launcher action token. - message (Optional[str]): Message to display in the UI. +def get_addons_project_settings( + project_name: str, + bundle_name: Optional[str] = None, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True, + only_values: bool = True, +) -> dict[str, Any]: + """Project settings of all addons. - """ - con = get_server_api_connection() - return con.abort_action( - action_token=action_token, - message=message, - ) + Server returns information about used addon versions, so full output + looks like: + ```json + { + "settings": {...}, + "addons": {...} + } + ``` -def get_activities( - project_name: str, - activity_ids: Optional[Iterable[str]] = None, - activity_types: Optional[Iterable["ActivityType"]] = None, - entity_ids: Optional[Iterable[str]] = None, - entity_names: Optional[Iterable[str]] = None, - entity_type: Optional[str] = None, - changed_after: Optional[str] = None, - changed_before: Optional[str] = None, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, - limit: Optional[int] = None, - order: Optional[SortOrder] = None, -) -> Generator[dict[str, Any], None, None]: - """Get activities from server with filtering options. + The output can be limited to only values. To do so is 'only_values' + argument which is by default set to 'True'. In that case output + contains only value of 'settings' key. + + Warnings: + Behavior of this function changed with AYON server version 0.3.0. + Structure of output from server changed. If using + 'only_values=True' then output should be same as before. Args: - project_name (str): Project on which activities happened. - activity_ids (Optional[Iterable[str]]): Activity ids. - activity_types (Optional[Iterable[ActivityType]]): Activity types. - entity_ids (Optional[Iterable[str]]): Entity ids. - entity_names (Optional[Iterable[str]]): Entity names. - entity_type (Optional[str]): Entity type. - changed_after (Optional[str]): Return only activities changed - after given iso datetime string. - changed_before (Optional[str]): Return only activities changed - before given iso datetime string. - reference_types (Optional[Iterable[ActivityReferenceType]]): - Reference types filter. Defaults to `['origin']`. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. - limit (Optional[int]): Limit number of activities to be fetched. - order (Optional[SortOrder]): Order activities in ascending - or descending order. It is recommended to set 'limit' - when used descending. + project_name (str): Name of project for which are settings + received. + bundle_name (Optional[str]): Name of bundle for which should be + settings received. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Site id for which want to receive + site overrides. + use_site (bool): To force disable option of using site overrides + set to 'False'. In that case won't be applied any site + overrides. + only_values (Optional[bool]): Output will contain only settings + values without metadata about addons. Returns: - Generator[dict[str, Any]]: Available activities matching filters. + dict[str, Any]: Settings of all addons on server for passed + project. """ con = get_server_api_connection() - return con.get_activities( + return con.get_addons_project_settings( project_name=project_name, - activity_ids=activity_ids, - activity_types=activity_types, - entity_ids=entity_ids, - entity_names=entity_names, - entity_type=entity_type, - changed_after=changed_after, - changed_before=changed_before, - reference_types=reference_types, - fields=fields, - limit=limit, - order=order, + bundle_name=bundle_name, + variant=variant, + site_id=site_id, + use_site=use_site, + only_values=only_values, ) -def get_activity_by_id( - project_name: str, - activity_id: str, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, - fields: Optional[Iterable[str]] = None, -) -> Optional[dict[str, Any]]: - """Get activity by id. +def get_addons_settings( + bundle_name: Optional[str] = None, + project_name: Optional[str] = None, + variant: Optional[str] = None, + site_id: Optional[str] = None, + use_site: bool = True, + only_values: bool = True, +) -> dict[str, Any]: + """Universal function to receive all addon settings. - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - reference_types: Optional[Iterable[ActivityReferenceType]]: Filter - by reference types. - fields (Optional[Iterable[str]]): Fields that should be received - for each activity. + Based on 'project_name' will receive studio settings or project + settings. In case project is not passed is 'site_id' ignored. - Returns: - Optional[dict[str, Any]]: Activity data or None if activity is not - found. + Warnings: + Behavior of this function changed with AYON server version 0.3.0. + Structure of output from server changed. If using + 'only_values=True' then output should be same as before. + + Args: + bundle_name (Optional[str]): Name of bundle for which should be + settings received. + project_name (Optional[str]): Name of project for which should be + settings received. + variant (Optional[Literal['production', 'staging']]): Name of + settings variant. Used 'default_settings_variant' by default. + site_id (Optional[str]): Id of site for which want to receive + site overrides. + use_site (Optional[bool]): To force disable option of using site + overrides set to 'False'. In that case won't be applied + any site overrides. + only_values (Optional[bool]): Only settings values will be + returned. By default, is set to 'True'. """ con = get_server_api_connection() - return con.get_activity_by_id( + return con.get_addons_settings( + bundle_name=bundle_name, project_name=project_name, - activity_id=activity_id, - reference_types=reference_types, - fields=fields, + variant=variant, + site_id=site_id, + use_site=use_site, + only_values=only_values, ) -def create_activity( - project_name: str, - entity_id: str, - entity_type: str, - activity_type: "ActivityType", - activity_id: Optional[str] = None, - body: Optional[str] = None, - file_ids: Optional[list[str]] = None, - timestamp: Optional[str] = None, - data: Optional[dict[str, Any]] = None, -) -> str: - """Create activity on a project. +def get_event( + event_id: str, +) -> Optional[dict[str, Any]]: + """Query full event data by id. + + Events received using event server do not contain full information. To + get the full event information is required to receive it explicitly. Args: - project_name (str): Project on which activity happened. - entity_id (str): Entity id. - entity_type (str): Entity type. - activity_type (ActivityType): Activity type. - activity_id (Optional[str]): Activity id. - body (Optional[str]): Activity body. - file_ids (Optional[list[str]]): List of file ids attached - to activity. - timestamp (Optional[str]): Activity timestamp. - data (Optional[dict[str, Any]]): Additional data. + event_id (str): Event id. Returns: - str: Activity id. + dict[str, Any]: Full event data. """ con = get_server_api_connection() - return con.create_activity( - project_name=project_name, - entity_id=entity_id, - entity_type=entity_type, - activity_type=activity_type, - activity_id=activity_id, - body=body, - file_ids=file_ids, - timestamp=timestamp, - data=data, + return con.get_event( + event_id=event_id, ) -def update_activity( - project_name: str, - activity_id: str, - body: Optional[str] = None, - file_ids: Optional[list[str]] = None, - append_file_ids: Optional[bool] = False, - data: Optional[dict[str, Any]] = None, -): - """Update activity by id. - - Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id. - body (str): Activity body. - file_ids (Optional[list[str]]): List of file ids attached - to activity. - append_file_ids (Optional[bool]): Append file ids to existing - list of file ids. - data (Optional[dict[str, Any]]): Update data in activity. - - """ - con = get_server_api_connection() - return con.update_activity( - project_name=project_name, - activity_id=activity_id, - body=body, - file_ids=file_ids, - append_file_ids=append_file_ids, - data=data, - ) - +def get_events( + topics: Optional[Iterable[str]] = None, + event_ids: Optional[Iterable[str]] = None, + project_names: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + users: Optional[Iterable[str]] = None, + include_logs: Optional[bool] = None, + has_children: Optional[bool] = None, + newer_than: Optional[str] = None, + older_than: Optional[str] = None, + fields: Optional[Iterable[str]] = None, + limit: Optional[int] = None, + order: Optional[SortOrder] = None, + states: Optional[Iterable[str]] = None, +) -> Generator[dict[str, Any], None, None]: + """Get events from server with filtering options. -def delete_activity( - project_name: str, - activity_id: str, -): - """Delete activity by id. + Notes: + Not all event happen on a project. Args: - project_name (str): Project on which activity happened. - activity_id (str): Activity id to remove. + topics (Optional[Iterable[str]]): Name of topics. + event_ids (Optional[Iterable[str]]): Event ids. + project_names (Optional[Iterable[str]]): Project on which + event happened. + statuses (Optional[Iterable[str]]): Filtering by statuses. + users (Optional[Iterable[str]]): Filtering by users + who created/triggered an event. + include_logs (Optional[bool]): Query also log events. + has_children (Optional[bool]): Event is with/without children + events. If 'None' then all events are returned, default. + newer_than (Optional[str]): Return only events newer than given + iso datetime string. + older_than (Optional[str]): Return only events older than given + iso datetime string. + fields (Optional[Iterable[str]]): Fields that should be received + for each event. + limit (Optional[int]): Limit number of events to be fetched. + order (Optional[SortOrder]): Order events in ascending + or descending order. It is recommended to set 'limit' + when used descending. + states (Optional[Iterable[str]]): DEPRECATED Filtering by states. + Use 'statuses' instead. + + Returns: + Generator[dict[str, Any]]: Available events matching filters. """ con = get_server_api_connection() - return con.delete_activity( - project_name=project_name, - activity_id=activity_id, + return con.get_events( + topics=topics, + event_ids=event_ids, + project_names=project_names, + statuses=statuses, + users=users, + include_logs=include_logs, + has_children=has_children, + newer_than=newer_than, + older_than=older_than, + fields=fields, + limit=limit, + order=order, + states=states, ) -def send_activities_batch_operations( - project_name: str, - operations: list, - can_fail: bool = False, - raise_on_fail: bool = True, -) -> list: - """Post multiple CRUD activities operations to server. - - When multiple changes should be made on server side this is the best - way to go. It is possible to pass multiple operations to process on a - server side and do the changes in a transaction. +def update_event( + event_id: str, + sender: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + status: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + progress: Optional[int] = None, + retries: Optional[int] = None, +): + """Update event data. Args: - project_name (str): On which project should be operations - processed. - operations (list[dict[str, Any]]): Operations to be processed. - can_fail (Optional[bool]): Server will try to process all - operations even if one of them fails. - raise_on_fail (Optional[bool]): Raise exception if an operation - fails. You can handle failed operations on your own - when set to 'False'. - - Raises: - ValueError: Operations can't be converted to json string. - FailedOperations: When output does not contain server operations - or 'raise_on_fail' is enabled and any operation fails. - - Returns: - list[dict[str, Any]]: Operations result with process details. + event_id (str): Event id. + sender (Optional[str]): New sender of event. + project_name (Optional[str]): New project name. + username (Optional[str]): New username. + status (Optional[str]): New event status. Enum: "pending", + "in_progress", "finished", "failed", "aborted", "restarted" + description (Optional[str]): New description. + summary (Optional[dict[str, Any]]): New summary. + payload (Optional[dict[str, Any]]): New payload. + progress (Optional[int]): New progress. Range [0-100]. + retries (Optional[int]): New retries. """ con = get_server_api_connection() - return con.send_activities_batch_operations( + return con.update_event( + event_id=event_id, + sender=sender, project_name=project_name, - operations=operations, - can_fail=can_fail, - raise_on_fail=raise_on_fail, + username=username, + status=status, + description=description, + summary=summary, + payload=payload, + progress=progress, + retries=retries, ) -def get_addon_endpoint( - addon_name: str, - addon_version: str, - *subpaths, -) -> str: - """Calculate endpoint to addon route. - - Examples: - >>> from ayon_api import ServerAPI - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'addons/example/1.0.0/private/my.zip' +def dispatch_event( + topic: str, + sender: Optional[str] = None, + event_hash: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + depends_on: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + finished: bool = True, + store: bool = True, + dependencies: Optional[list[str]] = None, +): + """Dispatch event to server. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. + topic (str): Event topic used for filtering of listeners. + sender (Optional[str]): Sender of event. + event_hash (Optional[str]): Event hash. + project_name (Optional[str]): Project name. + depends_on (Optional[str]): Add dependency to another event. + username (Optional[str]): Username which triggered event. + description (Optional[str]): Description of event. + summary (Optional[dict[str, Any]]): Summary of event that can + be used for simple filtering on listeners. + payload (Optional[dict[str, Any]]): Full payload of event data with + all details. + finished (Optional[bool]): Mark event as finished on dispatch. + store (Optional[bool]): Store event in event queue for possible + future processing otherwise is event send only + to active listeners. + dependencies (Optional[list[str]]): Deprecated. + List of event id dependencies. Returns: - str: Final url. - - """ - con = get_server_api_connection() - return con.get_addon_endpoint( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, - ) - - -def get_addons_info( - details: bool = True, -) -> "AddonsInfoDict": - """Get information about addons available on server. - - Args: - details (Optional[bool]): Detailed data with information how - to get client code. + RestApiResponse: Response from server. """ con = get_server_api_connection() - return con.get_addons_info( - details=details, + return con.dispatch_event( + topic=topic, + sender=sender, + event_hash=event_hash, + project_name=project_name, + username=username, + depends_on=depends_on, + description=description, + summary=summary, + payload=payload, + finished=finished, + store=store, + dependencies=dependencies, ) -def get_addon_url( - addon_name: str, - addon_version: str, - *subpaths, - use_rest: bool = True, -) -> str: - """Calculate url to addon route. - - Examples: +def delete_event( + event_id: str, +): + """Delete event by id. - >>> api = ServerAPI("https://your.url.com") - >>> api.get_addon_url( - ... "example", "1.0.0", "private", "my.zip") - 'https://your.url.com/api/addons/example/1.0.0/private/my.zip' + Supported since AYON server 1.6.0. Args: - addon_name (str): Name of addon. - addon_version (str): Version of addon. - *subpaths (str): Any amount of subpaths that are added to - addon url. - use_rest (Optional[bool]): Use rest endpoint. + event_id (str): Event id. Returns: - str: Final url. + RestApiResponse: Response from server. """ con = get_server_api_connection() - return con.get_addon_url( - addon_name=addon_name, - addon_version=addon_version, - *subpaths, - use_rest=use_rest, + return con.delete_event( + event_id=event_id, ) -def delete_addon( - addon_name: str, - purge: Optional[bool] = None, -) -> None: - """Delete addon from server. +def enroll_event_job( + source_topic: "Union[str, list[str]]", + target_topic: str, + sender: str, + description: Optional[str] = None, + sequential: Optional[bool] = None, + events_filter: Optional["EventFilter"] = None, + max_retries: Optional[int] = None, + ignore_older_than: Optional[str] = None, + ignore_sender_types: Optional[str] = None, +): + """Enroll job based on events. - Delete all versions of addon from server. + Enroll will find first unprocessed event with 'source_topic' and will + create new event with 'target_topic' for it and return the new event + data. - Args: - addon_name (str): Addon name. - purge (Optional[bool]): Purge all data related to the addon. + Use 'sequential' to control that only single target event is created + at same time. Creation of new target events is blocked while there is + at least one unfinished event with target topic, when set to 'True'. + This helps when order of events matter and more than one process using + the same target is running at the same time. - """ - con = get_server_api_connection() - return con.delete_addon( - addon_name=addon_name, - purge=purge, - ) + Make sure the new event has updated status to '"finished"' status + when you're done with logic + Target topic should not clash with other processes/services. -def delete_addon_version( - addon_name: str, - addon_version: str, - purge: Optional[bool] = None, -) -> None: - """Delete addon version from server. + Created target event have 'dependsOn' key where is id of source topic. - Delete all versions of addon from server. + Use-case: + - Service 1 is creating events with topic 'my.leech' + - Service 2 process 'my.leech' and uses target topic 'my.process' + - this service can run on 1-n machines + - all events must be processed in a sequence by their creation + time and only one event can be processed at a time + - in this case 'sequential' should be set to 'True' so only + one machine is actually processing events, but if one goes + down there are other that can take place + - Service 3 process 'my.leech' and uses target topic 'my.discover' + - this service can run on 1-n machines + - order of events is not important + - 'sequential' should be 'False' Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - purge (Optional[bool]): Purge all data related to the addon. + source_topic (Union[str, list[str]]): Source topic to enroll with + wildcards '*', or explicit list of topics. + target_topic (str): Topic of dependent event. + sender (str): Identifier of sender (e.g. service name or username). + description (Optional[str]): Human readable text shown + in target event. + sequential (Optional[bool]): The source topic must be processed + in sequence. + events_filter (Optional[dict[str, Any]]): Filtering conditions + to filter the source event. For more technical specifications + look to server backed 'ayon_server.sqlfilter.Filter'. + TODO: Add example of filters. + max_retries (Optional[int]): How many times can be event retried. + Default value is based on server (3 at the time of this PR). + ignore_older_than (Optional[int]): Ignore events older than + given number in days. + ignore_sender_types (Optional[list[str]]): Ignore events triggered + by given sender types. + + Returns: + Optional[dict[str, Any]]: None if there is no event matching + filters. Created event with 'target_topic'. """ con = get_server_api_connection() - return con.delete_addon_version( - addon_name=addon_name, - addon_version=addon_version, - purge=purge, + return con.enroll_event_job( + source_topic=source_topic, + target_topic=target_topic, + sender=sender, + description=description, + sequential=sequential, + events_filter=events_filter, + max_retries=max_retries, + ignore_older_than=ignore_older_than, + ignore_sender_types=ignore_sender_types, ) -def upload_addon_zip( - src_filepath: str, - progress: Optional[TransferProgress] = None, +def get_attributes_schema( + use_cache: bool = True, +) -> "AttributesSchemaDict": + con = get_server_api_connection() + return con.get_attributes_schema( + use_cache=use_cache, + ) + + +def reset_attributes_schema(): + con = get_server_api_connection() + return con.reset_attributes_schema() + + +def set_attribute_config( + attribute_name: str, + data: "AttributeSchemaDataDict", + scope: list["AttributeScope"], + position: Optional[int] = None, + builtin: bool = False, ): - """Upload addon zip file to server. + con = get_server_api_connection() + return con.set_attribute_config( + attribute_name=attribute_name, + data=data, + scope=scope, + position=position, + builtin=builtin, + ) - File is validated on server. If it is valid, it is installed. It will - create an event job which can be tracked (tracking part is not - implemented yet). - Example output:: +def remove_attribute_config( + attribute_name: str, +): + """Remove attribute from server. - {'eventId': 'a1bfbdee27c611eea7580242ac120003'} + This can't be un-done, please use carefully. Args: - src_filepath (str): Path to a zip file. - progress (Optional[TransferProgress]): Object to keep track about - upload state. - - Returns: - dict[str, Any]: Response data from server. + attribute_name (str): Name of attribute to remove. """ con = get_server_api_connection() - return con.upload_addon_zip( - src_filepath=src_filepath, - progress=progress, + return con.remove_attribute_config( + attribute_name=attribute_name, ) -def download_addon_private_file( - addon_name: str, - addon_version: str, - filename: str, - destination_dir: str, - destination_filename: Optional[str] = None, - chunk_size: Optional[int] = None, - progress: Optional[TransferProgress] = None, -) -> str: - """Download a file from addon private files. +def get_attributes_for_type( + entity_type: "AttributeScope", +) -> dict[str, "AttributeSchemaDict"]: + """Get attribute schemas available for an entity type. - This method requires to have authorized token available. Private files - are not under '/api' restpoint. + Example:: + + ``` + # Example attribute schema + { + # Common + "type": "integer", + "title": "Clip Out", + "description": null, + "example": 1, + "default": 1, + # These can be filled based on value of 'type' + "gt": null, + "ge": null, + "lt": null, + "le": null, + "minLength": null, + "maxLength": null, + "minItems": null, + "maxItems": null, + "regex": null, + "enum": null + } + ``` Args: - addon_name (str): Addon name. - addon_version (str): Addon version. - filename (str): Filename in private folder on server. - destination_dir (str): Where the file should be downloaded. - destination_filename (Optional[str]): Name of destination - filename. Source filename is used if not passed. - chunk_size (Optional[int]): Download chunk size. - progress (Optional[TransferProgress]): Object that gives ability - to track download progress. + entity_type (str): Entity type for which should be attributes + received. Returns: - str: Filepath to downloaded file. + dict[str, dict[str, Any]]: Attribute schemas that are available + for entered entity type. """ con = get_server_api_connection() - return con.download_addon_private_file( - addon_name=addon_name, - addon_version=addon_version, - filename=filename, - destination_dir=destination_dir, - destination_filename=destination_filename, - chunk_size=chunk_size, - progress=progress, + return con.get_attributes_for_type( + entity_type=entity_type, ) -def get_event( - event_id: str, -) -> Optional[dict[str, Any]]: - """Query full event data by id. +def get_attributes_fields_for_type( + entity_type: "AttributeScope", +) -> set[str]: + """Prepare attribute fields for entity type. - Events received using event server do not contain full information. To - get the full event information is required to receive it explicitly. + Returns: + set[str]: Attributes fields for entity type. - Args: - event_id (str): Event id. + """ + con = get_server_api_connection() + return con.get_attributes_fields_for_type( + entity_type=entity_type, + ) + + +def get_project_anatomy_presets() -> list["AnatomyPresetDict"]: + """Anatomy presets available on server. + + Content has basic information about presets. Example output:: + + [ + { + "name": "netflix_VFX", + "primary": false, + "version": "1.0.0" + }, + { + ... + }, + ... + ] Returns: - dict[str, Any]: Full event data. + list[dict[str, str]]: Anatomy presets available on server. """ con = get_server_api_connection() - return con.get_event( - event_id=event_id, - ) + return con.get_project_anatomy_presets() -def get_events( - topics: Optional[Iterable[str]] = None, - event_ids: Optional[Iterable[str]] = None, - project_names: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - users: Optional[Iterable[str]] = None, - include_logs: Optional[bool] = None, - has_children: Optional[bool] = None, - newer_than: Optional[str] = None, - older_than: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - limit: Optional[int] = None, - order: Optional[SortOrder] = None, - states: Optional[Iterable[str]] = None, -) -> Generator[dict[str, Any], None, None]: - """Get events from server with filtering options. +def get_default_anatomy_preset_name() -> str: + """Name of default anatomy preset. - Notes: - Not all event happen on a project. + Primary preset is used as default preset. But when primary preset is + not set a built-in is used instead. Built-in preset is named '_'. - Args: - topics (Optional[Iterable[str]]): Name of topics. - event_ids (Optional[Iterable[str]]): Event ids. - project_names (Optional[Iterable[str]]): Project on which - event happened. - statuses (Optional[Iterable[str]]): Filtering by statuses. - users (Optional[Iterable[str]]): Filtering by users - who created/triggered an event. - include_logs (Optional[bool]): Query also log events. - has_children (Optional[bool]): Event is with/without children - events. If 'None' then all events are returned, default. - newer_than (Optional[str]): Return only events newer than given - iso datetime string. - older_than (Optional[str]): Return only events older than given - iso datetime string. - fields (Optional[Iterable[str]]): Fields that should be received - for each event. - limit (Optional[int]): Limit number of events to be fetched. - order (Optional[SortOrder]): Order events in ascending - or descending order. It is recommended to set 'limit' - when used descending. - states (Optional[Iterable[str]]): DEPRECATED Filtering by states. - Use 'statuses' instead. + Returns: + str: Name of preset that can be used by + 'get_project_anatomy_preset'. + + """ + con = get_server_api_connection() + return con.get_default_anatomy_preset_name() + + +def get_project_anatomy_preset( + preset_name: Optional[str] = None, +) -> "AnatomyPresetDict": + """Anatomy preset values by name. + + Get anatomy preset values by preset name. Primary preset is returned + if preset name is set to 'None'. + + Args: + preset_name (Optional[str]): Preset name. Returns: - Generator[dict[str, Any]]: Available events matching filters. + AnatomyPresetDict: Anatomy preset values. """ con = get_server_api_connection() - return con.get_events( - topics=topics, - event_ids=event_ids, - project_names=project_names, - statuses=statuses, - users=users, - include_logs=include_logs, - has_children=has_children, - newer_than=newer_than, - older_than=older_than, - fields=fields, - limit=limit, - order=order, - states=states, + return con.get_project_anatomy_preset( + preset_name=preset_name, ) -def update_event( - event_id: str, - sender: Optional[str] = None, - project_name: Optional[str] = None, - username: Optional[str] = None, - status: Optional[str] = None, - description: Optional[str] = None, - summary: Optional[dict[str, Any]] = None, - payload: Optional[dict[str, Any]] = None, - progress: Optional[int] = None, - retries: Optional[int] = None, -): - """Update event data. +def get_built_in_anatomy_preset() -> "AnatomyPresetDict": + """Get built-in anatomy preset. + + Returns: + AnatomyPresetDict: Built-in anatomy preset. + + """ + con = get_server_api_connection() + return con.get_built_in_anatomy_preset() + + +def get_build_in_anatomy_preset() -> "AnatomyPresetDict": + con = get_server_api_connection() + return con.get_build_in_anatomy_preset() + + +def get_rest_project( + project_name: str, +) -> Optional["ProjectDict"]: + """Query project by name. + + This call returns project with anatomy data. Args: - event_id (str): Event id. - sender (Optional[str]): New sender of event. - project_name (Optional[str]): New project name. - username (Optional[str]): New username. - status (Optional[str]): New event status. Enum: "pending", - "in_progress", "finished", "failed", "aborted", "restarted" - description (Optional[str]): New description. - summary (Optional[dict[str, Any]]): New summary. - payload (Optional[dict[str, Any]]): New payload. - progress (Optional[int]): New progress. Range [0-100]. - retries (Optional[int]): New retries. + project_name (str): Name of project. + + Returns: + Optional[ProjectDict]: Project entity data or 'None' if + project was not found. """ con = get_server_api_connection() - return con.update_event( - event_id=event_id, - sender=sender, + return con.get_rest_project( project_name=project_name, - username=username, - status=status, - description=description, - summary=summary, - payload=payload, - progress=progress, - retries=retries, ) -def dispatch_event( - topic: str, - sender: Optional[str] = None, - event_hash: Optional[str] = None, - project_name: Optional[str] = None, - username: Optional[str] = None, - depends_on: Optional[str] = None, - description: Optional[str] = None, - summary: Optional[dict[str, Any]] = None, - payload: Optional[dict[str, Any]] = None, - finished: bool = True, - store: bool = True, - dependencies: Optional[list[str]] = None, -): - """Dispatch event to server. +def get_rest_projects( + active: Optional[bool] = True, + library: Optional[bool] = None, +) -> Generator["ProjectDict", None, None]: + """Query available project entities. + + User must be logged in. Args: - topic (str): Event topic used for filtering of listeners. - sender (Optional[str]): Sender of event. - event_hash (Optional[str]): Event hash. - project_name (Optional[str]): Project name. - depends_on (Optional[str]): Add dependency to another event. - username (Optional[str]): Username which triggered event. - description (Optional[str]): Description of event. - summary (Optional[dict[str, Any]]): Summary of event that can - be used for simple filtering on listeners. - payload (Optional[dict[str, Any]]): Full payload of event data with - all details. - finished (Optional[bool]): Mark event as finished on dispatch. - store (Optional[bool]): Store event in event queue for possible - future processing otherwise is event send only - to active listeners. - dependencies (Optional[list[str]]): Deprecated. - List of event id dependencies. + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. Returns: - RestApiResponse: Response from server. + Generator[ProjectDict, None, None]: Available projects. """ con = get_server_api_connection() - return con.dispatch_event( - topic=topic, - sender=sender, - event_hash=event_hash, - project_name=project_name, - username=username, - depends_on=depends_on, - description=description, - summary=summary, - payload=payload, - finished=finished, - store=store, - dependencies=dependencies, + return con.get_rest_projects( + active=active, + library=library, ) -def delete_event( - event_id: str, -): - """Delete event by id. +def get_project_names( + active: Optional[bool] = True, + library: Optional[bool] = None, +) -> list[str]: + """Receive available project names. - Supported since AYON server 1.6.0. + User must be logged in. Args: - event_id (str): Event id. + active (Optional[bool]): Filter active/inactive projects. Both + are returned if 'None' is passed. + library (Optional[bool]): Filter standard/library projects. Both + are returned if 'None' is passed. Returns: - RestApiResponse: Response from server. + list[str]: List of available project names. """ con = get_server_api_connection() - return con.delete_event( - event_id=event_id, + return con.get_project_names( + active=active, + library=library, ) -def enroll_event_job( - source_topic: "Union[str, list[str]]", - target_topic: str, - sender: str, - description: Optional[str] = None, - sequential: Optional[bool] = None, - events_filter: Optional["EventFilter"] = None, - max_retries: Optional[int] = None, - ignore_older_than: Optional[str] = None, - ignore_sender_types: Optional[str] = None, -): - """Enroll job based on events. +def get_projects( + active: Optional[bool] = True, + library: Optional[bool] = None, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Generator["ProjectDict", None, None]: + """Get projects. - Enroll will find first unprocessed event with 'source_topic' and will - create new event with 'target_topic' for it and return the new event - data. + Args: + active (Optional[bool]): Filter active or inactive projects. + Filter is disabled when 'None' is passed. + library (Optional[bool]): Filter library projects. Filter is + disabled when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. - Use 'sequential' to control that only single target event is created - at same time. Creation of new target events is blocked while there is - at least one unfinished event with target topic, when set to 'True'. - This helps when order of events matter and more than one process using - the same target is running at the same time. + Returns: + Generator[ProjectDict, None, None]: Queried projects. - Make sure the new event has updated status to '"finished"' status - when you're done with logic + """ + con = get_server_api_connection() + return con.get_projects( + active=active, + library=library, + fields=fields, + own_attributes=own_attributes, + ) - Target topic should not clash with other processes/services. - Created target event have 'dependsOn' key where is id of source topic. +def get_project( + project_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["ProjectDict"]: + """Get project. - Use-case: - - Service 1 is creating events with topic 'my.leech' - - Service 2 process 'my.leech' and uses target topic 'my.process' - - this service can run on 1-n machines - - all events must be processed in a sequence by their creation - time and only one event can be processed at a time - - in this case 'sequential' should be set to 'True' so only - one machine is actually processing events, but if one goes - down there are other that can take place - - Service 3 process 'my.leech' and uses target topic 'my.discover' - - this service can run on 1-n machines - - order of events is not important - - 'sequential' should be 'False' + Args: + project_name (str): Name of project. + fields (Optional[Iterable[str]]): fields to be queried + for project. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[ProjectDict]: Project entity data or None + if project was not found. + + """ + con = get_server_api_connection() + return con.get_project( + project_name=project_name, + fields=fields, + own_attributes=own_attributes, + ) + + +def create_project( + project_name: str, + project_code: str, + library_project: bool = False, + preset_name: Optional[str] = None, +) -> "ProjectDict": + """Create project using AYON settings. + + This project creation function is not validating project entity on + creation. It is because project entity is created blindly with only + minimum required information about project which is name and code. + + Entered project name must be unique and project must not exist yet. + + Note: + This function is here to be OP v4 ready but in v3 has more logic + to do. That's why inner imports are in the body. Args: - source_topic (Union[str, list[str]]): Source topic to enroll with - wildcards '*', or explicit list of topics. - target_topic (str): Topic of dependent event. - sender (str): Identifier of sender (e.g. service name or username). - description (Optional[str]): Human readable text shown - in target event. - sequential (Optional[bool]): The source topic must be processed - in sequence. - events_filter (Optional[dict[str, Any]]): Filtering conditions - to filter the source event. For more technical specifications - look to server backed 'ayon_server.sqlfilter.Filter'. - TODO: Add example of filters. - max_retries (Optional[int]): How many times can be event retried. - Default value is based on server (3 at the time of this PR). - ignore_older_than (Optional[int]): Ignore events older than - given number in days. - ignore_sender_types (Optional[list[str]]): Ignore events triggered - by given sender types. + project_name (str): New project name. Should be unique. + project_code (str): Project's code should be unique too. + library_project (Optional[bool]): Project is library project. + preset_name (Optional[str]): Name of anatomy preset. Default is + used if not passed. + + Raises: + ValueError: When project name already exists. Returns: - Optional[dict[str, Any]]: None if there is no event matching - filters. Created event with 'target_topic'. + ProjectDict: Created project entity. """ con = get_server_api_connection() - return con.enroll_event_job( - source_topic=source_topic, - target_topic=target_topic, - sender=sender, - description=description, - sequential=sequential, - events_filter=events_filter, - max_retries=max_retries, - ignore_older_than=ignore_older_than, - ignore_sender_types=ignore_sender_types, + return con.create_project( + project_name=project_name, + project_code=project_code, + library_project=library_project, + preset_name=preset_name, + ) + + +def update_project( + project_name: str, + library: Optional[bool] = None, + folder_types: Optional[list[dict[str, Any]]] = None, + task_types: Optional[list[dict[str, Any]]] = None, + link_types: Optional[list[dict[str, Any]]] = None, + statuses: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[dict[str, Any]]] = None, + config: Optional[dict[str, Any]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + active: Optional[bool] = None, + project_code: Optional[str] = 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[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. + + """ + con = get_server_api_connection() + return con.update_project( + project_name=project_name, + library=library, + folder_types=folder_types, + task_types=task_types, + link_types=link_types, + statuses=statuses, + tags=tags, + config=config, + attrib=attrib, + data=data, + active=active, + project_code=project_code, + **changes, ) -def get_full_link_type_name( - link_type_name: str, - input_type: str, - output_type: str, -) -> str: - """Calculate full link type name used for query from server. +def delete_project( + project_name: str, +): + """Delete project from server. - Args: - link_type_name (str): Type of link. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. + This will completely remove project from server without any step back. - Returns: - str: Full name of link type used for query from server. + Args: + project_name (str): Project name that will be removed. """ con = get_server_api_connection() - return con.get_full_link_type_name( - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, + return con.delete_project( + project_name=project_name, ) -def get_link_types( +def get_project_root_overrides( project_name: str, -) -> list[dict[str, Any]]: - """All link types available on a project. +) -> dict[str, dict[str, str]]: + """Root overrides per site name. - Example output: - [ - { - "name": "reference|folder|folder", - "link_type": "reference", - "input_type": "folder", - "output_type": "folder", - "data": {} - } - ] + Method is based on logged user and can't be received for any other + user on server. + + Output will contain only roots per site id used by logged user. Args: - project_name (str): Name of project where to look for link types. + project_name (str): Name of project. Returns: - list[dict[str, Any]]: Link types available on project. + dict[str, dict[str, str]]: Root values by root name by site id. """ con = get_server_api_connection() - return con.get_link_types( + return con.get_project_root_overrides( project_name=project_name, ) -def get_link_type( +def get_project_roots_by_site( project_name: str, - link_type_name: str, - input_type: str, - output_type: str, -) -> Optional[dict[str, Any]]: - """Get link type data. +) -> dict[str, dict[str, str]]: + """Root overrides per site name. - There is not dedicated REST endpoint to get single link type, - so method 'get_link_types' is used. + Method is based on logged user and can't be received for any other + user on server. - Example output: - { - "name": "reference|folder|folder", - "link_type": "reference", - "input_type": "folder", - "output_type": "folder", - "data": {} - } + Output will contain only roots per site id used by logged user. + + Deprecated: + Use 'get_project_root_overrides' instead. Function + deprecated since 1.0.6 Args: - project_name (str): Project where link type is available. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. + project_name (str): Name of project. Returns: - Optional[dict[str, Any]]: Link type information. + dict[str, dict[str, str]]: Root values by root name by site id. """ con = get_server_api_connection() - return con.get_link_type( + return con.get_project_roots_by_site( project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, ) -def create_link_type( +def get_project_root_overrides_by_site_id( project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - data: Optional[dict[str, Any]] = None, -): - """Create or update link type on server. + site_id: Optional[str] = None, +) -> dict[str, str]: + """Root overrides for site. - Warning: - Because PUT is used for creation it is also used for update. + If site id is not passed a site set in current api object is used + instead. Args: - project_name (str): Project where link type is created. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - data (Optional[dict[str, Any]]): Additional data related to link. + project_name (str): Name of project. + site_id (Optional[str]): Site id for which want to receive + site overrides. - Raises: - HTTPRequestError: Server error happened. + Returns: + dict[str, str]: Root values by root name or None if + site does not have overrides. """ con = get_server_api_connection() - return con.create_link_type( + return con.get_project_root_overrides_by_site_id( project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, - data=data, + site_id=site_id, ) -def delete_link_type( +def get_project_roots_for_site( project_name: str, - link_type_name: str, - input_type: str, - output_type: str, -): - """Remove link type from project. + site_id: Optional[str] = None, +) -> dict[str, str]: + """Root overrides for site. + + If site id is not passed a site set in current api object is used + instead. + Deprecated: + Use 'get_project_root_overrides_by_site_id' instead. Function + deprecated since 1.0.6 Args: - project_name (str): Project where link type is created. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. + project_name (str): Name of project. + site_id (Optional[str]): Site id for which want to receive + site overrides. - Raises: - HTTPRequestError: Server error happened. + Returns: + dict[str, str]: Root values by root name, root name is not + available if it does not have overrides. """ con = get_server_api_connection() - return con.delete_link_type( + return con.get_project_roots_for_site( project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, + site_id=site_id, ) -def make_sure_link_type_exists( +def get_project_roots_by_site_id( project_name: str, - link_type_name: str, - input_type: str, - output_type: str, - data: Optional[dict[str, Any]] = None, -): - """Make sure link type exists on a project. + site_id: Optional[str] = None, +) -> dict[str, str]: + """Root values for a site. + + If site id is not passed a site set in current api object is used + instead. If site id is not available, default roots are returned + for current platform. Args: project_name (str): Name of project. - link_type_name (str): Name of link type. - input_type (str): Input entity type of link. - output_type (str): Output entity type of link. - data (Optional[dict[str, Any]]): Link type related data. + site_id (Optional[str]): Site id for which want to receive + root values. + + Returns: + dict[str, str]: Root values. """ con = get_server_api_connection() - return con.make_sure_link_type_exists( + return con.get_project_roots_by_site_id( project_name=project_name, - link_type_name=link_type_name, - input_type=input_type, - output_type=output_type, - data=data, + site_id=site_id, ) -def create_link( +def get_project_roots_by_platform( project_name: str, - link_type_name: str, - input_id: str, - input_type: str, - output_id: str, - output_type: str, - link_name: Optional[str] = None, -): - """Create link between 2 entities. - - Link has a type which must already exists on a project. + platform_name: Optional[str] = None, +) -> dict[str, str]: + """Root values for a site. - Example output:: + If platform name is not passed current platform name is used instead. - { - "id": "59a212c0d2e211eda0e20242ac120002" - } + This function does return root values without site overrides. It is + possible to use the function to receive default root values. Args: - project_name (str): Project where the link is created. - link_type_name (str): Type of link. - input_id (str): Input entity id. - input_type (str): Entity type of input entity. - output_id (str): Output entity id. - output_type (str): Entity type of output entity. - link_name (Optional[str]): Name of link. - Available from server version '1.0.0-rc.6'. + project_name (str): Name of project. + platform_name (Optional[Literal["windows", "linux", "darwin"]]): + Platform name for which want to receive root values. Current + platform name is used if not passed. Returns: - dict[str, str]: Information about link. - - Raises: - HTTPRequestError: Server error happened. + dict[str, str]: Root values. """ con = get_server_api_connection() - return con.create_link( + return con.get_project_roots_by_platform( project_name=project_name, - link_type_name=link_type_name, - input_id=input_id, - input_type=input_type, - output_id=output_id, - output_type=output_type, - link_name=link_name, + platform_name=platform_name, + ) + + +def get_rest_folder( + project_name: str, + folder_id: str, +) -> Optional["FolderDict"]: + con = get_server_api_connection() + return con.get_rest_folder( + project_name=project_name, + folder_id=folder_id, ) -def delete_link( - project_name: str, - link_id: str, -): - """Remove link by id. +def get_rest_folders( + project_name: str, + include_attrib: bool = False, +) -> list["FlatFolderDict"]: + """Get simplified flat list of all project folders. + + Get all project folders in single REST call. This can be faster than + using 'get_folders' method which is using GraphQl, but does not + allow any filtering, and set of fields is defined + by server backend. + + Example:: + + [ + { + "id": "112233445566", + "parentId": "112233445567", + "path": "/root/parent/child", + "parents": ["root", "parent"], + "name": "child", + "label": "Child", + "folderType": "Folder", + "hasTasks": False, + "hasChildren": False, + "taskNames": [ + "Compositing", + ], + "status": "In Progress", + "attrib": {}, + "ownAttrib": [], + "updatedAt": "2023-06-12T15:37:02.420260", + }, + ... + ] Args: - project_name (str): Project where link exists. - link_id (str): Id of link. + project_name (str): Project name. + include_attrib (Optional[bool]): Include attribute values + in output. Slower to query. - Raises: - HTTPRequestError: Server error happened. + Returns: + list[FlatFolderDict]: List of folder entities. """ con = get_server_api_connection() - return con.delete_link( + return con.get_rest_folders( project_name=project_name, - link_id=link_id, + include_attrib=include_attrib, ) -def get_entities_links( +def get_folders_hierarchy( project_name: str, - entity_type: str, - entity_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, - link_names: Optional[Iterable[str]] = None, - link_name_regex: Optional[str] = None, -) -> dict[str, list[dict[str, Any]]]: - """Helper method to get links from server for entity types. + search_string: Optional[str] = None, + folder_types: Optional[Iterable[str]] = None, +) -> "ProjectHierarchyDict": + """Get project hierarchy. - .. highlight:: text - .. code-block:: text + All folders in project in hierarchy data structure. - Example output: + Example output: { - "59a212c0d2e211eda0e20242ac120001": [ + "hierarchy": [ { - "id": "59a212c0d2e211eda0e20242ac120002", - "linkType": "reference", - "description": "reference link between folders", - "projectName": "my_project", - "author": "frantadmin", - "entityId": "b1df109676db11ed8e8c6c9466b19aa8", - "entityType": "folder", - "direction": "out" + "id": "...", + "name": "...", + "label": "...", + "status": "...", + "folderType": "...", + "hasTasks": False, + "taskNames": [], + "parents": [], + "parentId": None, + "children": [...children folders...] }, ... - ], - ... + ] } Args: - project_name (str): Project where links are. - entity_type (Literal["folder", "task", "product", - "version", "representations"]): Entity type. - entity_ids (Optional[Iterable[str]]): Ids of entities for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - link_names (Optional[Iterable[str]]): Link name filters. - link_name_regex (Optional[str]): Regex filter for link name. + project_name (str): Project where to look for folders. + search_string (Optional[str]): Search string to filter folders. + folder_types (Optional[Iterable[str]]): Folder types to filter. Returns: - dict[str, list[dict[str, Any]]]: Link info by entity ids. + dict[str, Any]: Response data from server. """ con = get_server_api_connection() - return con.get_entities_links( + return con.get_folders_hierarchy( project_name=project_name, - entity_type=entity_type, - entity_ids=entity_ids, - link_types=link_types, - link_direction=link_direction, - link_names=link_names, - link_name_regex=link_name_regex, + search_string=search_string, + folder_types=folder_types, ) -def get_folders_links( +def get_folders_rest( project_name: str, - folder_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query folders links from server. + include_attrib: bool = False, +) -> list["FlatFolderDict"]: + """Get simplified flat list of all project folders. + + Get all project folders in single REST call. This can be faster than + using 'get_folders' method which is using GraphQl, but does not + allow any filtering, and set of fields is defined + by server backend. + + Example:: + + [ + { + "id": "112233445566", + "parentId": "112233445567", + "path": "/root/parent/child", + "parents": ["root", "parent"], + "name": "child", + "label": "Child", + "folderType": "Folder", + "hasTasks": False, + "hasChildren": False, + "taskNames": [ + "Compositing", + ], + "status": "In Progress", + "attrib": {}, + "ownAttrib": [], + "updatedAt": "2023-06-12T15:37:02.420260", + }, + ... + ] + + Deprecated: + Use 'get_rest_folders' instead. Function was renamed to match + other rest functions, like 'get_rest_folder', + 'get_rest_project' etc. . + Will be removed in '1.0.7' or '1.1.0'. Args: - project_name (str): Project where links are. - folder_ids (Optional[Iterable[str]]): Ids of folders for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Project name. + include_attrib (Optional[bool]): Include attribute values + in output. Slower to query. Returns: - dict[str, list[dict[str, Any]]]: Link info by folder ids. + list[FlatFolderDict]: List of folder entities. """ con = get_server_api_connection() - return con.get_folders_links( + return con.get_folders_rest( project_name=project_name, - folder_ids=folder_ids, - link_types=link_types, - link_direction=link_direction, + include_attrib=include_attrib, ) -def get_folder_links( +def get_folders( project_name: str, - folder_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query folder links from server. + folder_ids: Optional[Iterable[str]] = None, + folder_paths: Optional[Iterable[str]] = None, + folder_names: Optional[Iterable[str]] = None, + folder_types: Optional[Iterable[str]] = None, + parent_ids: Optional[Iterable[str]] = None, + folder_path_regex: Optional[str] = None, + has_products: Optional[bool] = None, + has_tasks: Optional[bool] = None, + has_children: Optional[bool] = None, + statuses: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + has_links: Optional[bool] = None, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Generator["FolderDict", None, None]: + """Query folders from server. + + Todos: + Folder name won't be unique identifier, so we should add + folder path filtering. + + Notes: + Filter 'active' don't have direct filter in GraphQl. Args: - project_name (str): Project where links are. - folder_id (str): Folder id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project. + folder_ids (Optional[Iterable[str]]): Folder ids to filter. + folder_paths (Optional[Iterable[str]]): Folder paths used + for filtering. + folder_names (Optional[Iterable[str]]): Folder names used + for filtering. + folder_types (Optional[Iterable[str]]): Folder types used + for filtering. + parent_ids (Optional[Iterable[str]]): Ids of folder parents. + Use 'None' if folder is direct child of project. + folder_path_regex (Optional[str]): Folder path regex used + for filtering. + has_products (Optional[bool]): Filter folders with/without + products. Ignored when None, default behavior. + has_tasks (Optional[bool]): Filter folders with/without + tasks. Ignored when None, default behavior. + has_children (Optional[bool]): Filter folders with/without + children. Ignored when None, default behavior. + statuses (Optional[Iterable[str]]): Folder statuses used + for filtering. + assignees_all (Optional[Iterable[str]]): Filter by assigness + on children tasks. Task must have all of passed assignees. + tags (Optional[Iterable[str]]): Folder tags used + for filtering. + active (Optional[bool]): Filter active/inactive folders. + Both are returned if is set to None. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - list[dict[str, Any]]: Link info of folder. + Generator[FolderDict, None, None]: Queried folder entities. """ con = get_server_api_connection() - return con.get_folder_links( + return con.get_folders( project_name=project_name, - folder_id=folder_id, - link_types=link_types, - link_direction=link_direction, + folder_ids=folder_ids, + folder_paths=folder_paths, + folder_names=folder_names, + folder_types=folder_types, + parent_ids=parent_ids, + folder_path_regex=folder_path_regex, + has_products=has_products, + has_tasks=has_tasks, + has_children=has_children, + statuses=statuses, + assignees_all=assignees_all, + tags=tags, + active=active, + has_links=has_links, + fields=fields, + own_attributes=own_attributes, ) -def get_tasks_links( +def get_folder_by_id( project_name: str, - task_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query tasks links from server. + folder_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["FolderDict"]: + """Query folder entity by id. Args: - project_name (str): Project where links are. - task_ids (Optional[Iterable[str]]): Ids of tasks for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project where to look for queried + entities. + folder_id (str): Folder id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - dict[str, list[dict[str, Any]]]: Link info by task ids. + Optional[FolderDict]: Folder entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_tasks_links( + return con.get_folder_by_id( project_name=project_name, - task_ids=task_ids, - link_types=link_types, - link_direction=link_direction, + folder_id=folder_id, + fields=fields, + own_attributes=own_attributes, ) -def get_task_links( +def get_folder_by_path( project_name: str, - task_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query task links from server. + folder_path: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["FolderDict"]: + """Query folder entity by path. + + Folder path is a path to folder with all parent names joined by slash. Args: - project_name (str): Project where links are. - task_id (str): Task id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project where to look for queried + entities. + folder_path (str): Folder path. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - list[dict[str, Any]]: Link info of task. + Optional[FolderDict]: Folder entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_task_links( + return con.get_folder_by_path( project_name=project_name, - task_id=task_id, - link_types=link_types, - link_direction=link_direction, + folder_path=folder_path, + fields=fields, + own_attributes=own_attributes, ) -def get_products_links( +def get_folder_by_name( project_name: str, - product_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query products links from server. + folder_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["FolderDict"]: + """Query folder entity by path. + + Warnings: + Folder name is not a unique identifier of a folder. Function is + kept for OpenPype 3 compatibility. Args: - project_name (str): Project where links are. - product_ids (Optional[Iterable[str]]): Ids of products for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project where to look for queried + entities. + folder_name (str): Folder name. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - dict[str, list[dict[str, Any]]]: Link info by product ids. + Optional[FolderDict]: Folder entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_products_links( + return con.get_folder_by_name( project_name=project_name, - product_ids=product_ids, - link_types=link_types, - link_direction=link_direction, + folder_name=folder_name, + fields=fields, + own_attributes=own_attributes, ) -def get_product_links( +def get_folder_ids_with_products( project_name: str, - product_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query product links from server. + folder_ids: Optional[Iterable[str]] = None, +) -> set[str]: + """Find folders which have at least one product. + + Folders that have at least one product should be immutable, so they + should not change path -> change of name or name of any parent + is not possible. Args: - project_name (str): Project where links are. - product_id (str): Product id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Name of project. + folder_ids (Optional[Iterable[str]]): Limit folder ids filtering + to a set of folders. If set to None all folders on project are + checked. Returns: - list[dict[str, Any]]: Link info of product. + set[str]: Folder ids that have at least one product. """ con = get_server_api_connection() - return con.get_product_links( + return con.get_folder_ids_with_products( project_name=project_name, - product_id=product_id, - link_types=link_types, - link_direction=link_direction, + folder_ids=folder_ids, ) -def get_versions_links( +def create_folder( project_name: str, - version_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query versions links from server. + name: str, + folder_type: Optional[str] = None, + parent_id: Optional[str] = None, + label: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + folder_id: Optional[str] = None, +) -> str: + """Create new folder. Args: - project_name (str): Project where links are. - version_ids (Optional[Iterable[str]]): Ids of versions for which - links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + project_name (str): Project name. + name (str): Folder name. + folder_type (Optional[str]): Folder type. + parent_id (Optional[str]): Parent folder id. Parent is project + if is ``None``. + label (Optional[str]): Label of folder. + attrib (Optional[dict[str, Any]]): Folder attributes. + data (Optional[dict[str, Any]]): Folder data. + tags (Optional[Iterable[str]]): Folder tags. + status (Optional[str]): Folder status. + active (Optional[bool]): Folder active state. + thumbnail_id (Optional[str]): Folder thumbnail id. + folder_id (Optional[str]): Folder id. If not passed new id is + generated. Returns: - dict[str, list[dict[str, Any]]]: Link info by version ids. + str: Entity id. """ con = get_server_api_connection() - return con.get_versions_links( + return con.create_folder( project_name=project_name, - version_ids=version_ids, - link_types=link_types, - link_direction=link_direction, + name=name, + folder_type=folder_type, + parent_id=parent_id, + label=label, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, + folder_id=folder_id, ) -def get_version_links( +def update_folder( project_name: str, - version_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query version links from server. + folder_id: str, + name: Optional[str] = None, + folder_type: Optional[str] = None, + parent_id: Optional[str] = NOT_SET, + label: Optional[str] = NOT_SET, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = NOT_SET, +): + """Update folder entity on server. - Args: - project_name (str): Project where links are. - version_id (str): Version id for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. + Do not pass ``parent_id``, ``label`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. - Returns: - list[dict[str, Any]]: Link info of version. + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. + + Args: + project_name (str): Project name. + folder_id (str): Folder id. + name (Optional[str]): New name. + folder_type (Optional[str]): New folder type. + parent_id (Optional[str]): New parent folder id. + label (Optional[str]): New label. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[str]): New thumbnail id. """ con = get_server_api_connection() - return con.get_version_links( + return con.update_folder( project_name=project_name, - version_id=version_id, - link_types=link_types, - link_direction=link_direction, + folder_id=folder_id, + name=name, + folder_type=folder_type, + parent_id=parent_id, + label=label, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, ) -def get_representations_links( +def delete_folder( project_name: str, - representation_ids: Optional[Iterable[str]] = None, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> dict[str, list[dict[str, Any]]]: - """Query representations links from server. + folder_id: str, + force: bool = False, +): + """Delete folder. Args: - project_name (str): Project where links are. - representation_ids (Optional[Iterable[str]]): Ids of - representations for which links should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - dict[str, list[dict[str, Any]]]: Link info by representation ids. + project_name (str): Project name. + folder_id (str): Folder id to delete. + force (Optional[bool]): Folder delete folder with all children + folder, products, versions and representations. """ con = get_server_api_connection() - return con.get_representations_links( + return con.delete_folder( project_name=project_name, - representation_ids=representation_ids, - link_types=link_types, - link_direction=link_direction, + folder_id=folder_id, + force=force, ) -def get_representation_links( +def get_rest_task( project_name: str, - representation_id: str, - link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, -) -> list[dict[str, Any]]: - """Query representation links from server. - - Args: - project_name (str): Project where links are. - representation_id (str): Representation id for which links - should be received. - link_types (Optional[Iterable[str]]): Link type filters. - link_direction (Optional[Literal["in", "out"]]): Link direction - filter. - - Returns: - list[dict[str, Any]]: Link info of representation. - - """ + task_id: str, +) -> Optional["TaskDict"]: con = get_server_api_connection() - return con.get_representation_links( + return con.get_rest_task( project_name=project_name, - representation_id=representation_id, - link_types=link_types, - link_direction=link_direction, + task_id=task_id, ) -def get_entity_lists( +def get_tasks( project_name: str, - *, - list_ids: Optional[Iterable[str]] = None, - active: Optional[bool] = None, + task_ids: Optional[Iterable[str]] = None, + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + folder_ids: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, -) -> Generator[Dict[str, Any], None, None]: - """Fetch entity lists from server. + own_attributes: bool = False, +) -> Generator["TaskDict", None, None]: + """Query task entities from server. Args: - project_name (str): Project name where entity lists are. - list_ids (Optional[Iterable[str]]): List of entity list ids to - fetch. - active (Optional[bool]): Filter by active state of entity lists. - fields (Optional[Iterable[str]]): Fields to fetch from server. + project_name (str): Name of project. + task_ids (Iterable[str]): Task ids to filter. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + folder_ids (Iterable[str]): Ids of task parents. Use 'None' + if folder is direct child of project. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - Generator[Dict[str, Any], None, None]: Entity list entities - matching defined filters. + Generator[TaskDict, None, None]: Queried task entities. """ con = get_server_api_connection() - return con.get_entity_lists( + return con.get_tasks( project_name=project_name, - list_ids=list_ids, + task_ids=task_ids, + task_names=task_names, + task_types=task_types, + folder_ids=folder_ids, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, + tags=tags, active=active, fields=fields, + own_attributes=own_attributes, ) -def get_entity_list_rest( +def get_task_by_name( project_name: str, - list_id: str, -) -> Optional[Dict[str, Any]]: - """Get entity list by id using REST API. + folder_id: str, + task_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["TaskDict"]: + """Query task entity by name and folder id. Args: - project_name (str): Project name. - list_id (str): Entity list id. + project_name (str): Name of project where to look for queried + entities. + folder_id (str): Folder id. + task_name (str): Task name + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - Optional[Dict[str, Any]]: Entity list data or None if not found. + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.get_entity_list_rest( + return con.get_task_by_name( project_name=project_name, - list_id=list_id, + folder_id=folder_id, + task_name=task_name, + fields=fields, + own_attributes=own_attributes, ) -def get_entity_list_by_id( +def get_task_by_id( project_name: str, - list_id: str, + task_id: str, fields: Optional[Iterable[str]] = None, -) -> Optional[Dict[str, Any]]: - """Get entity list by id using GraphQl. + own_attributes: bool = False, +) -> Optional["TaskDict"]: + """Query task entity by id. Args: - project_name (str): Project name. - list_id (str): Entity list id. - fields (Optional[Iterable[str]]): Fields to fetch from server. + project_name (str): Name of project where to look for queried + entities. + task_id (str): Task id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. Returns: - Optional[Dict[str, Any]]: Entity list data or None if not found. + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.get_entity_list_by_id( + return con.get_task_by_id( project_name=project_name, - list_id=list_id, + task_id=task_id, fields=fields, + own_attributes=own_attributes, ) -def create_entity_list( +def get_tasks_by_folder_paths( project_name: str, - entity_type: "EntityListEntityType", - label: str, - *, - list_type: Optional[str] = None, - access: Optional[Dict[str, Any]] = None, - attrib: Optional[List[Dict[str, Any]]] = None, - data: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[str]] = None, - template: Optional[Dict[str, Any]] = None, - owner: Optional[str] = None, - active: Optional[bool] = None, - items: Optional[List[Dict[str, Any]]] = None, - list_id: Optional[str] = None, -) -> str: - """Create entity list. + folder_paths: Iterable[str], + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> dict[str, list["TaskDict"]]: + """Query task entities from server by folder paths. Args: - project_name (str): Project name where entity list lives. - entity_type (EntityListEntityType): Which entity types can be - used in list. - label (str): Entity list label. - list_type (Optional[str]): Entity list type. - access (Optional[dict[str, Any]]): Access control for entity list. - attrib (Optional[dict[str, Any]]): Attribute values of - entity list. - data (Optional[dict[str, Any]]): Custom data of entity list. - tags (Optional[list[str]]): Entity list tags. - template (Optional[dict[str, Any]]): Dynamic list template. - owner (Optional[str]): New owner of the list. - active (Optional[bool]): Change active state of entity list. - items (Optional[list[dict[str, Any]]]): Initial items in - entity list. - list_id (Optional[str]): Entity list id. + project_name (str): Name of project. + folder_paths (list[str]): Folder paths. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + dict[str, list[TaskDict]]: Task entities by + folder path. """ con = get_server_api_connection() - return con.create_entity_list( + return con.get_tasks_by_folder_paths( project_name=project_name, - entity_type=entity_type, - label=label, - list_type=list_type, - access=access, - attrib=attrib, - data=data, + folder_paths=folder_paths, + task_names=task_names, + task_types=task_types, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, tags=tags, - template=template, - owner=owner, active=active, - items=items, - list_id=list_id, + fields=fields, + own_attributes=own_attributes, ) -def update_entity_list( +def get_tasks_by_folder_path( project_name: str, - list_id: str, - *, - label: Optional[str] = None, - access: Optional[Dict[str, Any]] = None, - attrib: Optional[List[Dict[str, Any]]] = None, - data: Optional[List[Dict[str, Any]]] = None, - tags: Optional[List[str]] = None, - owner: Optional[str] = None, - active: Optional[bool] = None, -) -> None: - """Update entity list. + folder_path: str, + task_names: Optional[Iterable[str]] = None, + task_types: Optional[Iterable[str]] = None, + assignees: Optional[Iterable[str]] = None, + assignees_all: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> list["TaskDict"]: + """Query task entities from server by folder path. Args: - project_name (str): Project name where entity list lives. - list_id (str): Entity list id that will be updated. - label (Optional[str]): New label of entity list. - access (Optional[dict[str, Any]]): Access control for entity list. - attrib (Optional[dict[str, Any]]): Attribute values of - entity list. - data (Optional[dict[str, Any]]): Custom data of entity list. - tags (Optional[list[str]]): Entity list tags. - owner (Optional[str]): New owner of the list. - active (Optional[bool]): Change active state of entity list. + project_name (str): Name of project. + folder_path (str): Folder path. + task_names (Iterable[str]): Task names used for filtering. + task_types (Iterable[str]): Task types used for filtering. + assignees (Optional[Iterable[str]]): Task assignees used for + filtering. All tasks with any of passed assignees are + returned. + assignees_all (Optional[Iterable[str]]): Task assignees used + for filtering. Task must have all of passed assignees to be + returned. + statuses (Optional[Iterable[str]]): Task statuses used for + filtering. + tags (Optional[Iterable[str]]): Task tags used for + filtering. + active (Optional[bool]): Filter active/inactive tasks. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. """ con = get_server_api_connection() - return con.update_entity_list( + return con.get_tasks_by_folder_path( project_name=project_name, - list_id=list_id, - label=label, - access=access, - attrib=attrib, - data=data, + folder_path=folder_path, + task_names=task_names, + task_types=task_types, + assignees=assignees, + assignees_all=assignees_all, + statuses=statuses, tags=tags, - owner=owner, active=active, + fields=fields, + own_attributes=own_attributes, ) -def delete_entity_list( +def get_task_by_folder_path( project_name: str, - list_id: str, -) -> None: - """Delete entity list from project. + folder_path: str, + task_name: str, + fields: Optional[Iterable[str]] = None, + own_attributes: bool = False, +) -> Optional["TaskDict"]: + """Query task entity by folder path and task name. Args: project_name (str): Project name. - list_id (str): Entity list id that will be removed. + folder_path (str): Folder path. + task_name (str): Task name. + fields (Optional[Iterable[str]]): Task fields that should + be returned. + own_attributes (Optional[bool]): Attribute values that are + not explicitly set on entity will have 'None' value. + + Returns: + Optional[TaskDict]: Task entity data or None if was not found. """ con = get_server_api_connection() - return con.delete_entity_list( + return con.get_task_by_folder_path( project_name=project_name, - list_id=list_id, + folder_path=folder_path, + task_name=task_name, + fields=fields, + own_attributes=own_attributes, ) -def get_entity_list_attribute_definitions( +def create_task( project_name: str, - list_id: str, -) -> List["EntityListAttributeDefinitionDict"]: - """Get attribute definitioins on entity list. + name: str, + task_type: str, + folder_id: str, + label: Optional[str] = None, + assignees: Optional[Iterable[str]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = None, + task_id: Optional[str] = None, +) -> str: + """Create new task. Args: project_name (str): Project name. - list_id (str): Entity list id. + name (str): Folder name. + task_type (str): Task type. + folder_id (str): Parent folder id. + label (Optional[str]): Label of folder. + assignees (Optional[Iterable[str]]): Task assignees. + attrib (Optional[dict[str, Any]]): Task attributes. + data (Optional[dict[str, Any]]): Task data. + tags (Optional[Iterable[str]]): Task tags. + status (Optional[str]): Task status. + active (Optional[bool]): Task active state. + thumbnail_id (Optional[str]): Task thumbnail id. + task_id (Optional[str]): Task id. If not passed new id is + generated. Returns: - List[EntityListAttributeDefinitionDict]: List of attribute - definitions. + str: Task id. """ con = get_server_api_connection() - return con.get_entity_list_attribute_definitions( + return con.create_task( project_name=project_name, - list_id=list_id, + name=name, + task_type=task_type, + folder_id=folder_id, + label=label, + assignees=assignees, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + thumbnail_id=thumbnail_id, + task_id=task_id, ) -def set_entity_list_attribute_definitions( +def update_task( project_name: str, - list_id: str, - attribute_definitions: List["EntityListAttributeDefinitionDict"], -) -> None: - """Set attribute definitioins on entity list. - - Args: - project_name (str): Project name. - list_id (str): Entity list id. - attribute_definitions (List[EntityListAttributeDefinitionDict]): - List of attribute definitions. + task_id: str, + name: Optional[str] = None, + task_type: Optional[str] = None, + folder_id: Optional[str] = None, + label: Optional[str] = NOT_SET, + assignees: Optional[list[str]] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + thumbnail_id: Optional[str] = NOT_SET, +): + """Update task entity on server. - """ - con = get_server_api_connection() - return con.set_entity_list_attribute_definitions( - project_name=project_name, - list_id=list_id, - attribute_definitions=attribute_definitions, - ) + Do not pass ``label`` amd ``thumbnail_id`` if you don't + want to change their values. Value ``None`` would unset + their value. + Update of ``data`` will override existing value on folder entity. -def create_entity_list_item( - project_name: str, - list_id: str, - *, - position: Optional[int] = None, - label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, - item_id: Optional[str] = None, -) -> str: - """Create entity list item. + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. Args: - project_name (str): Project name where entity list lives. - list_id (str): Entity list id where item will be added. - position (Optional[int]): Position of item in entity list. - label (Optional[str]): Label of item in entity list. - attrib (Optional[dict[str, Any]]): Item attribute values. - data (Optional[dict[str, Any]]): Item data. - tags (Optional[list[str]]): Tags of item in entity list. - item_id (Optional[str]): Id of item that will be created. - - Returns: - str: Item id. + project_name (str): Project name. + task_id (str): Task id. + name (Optional[str]): New name. + task_type (Optional[str]): New task type. + folder_id (Optional[str]): New folder id. + label (Optional[Optional[str]]): New label. + assignees (Optional[str]): New assignees. + attrib (Optional[dict[str, Any]]): New attributes. + data (Optional[dict[str, Any]]): New data. + tags (Optional[Iterable[str]]): New tags. + status (Optional[str]): New status. + active (Optional[bool]): New active state. + thumbnail_id (Optional[str]): New thumbnail id. """ con = get_server_api_connection() - return con.create_entity_list_item( + return con.update_task( project_name=project_name, - list_id=list_id, - position=position, + task_id=task_id, + name=name, + task_type=task_type, + folder_id=folder_id, label=label, + assignees=assignees, attrib=attrib, data=data, tags=tags, - item_id=item_id, + status=status, + active=active, + thumbnail_id=thumbnail_id, ) -def update_entity_list_items( +def delete_task( project_name: str, - list_id: str, - items: List[Dict[str, Any]], - mode: "EntityListItemMode", -) -> None: - """Update items in entity list. + task_id: str, +): + """Delete task. Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id. - items (List[Dict[str, Any]]): Entity list items. - mode (EntityListItemMode): Mode of items update. + project_name (str): Project name. + task_id (str): Task id to delete. """ con = get_server_api_connection() - return con.update_entity_list_items( + return con.delete_task( project_name=project_name, - list_id=list_id, - items=items, - mode=mode, + task_id=task_id, ) -def update_entity_list_item( +def get_rest_product( project_name: str, - list_id: str, - item_id: str, - *, - new_list_id: Optional[str], - position: Optional[int] = None, - label: Optional[str] = None, - attrib: Optional[Dict[str, Any]] = None, - data: Optional[Dict[str, Any]] = None, - tags: Optional[List[str]] = None, -) -> None: - """Update item in entity list. - - Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id where item lives. - item_id (str): Item id that will be removed from entity list. - new_list_id (Optional[str]): New entity list id where item will be - added. - position (Optional[int]): Position of item in entity list. - label (Optional[str]): Label of item in entity list. - attrib (Optional[dict[str, Any]]): Attributes of item in entity - list. - data (Optional[dict[str, Any]]): Custom data of item in - entity list. - tags (Optional[list[str]]): Tags of item in entity list. - - """ + product_id: str, +) -> Optional["ProductDict"]: con = get_server_api_connection() - return con.update_entity_list_item( + return con.get_rest_product( project_name=project_name, - list_id=list_id, - item_id=item_id, - new_list_id=new_list_id, - position=position, - label=label, - attrib=attrib, - data=data, - tags=tags, + product_id=product_id, ) -def delete_entity_list_item( +def get_products( project_name: str, - list_id: str, - item_id: str, -) -> None: - """Delete item from entity list. + product_ids: Optional[Iterable[str]] = None, + product_names: Optional[Iterable[str]] = None, + folder_ids: Optional[Iterable[str]] = None, + product_types: Optional[Iterable[str]] = None, + product_name_regex: Optional[str] = None, + product_path_regex: Optional[str] = None, + names_by_folder_ids: Optional[dict[str, Iterable[str]]] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Generator["ProductDict", None, None]: + """Query products from server. + + Todos: + Separate 'name_by_folder_ids' filtering to separated method. It + cannot be combined with some other filters. Args: - project_name (str): Project name where entity list live. - list_id (str): Entity list id from which item will be removed. - item_id (str): Item id that will be removed from entity list. + project_name (str): Name of project. + product_ids (Optional[Iterable[str]]): Task ids to filter. + product_names (Optional[Iterable[str]]): Task names used for + filtering. + folder_ids (Optional[Iterable[str]]): Ids of task parents. + Use 'None' if folder is direct child of project. + product_types (Optional[Iterable[str]]): Product types used for + filtering. + product_name_regex (Optional[str]): Filter products by name regex. + product_path_regex (Optional[str]): Filter products by path regex. + Path starts with folder path and ends with product name. + names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product + name filtering by folder id. + statuses (Optional[Iterable[str]]): Product statuses used + for filtering. + tags (Optional[Iterable[str]]): Product tags used + for filtering. + active (Optional[bool]): Filter active/inactive products. + Both are returned if is set to None. + fields (Optional[Iterable[str]]): Fields to be queried for + folder. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. + + Returns: + Generator[ProductDict, None, None]: Queried product entities. """ con = get_server_api_connection() - return con.delete_entity_list_item( + return con.get_products( project_name=project_name, - list_id=list_id, - item_id=item_id, + product_ids=product_ids, + product_names=product_names, + folder_ids=folder_ids, + product_types=product_types, + product_name_regex=product_name_regex, + product_path_regex=product_path_regex, + names_by_folder_ids=names_by_folder_ids, + statuses=statuses, + tags=tags, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def get_rest_project( +def get_product_by_id( project_name: str, -) -> Optional["ProjectDict"]: - """Query project by name. - - This call returns project with anatomy data. + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["ProductDict"]: + """Query product entity by id. Args: - project_name (str): Name of project. + project_name (str): Name of project where to look for queried + entities. + product_id (str): Product id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. Returns: - Optional[ProjectDict]: Project entity data or 'None' if - project was not found. + Optional[ProductDict]: Product entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_rest_project( + return con.get_product_by_id( project_name=project_name, + product_id=product_id, + fields=fields, + own_attributes=own_attributes, ) -def get_rest_projects( - active: Optional[bool] = True, - library: Optional[bool] = None, -) -> Generator["ProjectDict", None, None]: - """Query available project entities. - - User must be logged in. +def get_product_by_name( + project_name: str, + product_name: str, + folder_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["ProductDict"]: + """Query product entity by name and folder id. Args: - active (Optional[bool]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Optional[bool]): Filter standard/library projects. Both - are returned if 'None' is passed. + project_name (str): Name of project where to look for queried + entities. + product_name (str): Product name. + folder_id (str): Folder id (Folder is a parent of products). + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + products. Returns: - Generator[ProjectDict, None, None]: Available projects. + Optional[ProductDict]: Product entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_rest_projects( - active=active, - library=library, + return con.get_product_by_name( + project_name=project_name, + product_name=product_name, + folder_id=folder_id, + fields=fields, + own_attributes=own_attributes, ) -def get_project_names( - active: Optional[bool] = True, - library: Optional[bool] = None, -) -> list[str]: - """Receive available project names. +def get_product_types( + fields: Optional[Iterable[str]] = None, +) -> list["ProductTypeDict"]: + """Types of products. - User must be logged in. + This is server wide information. Product types have 'name', 'icon' and + 'color'. Args: - active (Optional[bool]): Filter active/inactive projects. Both - are returned if 'None' is passed. - library (Optional[bool]): Filter standard/library projects. Both - are returned if 'None' is passed. + fields (Optional[Iterable[str]]): Product types fields to query. Returns: - list[str]: List of available project names. + list[ProductTypeDict]: Product types information. """ con = get_server_api_connection() - return con.get_project_names( - active=active, - library=library, + return con.get_product_types( + fields=fields, ) -def get_projects( - active: Optional[bool] = True, - library: Optional[bool] = None, +def get_project_product_types( + project_name: str, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Generator["ProjectDict", None, None]: - """Get projects. +) -> list["ProductTypeDict"]: + """DEPRECATED Types of products available in a project. + + Filter only product types available in a project. Args: - active (Optional[bool]): Filter active or inactive projects. - Filter is disabled when 'None' is passed. - library (Optional[bool]): Filter library projects. Filter is - disabled when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Name of the project where to look for + product types. + fields (Optional[Iterable[str]]): Product types fields to query. Returns: - Generator[ProjectDict, None, None]: Queried projects. + list[ProductTypeDict]: Product types information. """ con = get_server_api_connection() - return con.get_projects( - active=active, - library=library, + return con.get_project_product_types( + project_name=project_name, fields=fields, - own_attributes=own_attributes, ) -def get_project( - project_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["ProjectDict"]: - """Get project. +def get_product_type_names( + project_name: Optional[str] = None, + product_ids: Optional[Iterable[str]] = None, +) -> set[str]: + """DEPRECATED Product type names. + + Warnings: + This function will be probably removed. Matters if 'products_id' + filter has real use-case. Args: - project_name (str): Name of project. - fields (Optional[Iterable[str]]): fields to be queried - for project. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (Optional[str]): Name of project where to look for + queried entities. + product_ids (Optional[Iterable[str]]): Product ids filter. Can be + used only with 'project_name'. Returns: - Optional[ProjectDict]: Project entity data or None - if project was not found. + set[str]: Product type names. """ con = get_server_api_connection() - return con.get_project( + return con.get_product_type_names( project_name=project_name, - fields=fields, - own_attributes=own_attributes, + product_ids=product_ids, ) -def create_project( +def create_product( project_name: str, - project_code: str, - library_project: bool = False, - preset_name: Optional[str] = None, -) -> "ProjectDict": - """Create project using AYON settings. - - This project creation function is not validating project entity on - creation. It is because project entity is created blindly with only - minimum required information about project which is name and code. - - Entered project name must be unique and project must not exist yet. - - Note: - This function is here to be OP v4 ready but in v3 has more logic - to do. That's why inner imports are in the body. + name: str, + product_type: str, + folder_id: str, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, + active: Optional[bool] = None, + product_id: Optional[str] = None, +) -> str: + """Create new product. Args: - project_name (str): New project name. Should be unique. - project_code (str): Project's code should be unique too. - library_project (Optional[bool]): Project is library project. - preset_name (Optional[str]): Name of anatomy preset. Default is - used if not passed. - - Raises: - ValueError: When project name already exists. + project_name (str): Project name. + name (str): Product name. + product_type (str): Product type. + folder_id (str): Parent folder id. + attrib (Optional[dict[str, Any]]): Product attributes. + data (Optional[dict[str, Any]]): Product data. + tags (Optional[Iterable[str]]): Product tags. + status (Optional[str]): Product status. + active (Optional[bool]): Product active state. + product_id (Optional[str]): Product id. If not passed new id is + generated. Returns: - ProjectDict: Created project entity. + str: Product id. """ con = get_server_api_connection() - return con.create_project( + return con.create_product( project_name=project_name, - project_code=project_code, - library_project=library_project, - preset_name=preset_name, + name=name, + product_type=product_type, + folder_id=folder_id, + attrib=attrib, + data=data, + tags=tags, + status=status, + active=active, + product_id=product_id, ) -def update_project( +def update_product( project_name: str, - library: Optional[bool] = None, - folder_types: Optional[list[dict[str, Any]]] = None, - task_types: Optional[list[dict[str, Any]]] = None, - link_types: Optional[list[dict[str, Any]]] = None, - statuses: Optional[list[dict[str, Any]]] = None, - tags: Optional[list[dict[str, Any]]] = None, - config: Optional[dict[str, Any]] = None, + product_id: str, + name: Optional[str] = None, + folder_id: Optional[str] = None, + product_type: Optional[str] = None, attrib: Optional[dict[str, Any]] = None, data: Optional[dict[str, Any]] = None, + tags: Optional[Iterable[str]] = None, + status: Optional[str] = None, active: Optional[bool] = None, - project_code: Optional[str] = None, - **changes, ): - """Update project entity on server. + """Update product entity on server. + + Update of ``data`` will override existing value on folder entity. + + Update of ``attrib`` does change only passed attributes. If you want + to unset value, use ``None``. 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[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. + project_name (str): Project name. + product_id (str): Product id. + name (Optional[str]): New product name. + folder_id (Optional[str]): New product id. + product_type (Optional[str]): New product type. + attrib (Optional[dict[str, Any]]): New product attributes. + data (Optional[dict[str, Any]]): New product data. + tags (Optional[Iterable[str]]): New product tags. + status (Optional[str]): New product status. + active (Optional[bool]): New product active state. """ con = get_server_api_connection() - return con.update_project( + return con.update_product( project_name=project_name, - library=library, - folder_types=folder_types, - task_types=task_types, - link_types=link_types, - statuses=statuses, - tags=tags, - config=config, + product_id=product_id, + name=name, + folder_id=folder_id, + product_type=product_type, attrib=attrib, data=data, + tags=tags, + status=status, active=active, - project_code=project_code, - **changes, ) -def delete_project( +def delete_product( project_name: str, + product_id: str, ): - """Delete project from server. + """Delete product. - This will completely remove project from server without any step back. + Args: + project_name (str): Project name. + product_id (str): Product id to delete. + + """ + con = get_server_api_connection() + return con.delete_product( + project_name=project_name, + product_id=product_id, + ) + + +def get_rest_version( + project_name: str, + version_id: str, +) -> Optional["VersionDict"]: + con = get_server_api_connection() + return con.get_rest_version( + project_name=project_name, + version_id=version_id, + ) + + +def get_versions( + project_name: str, + version_ids: Optional[Iterable[str]] = None, + product_ids: Optional[Iterable[str]] = None, + task_ids: Optional[Iterable[str]] = None, + versions: Optional[Iterable[str]] = None, + hero: bool = True, + standard: bool = True, + latest: Optional[bool] = None, + statuses: Optional[Iterable[str]] = None, + tags: Optional[Iterable[str]] = None, + active: Optional[bool] = True, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Generator["VersionDict", None, None]: + """Get version entities based on passed filters from server. Args: - project_name (str): Project name that will be removed. + project_name (str): Name of project where to look for versions. + version_ids (Optional[Iterable[str]]): Version ids used for + version filtering. + product_ids (Optional[Iterable[str]]): Product ids used for + version filtering. + task_ids (Optional[Iterable[str]]): Task ids used for + version filtering. + versions (Optional[Iterable[int]]): Versions we're interested in. + hero (Optional[bool]): Skip hero versions when set to False. + standard (Optional[bool]): Skip standard (non-hero) when + set to False. + latest (Optional[bool]): Return only latest version of standard + versions. This can be combined only with 'standard' attribute + set to True. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): Fields to be queried + for version. All possible folder fields are returned + if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Generator[VersionDict, None, None]: Queried version entities. """ con = get_server_api_connection() - return con.delete_project( + return con.get_versions( project_name=project_name, + version_ids=version_ids, + product_ids=product_ids, + task_ids=task_ids, + versions=versions, + hero=hero, + standard=standard, + latest=latest, + statuses=statuses, + tags=tags, + active=active, + fields=fields, + own_attributes=own_attributes, ) -def get_rest_folder( - project_name: str, - folder_id: str, -) -> Optional["FolderDict"]: +def get_version_by_id( + project_name: str, + version_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query version entity by id. + + Args: + project_name (str): Name of project where to look for queried + entities. + version_id (str): Version id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. + + Returns: + Optional[VersionDict]: Version entity data or None + if was not found. + + """ con = get_server_api_connection() - return con.get_rest_folder( + return con.get_version_by_id( project_name=project_name, - folder_id=folder_id, + version_id=version_id, + fields=fields, + own_attributes=own_attributes, ) -def get_rest_folders( +def get_version_by_name( project_name: str, - include_attrib: bool = False, -) -> list["FlatFolderDict"]: - """Get simplified flat list of all project folders. - - Get all project folders in single REST call. This can be faster than - using 'get_folders' method which is using GraphQl, but does not - allow any filtering, and set of fields is defined - by server backend. - - Example:: - - [ - { - "id": "112233445566", - "parentId": "112233445567", - "path": "/root/parent/child", - "parents": ["root", "parent"], - "name": "child", - "label": "Child", - "folderType": "Folder", - "hasTasks": False, - "hasChildren": False, - "taskNames": [ - "Compositing", - ], - "status": "In Progress", - "attrib": {}, - "ownAttrib": [], - "updatedAt": "2023-06-12T15:37:02.420260", - }, - ... - ] + version: int, + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query version entity by version and product id. Args: - project_name (str): Project name. - include_attrib (Optional[bool]): Include attribute values - in output. Slower to query. + project_name (str): Name of project where to look for queried + entities. + version (int): Version of version entity. + product_id (str): Product id. Product is a parent of version. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - List[FlatFolderDict]: List of folder entities. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_rest_folders( + return con.get_version_by_name( project_name=project_name, - include_attrib=include_attrib, + version=version, + product_id=product_id, + fields=fields, + own_attributes=own_attributes, ) -def get_folders_hierarchy( +def get_hero_version_by_id( project_name: str, - search_string: Optional[str] = None, - folder_types: Optional[Iterable[str]] = None, -) -> "ProjectHierarchyDict": - """Get project hierarchy. - - All folders in project in hierarchy data structure. - - Example output: - { - "hierarchy": [ - { - "id": "...", - "name": "...", - "label": "...", - "status": "...", - "folderType": "...", - "hasTasks": False, - "taskNames": [], - "parents": [], - "parentId": None, - "children": [...children folders...] - }, - ... - ] - } + version_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query hero version entity by id. Args: - project_name (str): Project where to look for folders. - search_string (Optional[str]): Search string to filter folders. - folder_types (Optional[Iterable[str]]): Folder types to filter. + project_name (str): Name of project where to look for queried + entities. + version_id (int): Hero version id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - dict[str, Any]: Response data from server. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_folders_hierarchy( + return con.get_hero_version_by_id( project_name=project_name, - search_string=search_string, - folder_types=folder_types, + version_id=version_id, + fields=fields, + own_attributes=own_attributes, ) -def get_folders_rest( +def get_hero_version_by_product_id( project_name: str, - include_attrib: bool = False, -) -> list["FlatFolderDict"]: - """Get simplified flat list of all project folders. - - Get all project folders in single REST call. This can be faster than - using 'get_folders' method which is using GraphQl, but does not - allow any filtering, and set of fields is defined - by server backend. - - Example:: - - [ - { - "id": "112233445566", - "parentId": "112233445567", - "path": "/root/parent/child", - "parents": ["root", "parent"], - "name": "child", - "label": "Child", - "folderType": "Folder", - "hasTasks": False, - "hasChildren": False, - "taskNames": [ - "Compositing", - ], - "status": "In Progress", - "attrib": {}, - "ownAttrib": [], - "updatedAt": "2023-06-12T15:37:02.420260", - }, - ... - ] + product_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query hero version entity by product id. - Deprecated: - Use 'get_rest_folders' instead. Function was renamed to match - other rest functions, like 'get_rest_folder', - 'get_rest_project' etc. . - Will be removed in '1.0.7' or '1.1.0'. + Only one hero version is available on a product. Args: - project_name (str): Project name. - include_attrib (Optional[bool]): Include attribute values - in output. Slower to query. + project_name (str): Name of project where to look for queried + entities. + product_id (int): Product id. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - List[FlatFolderDict]: List of folder entities. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_folders_rest( + return con.get_hero_version_by_product_id( project_name=project_name, - include_attrib=include_attrib, + product_id=product_id, + fields=fields, + own_attributes=own_attributes, ) -def get_folders( +def get_hero_versions( project_name: str, - folder_ids: Optional[Iterable[str]] = None, - folder_paths: Optional[Iterable[str]] = None, - folder_names: Optional[Iterable[str]] = None, - folder_types: Optional[Iterable[str]] = None, - parent_ids: Optional[Iterable[str]] = None, - folder_path_regex: Optional[str] = None, - has_products: Optional[bool] = None, - has_tasks: Optional[bool] = None, - has_children: Optional[bool] = None, - statuses: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, + product_ids: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, active: Optional[bool] = True, - has_links: Optional[bool] = None, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Generator["FolderDict", None, None]: - """Query folders from server. - - Todos: - Folder name won't be unique identifier, so we should add - folder path filtering. - - Notes: - Filter 'active' don't have direct filter in GraphQl. - - Args: - project_name (str): Name of project. - folder_ids (Optional[Iterable[str]]): Folder ids to filter. - folder_paths (Optional[Iterable[str]]): Folder paths used - for filtering. - folder_names (Optional[Iterable[str]]): Folder names used - for filtering. - folder_types (Optional[Iterable[str]]): Folder types used - for filtering. - parent_ids (Optional[Iterable[str]]): Ids of folder parents. - Use 'None' if folder is direct child of project. - folder_path_regex (Optional[str]): Folder path regex used - for filtering. - has_products (Optional[bool]): Filter folders with/without - products. Ignored when None, default behavior. - has_tasks (Optional[bool]): Filter folders with/without - tasks. Ignored when None, default behavior. - has_children (Optional[bool]): Filter folders with/without - children. Ignored when None, default behavior. - statuses (Optional[Iterable[str]]): Folder statuses used - for filtering. - assignees_all (Optional[Iterable[str]]): Filter by assigness - on children tasks. Task must have all of passed assignees. - tags (Optional[Iterable[str]]): Folder tags used - for filtering. - active (Optional[bool]): Filter active/inactive folders. - Both are returned if is set to None. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + own_attributes=_PLACEHOLDER, +) -> Generator["VersionDict", None, None]: + """Query hero versions by multiple filters. + + Only one hero version is available on a product. + + Args: + project_name (str): Name of project where to look for queried + entities. + product_ids (Optional[Iterable[str]]): Product ids. + version_ids (Optional[Iterable[str]]): Version ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): Fields that should be returned. + All fields are returned if 'None' is passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - Generator[FolderDict, None, None]: Queried folder entities. + Optional[VersionDict]: Version entity data or None + if was not found. """ con = get_server_api_connection() - return con.get_folders( + return con.get_hero_versions( project_name=project_name, - folder_ids=folder_ids, - folder_paths=folder_paths, - folder_names=folder_names, - folder_types=folder_types, - parent_ids=parent_ids, - folder_path_regex=folder_path_regex, - has_products=has_products, - has_tasks=has_tasks, - has_children=has_children, - statuses=statuses, - assignees_all=assignees_all, - tags=tags, + product_ids=product_ids, + version_ids=version_ids, active=active, - has_links=has_links, fields=fields, own_attributes=own_attributes, ) -def get_folder_by_id( +def get_last_versions( project_name: str, - folder_id: str, + product_ids: Iterable[str], + active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["FolderDict"]: - """Query folder entity by id. + own_attributes=_PLACEHOLDER, +) -> dict[str, Optional["VersionDict"]]: + """Query last version entities by product ids. Args: - project_name (str): Name of project where to look for queried - entities. - folder_id (str): Folder id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where to look for representation. + product_ids (Iterable[str]): Product ids. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. + dict[str, Optional[VersionDict]]: Last versions by product id. """ con = get_server_api_connection() - return con.get_folder_by_id( + return con.get_last_versions( project_name=project_name, - folder_id=folder_id, + product_ids=product_ids, + active=active, fields=fields, own_attributes=own_attributes, ) -def get_folder_by_path( +def get_last_version_by_product_id( project_name: str, - folder_path: str, + product_id: str, + active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["FolderDict"]: - """Query folder entity by path. - - Folder path is a path to folder with all parent names joined by slash. + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query last version entity by product id. Args: - project_name (str): Name of project where to look for queried - entities. - folder_path (str): Folder path. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where to look for representation. + product_id (str): Product id. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + versions. Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. + Optional[VersionDict]: Queried version entity or None. """ con = get_server_api_connection() - return con.get_folder_by_path( + return con.get_last_version_by_product_id( project_name=project_name, - folder_path=folder_path, + product_id=product_id, + active=active, fields=fields, own_attributes=own_attributes, ) -def get_folder_by_name( +def get_last_version_by_product_name( project_name: str, - folder_name: str, + product_name: str, + folder_id: str, + active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["FolderDict"]: - """Query folder entity by path. - - Warnings: - Folder name is not a unique identifier of a folder. Function is - kept for OpenPype 3 compatibility. + own_attributes=_PLACEHOLDER, +) -> Optional["VersionDict"]: + """Query last version entity by product name and folder id. Args: - project_name (str): Name of project where to look for queried - entities. - folder_name (str): Folder name. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where to look for representation. + product_name (str): Product name. + folder_id (str): Folder id. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. Returns: - Optional[FolderDict]: Folder entity data or None - if was not found. + Optional[VersionDict]: Queried version entity or None. """ con = get_server_api_connection() - return con.get_folder_by_name( + return con.get_last_version_by_product_name( project_name=project_name, - folder_name=folder_name, + product_name=product_name, + folder_id=folder_id, + active=active, fields=fields, own_attributes=own_attributes, ) -def get_folder_ids_with_products( +def version_is_latest( project_name: str, - folder_ids: Optional[Iterable[str]] = None, -) -> set[str]: - """Find folders which have at least one product. - - Folders that have at least one product should be immutable, so they - should not change path -> change of name or name of any parent - is not possible. + version_id: str, +) -> bool: + """Is version latest from a product. Args: - project_name (str): Name of project. - folder_ids (Optional[Iterable[str]]): Limit folder ids filtering - to a set of folders. If set to None all folders on project are - checked. + project_name (str): Project where to look for representation. + version_id (str): Version id. Returns: - set[str]: Folder ids that have at least one product. + bool: Version is latest or not. """ con = get_server_api_connection() - return con.get_folder_ids_with_products( + return con.version_is_latest( project_name=project_name, - folder_ids=folder_ids, + version_id=version_id, ) -def create_folder( +def create_version( project_name: str, - name: str, - folder_type: Optional[str] = None, - parent_id: Optional[str] = None, - label: Optional[str] = None, + version: int, + product_id: str, + task_id: Optional[str] = None, + author: Optional[str] = None, attrib: Optional[dict[str, Any]] = None, data: Optional[dict[str, Any]] = None, tags: Optional[Iterable[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = None, - folder_id: Optional[str] = None, + version_id: Optional[str] = None, ) -> str: - """Create new folder. + """Create new version. Args: project_name (str): Project name. - name (str): Folder name. - folder_type (Optional[str]): Folder type. - parent_id (Optional[str]): Parent folder id. Parent is project - if is ``None``. - label (Optional[str]): Label of folder. - attrib (Optional[dict[str, Any]]): Folder attributes. - data (Optional[dict[str, Any]]): Folder data. - tags (Optional[Iterable[str]]): Folder tags. - status (Optional[str]): Folder status. - active (Optional[bool]): Folder active state. - thumbnail_id (Optional[str]): Folder thumbnail id. - folder_id (Optional[str]): Folder id. If not passed new id is + version (int): Version. + product_id (str): Parent product id. + task_id (Optional[str]): Parent task id. + author (Optional[str]): Version author. + attrib (Optional[dict[str, Any]]): Version attributes. + data (Optional[dict[str, Any]]): Version data. + tags (Optional[Iterable[str]]): Version tags. + status (Optional[str]): Version status. + active (Optional[bool]): Version active state. + thumbnail_id (Optional[str]): Version thumbnail id. + version_id (Optional[str]): Version id. If not passed new id is generated. Returns: - str: Entity id. + str: Version id. """ con = get_server_api_connection() - return con.create_folder( + return con.create_version( project_name=project_name, - name=name, - folder_type=folder_type, - parent_id=parent_id, - label=label, + version=version, + product_id=product_id, + task_id=task_id, + author=author, attrib=attrib, data=data, tags=tags, status=status, active=active, thumbnail_id=thumbnail_id, - folder_id=folder_id, + version_id=version_id, ) -def update_folder( +def update_version( project_name: str, - folder_id: str, - name: Optional[str] = None, - folder_type: Optional[str] = None, - parent_id: Optional[str] = NOT_SET, - label: Optional[str] = NOT_SET, + version_id: str, + version: Optional[int] = None, + product_id: Optional[str] = None, + task_id: Optional[str] = NOT_SET, + author: Optional[str] = None, attrib: Optional[dict[str, Any]] = None, data: Optional[dict[str, Any]] = None, tags: Optional[Iterable[str]] = None, @@ -5192,9 +5479,9 @@ def update_folder( active: Optional[bool] = None, thumbnail_id: Optional[str] = NOT_SET, ): - """Update folder entity on server. + """Update version entity on server. - Do not pass ``parent_id``, ``label`` amd ``thumbnail_id`` if you don't + Do not pass ``task_id`` amd ``thumbnail_id`` if you don't want to change their values. Value ``None`` would unset their value. @@ -5205,11 +5492,11 @@ def update_folder( Args: project_name (str): Project name. - folder_id (str): Folder id. - name (Optional[str]): New name. - folder_type (Optional[str]): New folder type. - parent_id (Optional[str]): New parent folder id. - label (Optional[str]): New label. + version_id (str): Version id. + version (Optional[int]): New version. + product_id (Optional[str]): New product id. + task_id (Optional[str]): New task id. + author (Optional[str]): New author username. attrib (Optional[dict[str, Any]]): New attributes. data (Optional[dict[str, Any]]): New data. tags (Optional[Iterable[str]]): New tags. @@ -5219,13 +5506,13 @@ def update_folder( """ con = get_server_api_connection() - return con.update_folder( + return con.update_version( project_name=project_name, - folder_id=folder_id, - name=name, - folder_type=folder_type, - parent_id=parent_id, - label=label, + version_id=version_id, + version=version, + product_id=product_id, + task_id=task_id, + author=author, attrib=attrib, data=data, tags=tags, @@ -5235,385 +5522,445 @@ def update_folder( ) -def delete_folder( +def delete_version( project_name: str, - folder_id: str, - force: bool = False, + version_id: str, ): - """Delete folder. + """Delete version. Args: project_name (str): Project name. - folder_id (str): Folder id to delete. - force (Optional[bool]): Folder delete folder with all children - folder, products, versions and representations. + version_id (str): Version id to delete. """ con = get_server_api_connection() - return con.delete_folder( + return con.delete_version( project_name=project_name, - folder_id=folder_id, - force=force, + version_id=version_id, ) -def get_rest_task( +def get_rest_representation( project_name: str, - task_id: str, -) -> Optional["TaskDict"]: + representation_id: str, +) -> Optional["RepresentationDict"]: con = get_server_api_connection() - return con.get_rest_task( + return con.get_rest_representation( project_name=project_name, - task_id=task_id, + representation_id=representation_id, ) -def get_tasks( +def get_representations( project_name: str, - task_ids: Optional[Iterable[str]] = None, - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - folder_ids: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, + representation_ids: Optional[Iterable[str]] = None, + representation_names: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, + names_by_version_ids: Optional[dict[str, Iterable[str]]] = None, statuses: Optional[Iterable[str]] = None, tags: Optional[Iterable[str]] = None, active: Optional[bool] = True, + has_links: Optional[str] = None, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Generator["TaskDict", None, None]: - """Query task entities from server. + own_attributes=_PLACEHOLDER, +) -> Generator["RepresentationDict", None, None]: + """Get representation entities based on passed filters from server. + + .. todo:: + + Add separated function for 'names_by_version_ids' filtering. + Because can't be combined with others. Args: - project_name (str): Name of project. - task_ids (Iterable[str]): Task ids to filter. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - folder_ids (Iterable[str]): Ids of task parents. Use 'None' - if folder is direct child of project. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. + project_name (str): Name of project where to look for versions. + representation_ids (Optional[Iterable[str]]): Representation ids + used for representation filtering. + representation_names (Optional[Iterable[str]]): Representation + names used for representation filtering. + version_ids (Optional[Iterable[str]]): Version ids used for + representation filtering. Versions are parents of + representations. + names_by_version_ids (Optional[dict[str, Iterable[str]]]): Find + representations by names and version ids. This filter + discards all other filters. + statuses (Optional[Iterable[str]]): Representation statuses used + for filtering. + tags (Optional[Iterable[str]]): Representation tags used + for filtering. + active (Optional[bool]): Receive active/inactive entities. + Both are returned when 'None' is passed. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. Returns: - Generator[TaskDict, None, None]: Queried task entities. + Generator[RepresentationDict, None, None]: Queried + representation entities. """ con = get_server_api_connection() - return con.get_tasks( + return con.get_representations( project_name=project_name, - task_ids=task_ids, - task_names=task_names, - task_types=task_types, - folder_ids=folder_ids, - assignees=assignees, - assignees_all=assignees_all, + representation_ids=representation_ids, + representation_names=representation_names, + version_ids=version_ids, + names_by_version_ids=names_by_version_ids, statuses=statuses, tags=tags, active=active, + has_links=has_links, fields=fields, own_attributes=own_attributes, ) -def get_task_by_name( +def get_representation_by_id( project_name: str, - folder_id: str, - task_name: str, + representation_id: str, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by name and folder id. + own_attributes=_PLACEHOLDER, +) -> Optional["RepresentationDict"]: + """Query representation entity from server based on id filter. Args: - project_name (str): Name of project where to look for queried - entities. - folder_id (str): Folder id. - task_name (str): Task name - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where to look for representation. + representation_id (str): Id of representation. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + Optional[RepresentationDict]: Queried representation + entity or None. """ con = get_server_api_connection() - return con.get_task_by_name( + return con.get_representation_by_id( project_name=project_name, - folder_id=folder_id, - task_name=task_name, + representation_id=representation_id, fields=fields, own_attributes=own_attributes, ) -def get_task_by_id( +def get_representation_by_name( project_name: str, - task_id: str, + representation_name: str, + version_id: str, fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by id. + own_attributes=_PLACEHOLDER, +) -> Optional["RepresentationDict"]: + """Query representation entity by name and version id. Args: - project_name (str): Name of project where to look for queried - entities. - task_id (str): Task id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where to look for representation. + representation_name (str): Representation name. + version_id (str): Version id. + fields (Optional[Iterable[str]]): fields to be queried + for representations. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + representations. + + Returns: + Optional[RepresentationDict]: Queried representation entity + or None. + + """ + con = get_server_api_connection() + return con.get_representation_by_name( + project_name=project_name, + representation_name=representation_name, + version_id=version_id, + fields=fields, + own_attributes=own_attributes, + ) + + +def get_representations_hierarchy( + project_name: str, + representation_ids: Iterable[str], + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + task_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + representation_fields: Optional[Iterable[str]] = None, +) -> dict[str, RepresentationHierarchy]: + """Find representation with parents by representation id. + + Representation entity with parent entities up to project. + + Default fields are used when any fields are set to `None`. But it is + possible to pass in empty iterable (list, set, tuple) to skip + entity. + + Args: + project_name (str): Project where to look for entities. + representation_ids (Iterable[str]): Representation ids. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + task_fields (Optional[Iterable[str]]): Task fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + representation_fields (Optional[Iterable[str]]): Representation + fields. + + Returns: + dict[str, RepresentationHierarchy]: Parent entities by + representation id. + + """ + con = get_server_api_connection() + return con.get_representations_hierarchy( + project_name=project_name, + representation_ids=representation_ids, + project_fields=project_fields, + folder_fields=folder_fields, + task_fields=task_fields, + product_fields=product_fields, + version_fields=version_fields, + representation_fields=representation_fields, + ) + + +def get_representation_hierarchy( + project_name: str, + representation_id: str, + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + task_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, + representation_fields: Optional[Iterable[str]] = None, +) -> Optional[RepresentationHierarchy]: + """Find representation parents by representation id. + + Representation parent entities up to project. + + Args: + project_name (str): Project where to look for entities. + representation_id (str): Representation id. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + task_fields (Optional[Iterable[str]]): Task fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + representation_fields (Optional[Iterable[str]]): Representation + fields. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + RepresentationHierarchy: Representation hierarchy entities. """ con = get_server_api_connection() - return con.get_task_by_id( + return con.get_representation_hierarchy( project_name=project_name, - task_id=task_id, - fields=fields, - own_attributes=own_attributes, + representation_id=representation_id, + project_fields=project_fields, + folder_fields=folder_fields, + task_fields=task_fields, + product_fields=product_fields, + version_fields=version_fields, + representation_fields=representation_fields, ) -def get_tasks_by_folder_paths( +def get_representations_parents( project_name: str, - folder_paths: Iterable[str], - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: Optional[bool] = True, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> dict[str, list["TaskDict"]]: - """Query task entities from server by folder paths. + representation_ids: Iterable[str], + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, +) -> dict[str, RepresentationParents]: + """Find representations parents by representation id. + + Representation parent entities up to project. Args: - project_name (str): Name of project. - folder_paths (list[str]): Folder paths. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where to look for entities. + representation_ids (Iterable[str]): Representation ids. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. Returns: - dict[str, list[TaskDict]]: Task entities by - folder path. + dict[str, RepresentationParents]: Parent entities by + representation id. """ con = get_server_api_connection() - return con.get_tasks_by_folder_paths( + return con.get_representations_parents( project_name=project_name, - folder_paths=folder_paths, - task_names=task_names, - task_types=task_types, - assignees=assignees, - assignees_all=assignees_all, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, + representation_ids=representation_ids, + project_fields=project_fields, + folder_fields=folder_fields, + product_fields=product_fields, + version_fields=version_fields, ) -def get_tasks_by_folder_path( +def get_representation_parents( project_name: str, - folder_path: str, - task_names: Optional[Iterable[str]] = None, - task_types: Optional[Iterable[str]] = None, - assignees: Optional[Iterable[str]] = None, - assignees_all: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: Optional[bool] = True, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> list["TaskDict"]: - """Query task entities from server by folder path. + representation_id: str, + project_fields: Optional[Iterable[str]] = None, + folder_fields: Optional[Iterable[str]] = None, + product_fields: Optional[Iterable[str]] = None, + version_fields: Optional[Iterable[str]] = None, +) -> Optional["RepresentationParents"]: + """Find representation parents by representation id. + + Representation parent entities up to project. Args: - project_name (str): Name of project. - folder_path (str): Folder path. - task_names (Iterable[str]): Task names used for filtering. - task_types (Iterable[str]): Task types used for filtering. - assignees (Optional[Iterable[str]]): Task assignees used for - filtering. All tasks with any of passed assignees are - returned. - assignees_all (Optional[Iterable[str]]): Task assignees used - for filtering. Task must have all of passed assignees to be - returned. - statuses (Optional[Iterable[str]]): Task statuses used for - filtering. - tags (Optional[Iterable[str]]): Task tags used for - filtering. - active (Optional[bool]): Filter active/inactive tasks. - Both are returned if is set to None. - fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where to look for entities. + representation_id (str): Representation id. + project_fields (Optional[Iterable[str]]): Project fields. + folder_fields (Optional[Iterable[str]]): Folder fields. + product_fields (Optional[Iterable[str]]): Product fields. + version_fields (Optional[Iterable[str]]): Version fields. + + Returns: + RepresentationParents: Representation parent entities. """ con = get_server_api_connection() - return con.get_tasks_by_folder_path( + return con.get_representation_parents( project_name=project_name, - folder_path=folder_path, - task_names=task_names, - task_types=task_types, - assignees=assignees, - assignees_all=assignees_all, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, + representation_id=representation_id, + project_fields=project_fields, + folder_fields=folder_fields, + product_fields=product_fields, + version_fields=version_fields, ) -def get_task_by_folder_path( +def get_repre_ids_by_context_filters( project_name: str, - folder_path: str, - task_name: str, - fields: Optional[Iterable[str]] = None, - own_attributes: bool = False, -) -> Optional["TaskDict"]: - """Query task entity by folder path and task name. + context_filters: Optional[dict[str, Iterable[str]]], + representation_names: Optional[Iterable[str]] = None, + version_ids: Optional[Iterable[str]] = None, +) -> list[str]: + """Find representation ids which match passed context filters. + + Each representation has context integrated on representation entity in + database. The context may contain project, folder, task name or + product name, product type and many more. This implementation gives + option to quickly filter representation based on representation data + in database. + + Context filters have defined structure. To define filter of nested + subfield use dot '.' as delimiter (For example 'task.name'). + Filter values can be regex filters. String or ``re.Pattern`` can + be used. Args: - project_name (str): Project name. - folder_path (str): Folder path. - task_name (str): Task name. - fields (Optional[Iterable[str]]): Task fields that should - be returned. - own_attributes (Optional[bool]): Attribute values that are - not explicitly set on entity will have 'None' value. + project_name (str): Project where to look for representations. + context_filters (dict[str, list[str]]): Filters of context fields. + representation_names (Optional[Iterable[str]]): Representation + names, can be used as additional filter for representations + by their names. + version_ids (Optional[Iterable[str]]): Version ids, can be used + as additional filter for representations by their parent ids. Returns: - Optional[TaskDict]: Task entity data or None if was not found. + list[str]: Representation ids that match passed filters. + + Example: + The function returns just representation ids so if entities are + required for funtionality they must be queried afterwards by + their ids. + >>> from ayon_api import get_repre_ids_by_context_filters + >>> from ayon_api import get_representations + >>> project_name = "testProject" + >>> filters = { + ... "task.name": ["[aA]nimation"], + ... "product": [".*[Mm]ain"] + ... } + >>> repre_ids = get_repre_ids_by_context_filters( + ... project_name, filters) + >>> repres = get_representations(project_name, repre_ids) """ con = get_server_api_connection() - return con.get_task_by_folder_path( + return con.get_repre_ids_by_context_filters( project_name=project_name, - folder_path=folder_path, - task_name=task_name, - fields=fields, - own_attributes=own_attributes, + context_filters=context_filters, + representation_names=representation_names, + version_ids=version_ids, ) -def create_task( +def create_representation( project_name: str, name: str, - task_type: str, - folder_id: str, - label: Optional[str] = None, - assignees: Optional[Iterable[str]] = None, + version_id: str, + files: Optional[list[dict[str, Any]]] = None, attrib: Optional[dict[str, Any]] = None, data: Optional[dict[str, Any]] = None, + traits: Optional[dict[str, Any]] = None, tags: Optional[list[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - task_id: Optional[str] = None, + representation_id: Optional[str] = None, ) -> str: - """Create new task. + """Create new representation. Args: project_name (str): Project name. - name (str): Folder name. - task_type (str): Task type. - folder_id (str): Parent folder id. - label (Optional[str]): Label of folder. - assignees (Optional[Iterable[str]]): Task assignees. - attrib (Optional[dict[str, Any]]): Task attributes. - data (Optional[dict[str, Any]]): Task data. - tags (Optional[Iterable[str]]): Task tags. - status (Optional[str]): Task status. - active (Optional[bool]): Task active state. - thumbnail_id (Optional[str]): Task thumbnail id. - task_id (Optional[str]): Task id. If not passed new id is - generated. + name (str): Representation name. + version_id (str): Parent version id. + files (Optional[list[dict]]): Representation files information. + attrib (Optional[dict[str, Any]]): Representation attributes. + data (Optional[dict[str, Any]]): Representation data. + traits (Optional[dict[str, Any]]): Representation traits + serialized data as dict. + tags (Optional[Iterable[str]]): Representation tags. + status (Optional[str]): Representation status. + active (Optional[bool]): Representation active state. + representation_id (Optional[str]): Representation id. If not + passed new id is generated. Returns: - str: Task id. + str: Representation id. """ con = get_server_api_connection() - return con.create_task( + return con.create_representation( project_name=project_name, name=name, - task_type=task_type, - folder_id=folder_id, - label=label, - assignees=assignees, + version_id=version_id, + files=files, attrib=attrib, data=data, + traits=traits, tags=tags, status=status, active=active, - thumbnail_id=thumbnail_id, - task_id=task_id, + representation_id=representation_id, ) -def update_task( +def update_representation( project_name: str, - task_id: str, + representation_id: str, name: Optional[str] = None, - task_type: Optional[str] = None, - folder_id: Optional[str] = None, - label: Optional[str] = NOT_SET, - assignees: Optional[list[str]] = None, + version_id: Optional[str] = None, + files: Optional[list[dict[str, Any]]] = None, attrib: Optional[dict[str, Any]] = None, data: Optional[dict[str, Any]] = None, + traits: Optional[dict[str, Any]] = None, tags: Optional[list[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, ): - """Update task entity on server. - - Do not pass ``label`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. + """Update representation entity on server. Update of ``data`` will override existing value on folder entity. @@ -5622,1700 +5969,1352 @@ def update_task( Args: project_name (str): Project name. - task_id (str): Task id. + representation_id (str): Representation id. name (Optional[str]): New name. - task_type (Optional[str]): New task type. - folder_id (Optional[str]): New folder id. - label (Optional[Optional[str]]): New label. - assignees (Optional[str]): New assignees. + version_id (Optional[str]): New version id. + files (Optional[list[dict]]): New files + information. attrib (Optional[dict[str, Any]]): New attributes. data (Optional[dict[str, Any]]): New data. + traits (Optional[dict[str, Any]]): New traits. tags (Optional[Iterable[str]]): New tags. status (Optional[str]): New status. active (Optional[bool]): New active state. - thumbnail_id (Optional[str]): New thumbnail id. """ con = get_server_api_connection() - return con.update_task( + return con.update_representation( project_name=project_name, - task_id=task_id, + representation_id=representation_id, name=name, - task_type=task_type, - folder_id=folder_id, - label=label, - assignees=assignees, + version_id=version_id, + files=files, attrib=attrib, data=data, + traits=traits, tags=tags, status=status, active=active, - thumbnail_id=thumbnail_id, ) -def delete_task( +def delete_representation( project_name: str, - task_id: str, + representation_id: str, ): - """Delete task. + """Delete representation. Args: project_name (str): Project name. - task_id (str): Task id to delete. + representation_id (str): Representation id to delete. """ con = get_server_api_connection() - return con.delete_task( - project_name=project_name, - task_id=task_id, - ) - - -def get_rest_product( - project_name: str, - product_id: str, -) -> Optional["ProductDict"]: - con = get_server_api_connection() - return con.get_rest_product( + return con.delete_representation( project_name=project_name, - product_id=product_id, + representation_id=representation_id, ) -def get_products( +def get_workfiles_info( project_name: str, - product_ids: Optional[Iterable[str]] = None, - product_names: Optional[Iterable[str]] = None, - folder_ids: Optional[Iterable[str]] = None, - product_types: Optional[Iterable[str]] = None, - product_name_regex: Optional[str] = None, - product_path_regex: Optional[str] = None, - names_by_folder_ids: Optional[dict[str, Iterable[str]]] = None, + workfile_ids: Optional[Iterable[str]] = None, + task_ids: Optional[Iterable[str]] = None, + paths: Optional[Iterable[str]] = None, + path_regex: Optional[str] = None, statuses: Optional[Iterable[str]] = None, tags: Optional[Iterable[str]] = None, - active: Optional[bool] = True, + has_links: Optional[str] = None, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["ProductDict", None, None]: - """Query products from server. - - Todos: - Separate 'name_by_folder_ids' filtering to separated method. It - cannot be combined with some other filters. +) -> Generator["WorkfileInfoDict", None, None]: + """Workfile info entities by passed filters. Args: - project_name (str): Name of project. - product_ids (Optional[Iterable[str]]): Task ids to filter. - product_names (Optional[Iterable[str]]): Task names used for - filtering. - folder_ids (Optional[Iterable[str]]): Ids of task parents. - Use 'None' if folder is direct child of project. - product_types (Optional[Iterable[str]]): Product types used for - filtering. - product_name_regex (Optional[str]): Filter products by name regex. - product_path_regex (Optional[str]): Filter products by path regex. - Path starts with folder path and ends with product name. - names_by_folder_ids (Optional[dict[str, Iterable[str]]]): Product - name filtering by folder id. - statuses (Optional[Iterable[str]]): Product statuses used + project_name (str): Project under which the entity is located. + workfile_ids (Optional[Iterable[str]]): Workfile ids. + task_ids (Optional[Iterable[str]]): Task ids. + paths (Optional[Iterable[str]]): Rootless workfiles paths. + path_regex (Optional[str]): Regex filter for workfile path. + statuses (Optional[Iterable[str]]): Workfile info statuses used for filtering. - tags (Optional[Iterable[str]]): Product tags used + tags (Optional[Iterable[str]]): Workfile info tags used for filtering. - active (Optional[bool]): Filter active/inactive products. - Both are returned if is set to None. + has_links (Optional[Literal[IN, OUT, ANY]]): Filter + representations with IN/OUT/ANY links. fields (Optional[Iterable[str]]): Fields to be queried for - folder. All possible folder fields are returned - if 'None' is passed. + representation. All possible fields are returned if 'None' is + passed. own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. + workfiles. Returns: - Generator[ProductDict, None, None]: Queried product entities. + Generator[WorkfileInfoDict, None, None]: Queried workfile info + entites. """ con = get_server_api_connection() - return con.get_products( + return con.get_workfiles_info( project_name=project_name, - product_ids=product_ids, - product_names=product_names, - folder_ids=folder_ids, - product_types=product_types, - product_name_regex=product_name_regex, - product_path_regex=product_path_regex, - names_by_folder_ids=names_by_folder_ids, + workfile_ids=workfile_ids, + task_ids=task_ids, + paths=paths, + path_regex=path_regex, statuses=statuses, tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, - ) - - -def get_product_by_id( - project_name: str, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["ProductDict"]: - """Query product entity by id. - - Args: - project_name (str): Name of project where to look for queried - entities. - product_id (str): Product id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. - - Returns: - Optional[ProductDict]: Product entity data or None - if was not found. - - """ - con = get_server_api_connection() - return con.get_product_by_id( - project_name=project_name, - product_id=product_id, + has_links=has_links, fields=fields, own_attributes=own_attributes, ) -def get_product_by_name( +def get_workfile_info( project_name: str, - product_name: str, - folder_id: str, + task_id: str, + path: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["ProductDict"]: - """Query product entity by name and folder id. - - Args: - project_name (str): Name of project where to look for queried - entities. - product_name (str): Product name. - folder_id (str): Folder id (Folder is a parent of products). - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - products. - - Returns: - Optional[ProductDict]: Product entity data or None - if was not found. - - """ - con = get_server_api_connection() - return con.get_product_by_name( - project_name=project_name, - product_name=product_name, - folder_id=folder_id, - fields=fields, - own_attributes=own_attributes, - ) - - -def get_product_types( - fields: Optional[Iterable[str]] = None, -) -> list["ProductTypeDict"]: - """Types of products. - - This is server wide information. Product types have 'name', 'icon' and - 'color'. - - Args: - fields (Optional[Iterable[str]]): Product types fields to query. - - Returns: - list[ProductTypeDict]: Product types information. - - """ - con = get_server_api_connection() - return con.get_product_types( - fields=fields, - ) - - -def get_project_product_types( - project_name: str, - fields: Optional[Iterable[str]] = None, -) -> list["ProductTypeDict"]: - """DEPRECATED Types of products available in a project. - - Filter only product types available in a project. +) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by task id and workfile path. - Args: - project_name (str): Name of the project where to look for - product types. - fields (Optional[Iterable[str]]): Product types fields to query. + Args: + project_name (str): Project under which the entity is located. + task_id (str): Task id. + path (str): Rootless workfile path. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. Returns: - list[ProductTypeDict]: Product types information. + Optional[WorkfileInfoDict]: Workfile info entity or None. """ con = get_server_api_connection() - return con.get_project_product_types( + return con.get_workfile_info( project_name=project_name, + task_id=task_id, + path=path, fields=fields, + own_attributes=own_attributes, ) -def get_product_type_names( - project_name: Optional[str] = None, - product_ids: Optional[Iterable[str]] = None, -) -> set[str]: - """DEPRECATED Product type names. - - Warnings: - This function will be probably removed. Matters if 'products_id' - filter has real use-case. +def get_workfile_info_by_id( + project_name: str, + workfile_id: str, + fields: Optional[Iterable[str]] = None, + own_attributes=_PLACEHOLDER, +) -> Optional["WorkfileInfoDict"]: + """Workfile info entity by id. Args: - project_name (Optional[str]): Name of project where to look for - queried entities. - product_ids (Optional[Iterable[str]]): Product ids filter. Can be - used only with 'project_name'. + project_name (str): Project under which the entity is located. + workfile_id (str): Workfile info id. + fields (Optional[Iterable[str]]): Fields to be queried for + representation. All possible fields are returned if 'None' is + passed. + own_attributes (Optional[bool]): DEPRECATED: Not supported for + workfiles. Returns: - set[str]: Product type names. + Optional[WorkfileInfoDict]: Workfile info entity or None. """ con = get_server_api_connection() - return con.get_product_type_names( + return con.get_workfile_info_by_id( project_name=project_name, - product_ids=product_ids, + workfile_id=workfile_id, + fields=fields, + own_attributes=own_attributes, ) -def create_product( - project_name: str, - name: str, - product_type: str, - folder_id: str, - attrib: Optional[dict[str, Any]] = None, - data: Optional[dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - product_id: Optional[str] = None, +def get_full_link_type_name( + link_type_name: str, + input_type: str, + output_type: str, ) -> str: - """Create new product. + """Calculate full link type name used for query from server. Args: - project_name (str): Project name. - name (str): Product name. - product_type (str): Product type. - folder_id (str): Parent folder id. - attrib (Optional[dict[str, Any]]): Product attributes. - data (Optional[dict[str, Any]]): Product data. - tags (Optional[Iterable[str]]): Product tags. - status (Optional[str]): Product status. - active (Optional[bool]): Product active state. - product_id (Optional[str]): Product id. If not passed new id is - generated. + link_type_name (str): Type of link. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. Returns: - str: Product id. + str: Full name of link type used for query from server. """ con = get_server_api_connection() - return con.create_product( - project_name=project_name, - name=name, - product_type=product_type, - folder_id=folder_id, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - product_id=product_id, + return con.get_full_link_type_name( + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) -def update_product( +def get_link_types( project_name: str, - product_id: str, - name: Optional[str] = None, - folder_id: Optional[str] = None, - product_type: Optional[str] = None, - attrib: Optional[dict[str, Any]] = None, - data: Optional[dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, -): - """Update product entity on server. - - Update of ``data`` will override existing value on folder entity. +) -> list[dict[str, Any]]: + """All link types available on a project. - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. + Example output: + [ + { + "name": "reference|folder|folder", + "link_type": "reference", + "input_type": "folder", + "output_type": "folder", + "data": {} + } + ] Args: - project_name (str): Project name. - product_id (str): Product id. - name (Optional[str]): New product name. - folder_id (Optional[str]): New product id. - product_type (Optional[str]): New product type. - attrib (Optional[dict[str, Any]]): New product attributes. - data (Optional[dict[str, Any]]): New product data. - tags (Optional[Iterable[str]]): New product tags. - status (Optional[str]): New product status. - active (Optional[bool]): New product active state. - - """ - con = get_server_api_connection() - return con.update_product( - project_name=project_name, - product_id=product_id, - name=name, - folder_id=folder_id, - product_type=product_type, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - ) - - -def delete_product( - project_name: str, - product_id: str, -): - """Delete product. + project_name (str): Name of project where to look for link types. - Args: - project_name (str): Project name. - product_id (str): Product id to delete. + Returns: + list[dict[str, Any]]: Link types available on project. """ con = get_server_api_connection() - return con.delete_product( + return con.get_link_types( project_name=project_name, - product_id=product_id, ) -def get_rest_version( +def get_link_type( project_name: str, - version_id: str, -) -> Optional["VersionDict"]: - con = get_server_api_connection() - return con.get_rest_version( - project_name=project_name, - version_id=version_id, - ) + link_type_name: str, + input_type: str, + output_type: str, +) -> Optional[dict[str, Any]]: + """Get link type data. + There is not dedicated REST endpoint to get single link type, + so method 'get_link_types' is used. -def get_versions( - project_name: str, - version_ids: Optional[Iterable[str]] = None, - product_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] = None, - versions: Optional[Iterable[str]] = None, - hero: bool = True, - standard: bool = True, - latest: Optional[bool] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: Optional[bool] = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["VersionDict", None, None]: - """Get version entities based on passed filters from server. + Example output: + { + "name": "reference|folder|folder", + "link_type": "reference", + "input_type": "folder", + "output_type": "folder", + "data": {} + } Args: - project_name (str): Name of project where to look for versions. - version_ids (Optional[Iterable[str]]): Version ids used for - version filtering. - product_ids (Optional[Iterable[str]]): Product ids used for - version filtering. - task_ids (Optional[Iterable[str]]): Task ids used for - version filtering. - versions (Optional[Iterable[int]]): Versions we're interested in. - hero (Optional[bool]): Skip hero versions when set to False. - standard (Optional[bool]): Skip standard (non-hero) when - set to False. - latest (Optional[bool]): Return only latest version of standard - versions. This can be combined only with 'standard' attribute - set to True. - statuses (Optional[Iterable[str]]): Representation statuses used - for filtering. - tags (Optional[Iterable[str]]): Representation tags used - for filtering. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): Fields to be queried - for version. All possible folder fields are returned - if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Generator[VersionDict, None, None]: Queried version entities. - - """ - con = get_server_api_connection() - return con.get_versions( - project_name=project_name, - version_ids=version_ids, - product_ids=product_ids, - task_ids=task_ids, - versions=versions, - hero=hero, - standard=standard, - latest=latest, - statuses=statuses, - tags=tags, - active=active, - fields=fields, - own_attributes=own_attributes, + project_name (str): Project where link type is available. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + + Returns: + Optional[dict[str, Any]]: Link type information. + + """ + con = get_server_api_connection() + return con.get_link_type( + project_name=project_name, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) -def get_version_by_id( +def create_link_type( project_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query version entity by id. + link_type_name: str, + input_type: str, + output_type: str, + data: Optional[dict[str, Any]] = None, +): + """Create or update link type on server. + + Warning: + Because PUT is used for creation it is also used for update. Args: - project_name (str): Name of project where to look for queried - entities. - version_id (str): Version id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where link type is created. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + data (Optional[dict[str, Any]]): Additional data related to link. - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_version_by_id( + return con.create_link_type( project_name=project_name, - version_id=version_id, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, + data=data, ) -def get_version_by_name( +def delete_link_type( project_name: str, - version: int, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query version entity by version and product id. + link_type_name: str, + input_type: str, + output_type: str, +): + """Remove link type from project. Args: - project_name (str): Name of project where to look for queried - entities. - version (int): Version of version entity. - product_id (str): Product id. Product is a parent of version. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where link type is created. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_version_by_name( + return con.delete_link_type( project_name=project_name, - version=version, - product_id=product_id, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, ) -def get_hero_version_by_id( +def make_sure_link_type_exists( project_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query hero version entity by id. + link_type_name: str, + input_type: str, + output_type: str, + data: Optional[dict[str, Any]] = None, +): + """Make sure link type exists on a project. Args: - project_name (str): Name of project where to look for queried - entities. - version_id (int): Hero version id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. - - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + project_name (str): Name of project. + link_type_name (str): Name of link type. + input_type (str): Input entity type of link. + output_type (str): Output entity type of link. + data (Optional[dict[str, Any]]): Link type related data. """ con = get_server_api_connection() - return con.get_hero_version_by_id( + return con.make_sure_link_type_exists( project_name=project_name, - version_id=version_id, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_type=input_type, + output_type=output_type, + data=data, ) -def get_hero_version_by_product_id( +def create_link( project_name: str, - product_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query hero version entity by product id. + link_type_name: str, + input_id: str, + input_type: str, + output_id: str, + output_type: str, + link_name: Optional[str] = None, +): + """Create link between 2 entities. - Only one hero version is available on a product. + Link has a type which must already exists on a project. + + Example output:: + + { + "id": "59a212c0d2e211eda0e20242ac120002" + } Args: - project_name (str): Name of project where to look for queried - entities. - product_id (int): Product id. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where the link is created. + link_type_name (str): Type of link. + input_id (str): Input entity id. + input_type (str): Entity type of input entity. + output_id (str): Output entity id. + output_type (str): Entity type of output entity. + link_name (Optional[str]): Name of link. + Available from server version '1.0.0-rc.6'. Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + dict[str, str]: Information about link. + + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_hero_version_by_product_id( + return con.create_link( project_name=project_name, - product_id=product_id, - fields=fields, - own_attributes=own_attributes, + link_type_name=link_type_name, + input_id=input_id, + input_type=input_type, + output_id=output_id, + output_type=output_type, + link_name=link_name, ) -def get_hero_versions( +def delete_link( project_name: str, - product_ids: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - active: Optional[bool] = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["VersionDict", None, None]: - """Query hero versions by multiple filters. - - Only one hero version is available on a product. + link_id: str, +): + """Remove link by id. Args: - project_name (str): Name of project where to look for queried - entities. - product_ids (Optional[Iterable[str]]): Product ids. - version_ids (Optional[Iterable[str]]): Version ids. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): Fields that should be returned. - All fields are returned if 'None' is passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where link exists. + link_id (str): Id of link. - Returns: - Optional[VersionDict]: Version entity data or None - if was not found. + Raises: + HTTPRequestError: Server error happened. """ con = get_server_api_connection() - return con.get_hero_versions( + return con.delete_link( project_name=project_name, - product_ids=product_ids, - version_ids=version_ids, - active=active, - fields=fields, - own_attributes=own_attributes, + link_id=link_id, ) -def get_last_versions( +def get_entities_links( project_name: str, - product_ids: Iterable[str], - active: Optional[bool] = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> dict[str, Optional["VersionDict"]]: - """Query last version entities by product ids. + entity_type: str, + entity_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, + link_names: Optional[Iterable[str]] = None, + link_name_regex: Optional[str] = None, +) -> dict[str, list[dict[str, Any]]]: + """Helper method to get links from server for entity types. + + .. highlight:: text + .. code-block:: text + + Example output: + { + "59a212c0d2e211eda0e20242ac120001": [ + { + "id": "59a212c0d2e211eda0e20242ac120002", + "linkType": "reference", + "description": "reference link between folders", + "projectName": "my_project", + "author": "frantadmin", + "entityId": "b1df109676db11ed8e8c6c9466b19aa8", + "entityType": "folder", + "direction": "out" + }, + ... + ], + ... + } Args: - project_name (str): Project where to look for representation. - product_ids (Iterable[str]): Product ids. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where links are. + entity_type (Literal["folder", "task", "product", + "version", "representations"]): Entity type. + entity_ids (Optional[Iterable[str]]): Ids of entities for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + link_names (Optional[Iterable[str]]): Link name filters. + link_name_regex (Optional[str]): Regex filter for link name. Returns: - dict[str, Optional[VersionDict]]: Last versions by product id. + dict[str, list[dict[str, Any]]]: Link info by entity ids. """ con = get_server_api_connection() - return con.get_last_versions( + return con.get_entities_links( project_name=project_name, - product_ids=product_ids, - active=active, - fields=fields, - own_attributes=own_attributes, + entity_type=entity_type, + entity_ids=entity_ids, + link_types=link_types, + link_direction=link_direction, + link_names=link_names, + link_name_regex=link_name_regex, ) -def get_last_version_by_product_id( +def get_folders_links( project_name: str, - product_id: str, - active: Optional[bool] = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query last version entity by product id. + folder_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query folders links from server. Args: - project_name (str): Project where to look for representation. - product_id (str): Product id. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - versions. + project_name (str): Project where links are. + folder_ids (Optional[Iterable[str]]): Ids of folders for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - Optional[VersionDict]: Queried version entity or None. + dict[str, list[dict[str, Any]]]: Link info by folder ids. """ con = get_server_api_connection() - return con.get_last_version_by_product_id( + return con.get_folders_links( project_name=project_name, - product_id=product_id, - active=active, - fields=fields, - own_attributes=own_attributes, + folder_ids=folder_ids, + link_types=link_types, + link_direction=link_direction, ) -def get_last_version_by_product_name( +def get_folder_links( project_name: str, - product_name: str, folder_id: str, - active: Optional[bool] = True, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: - """Query last version entity by product name and folder id. + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query folder links from server. Args: - project_name (str): Project where to look for representation. - product_name (str): Product name. - folder_id (str): Folder id. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + project_name (str): Project where links are. + folder_id (str): Folder id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - Optional[VersionDict]: Queried version entity or None. + list[dict[str, Any]]: Link info of folder. """ con = get_server_api_connection() - return con.get_last_version_by_product_name( + return con.get_folder_links( project_name=project_name, - product_name=product_name, folder_id=folder_id, - active=active, - fields=fields, - own_attributes=own_attributes, + link_types=link_types, + link_direction=link_direction, ) -def version_is_latest( +def get_tasks_links( project_name: str, - version_id: str, -) -> bool: - """Is version latest from a product. + task_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query tasks links from server. Args: - project_name (str): Project where to look for representation. - version_id (str): Version id. + project_name (str): Project where links are. + task_ids (Optional[Iterable[str]]): Ids of tasks for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - bool: Version is latest or not. + dict[str, list[dict[str, Any]]]: Link info by task ids. """ con = get_server_api_connection() - return con.version_is_latest( + return con.get_tasks_links( project_name=project_name, - version_id=version_id, + task_ids=task_ids, + link_types=link_types, + link_direction=link_direction, ) -def create_version( +def get_task_links( project_name: str, - version: int, - product_id: str, - task_id: Optional[str] = None, - author: Optional[str] = None, - attrib: Optional[dict[str, Any]] = None, - data: Optional[dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = None, - version_id: Optional[str] = None, -) -> str: - """Create new version. + task_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query task links from server. Args: - project_name (str): Project name. - version (int): Version. - product_id (str): Parent product id. - task_id (Optional[str]): Parent task id. - author (Optional[str]): Version author. - attrib (Optional[dict[str, Any]]): Version attributes. - data (Optional[dict[str, Any]]): Version data. - tags (Optional[Iterable[str]]): Version tags. - status (Optional[str]): Version status. - active (Optional[bool]): Version active state. - thumbnail_id (Optional[str]): Version thumbnail id. - version_id (Optional[str]): Version id. If not passed new id is - generated. + project_name (str): Project where links are. + task_id (str): Task id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - str: Version id. + list[dict[str, Any]]: Link info of task. """ con = get_server_api_connection() - return con.create_version( + return con.get_task_links( project_name=project_name, - version=version, - product_id=product_id, task_id=task_id, - author=author, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - thumbnail_id=thumbnail_id, - version_id=version_id, + link_types=link_types, + link_direction=link_direction, ) -def update_version( +def get_products_links( project_name: str, - version_id: str, - version: Optional[int] = None, - product_id: Optional[str] = None, - task_id: Optional[str] = NOT_SET, - author: Optional[str] = None, - attrib: Optional[dict[str, Any]] = None, - data: Optional[dict[str, Any]] = None, - tags: Optional[Iterable[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - thumbnail_id: Optional[str] = NOT_SET, -): - """Update version entity on server. - - Do not pass ``task_id`` amd ``thumbnail_id`` if you don't - want to change their values. Value ``None`` would unset - their value. - - Update of ``data`` will override existing value on folder entity. - - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. + product_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query products links from server. Args: - project_name (str): Project name. - version_id (str): Version id. - version (Optional[int]): New version. - product_id (Optional[str]): New product id. - task_id (Optional[str]): New task id. - author (Optional[str]): New author username. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. - thumbnail_id (Optional[str]): New thumbnail id. + project_name (str): Project where links are. + product_ids (Optional[Iterable[str]]): Ids of products for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + dict[str, list[dict[str, Any]]]: Link info by product ids. """ con = get_server_api_connection() - return con.update_version( - project_name=project_name, - version_id=version_id, - version=version, - product_id=product_id, - task_id=task_id, - author=author, - attrib=attrib, - data=data, - tags=tags, - status=status, - active=active, - thumbnail_id=thumbnail_id, + return con.get_products_links( + project_name=project_name, + product_ids=product_ids, + link_types=link_types, + link_direction=link_direction, ) -def delete_version( +def get_product_links( project_name: str, - version_id: str, -): - """Delete version. + product_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query product links from server. Args: - project_name (str): Project name. - version_id (str): Version id to delete. + project_name (str): Project where links are. + product_id (str): Product id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. + + Returns: + list[dict[str, Any]]: Link info of product. """ con = get_server_api_connection() - return con.delete_version( + return con.get_product_links( project_name=project_name, - version_id=version_id, + product_id=product_id, + link_types=link_types, + link_direction=link_direction, ) -def get_thumbnail_by_id( +def get_versions_links( project_name: str, - thumbnail_id: str, -) -> ThumbnailContent: - """Get thumbnail from server by id. - - Warnings: - Please keep in mind that used endpoint is allowed only for admins - and managers. Use 'get_thumbnail' with entity type and id - to allow access for artists. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. + version_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query versions links from server. Args: - project_name (str): Project under which the entity is located. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. + project_name (str): Project where links are. + version_ids (Optional[Iterable[str]]): Ids of versions for which + links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + dict[str, list[dict[str, Any]]]: Link info by version ids. """ con = get_server_api_connection() - return con.get_thumbnail_by_id( + return con.get_versions_links( project_name=project_name, - thumbnail_id=thumbnail_id, + version_ids=version_ids, + link_types=link_types, + link_direction=link_direction, ) -def get_thumbnail( +def get_version_links( project_name: str, - entity_type: str, - entity_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Get thumbnail from server. - - Permissions of thumbnails are related to entities so thumbnails must - be queried per entity. So an entity type and entity id is required - to be passed. - - Notes: - It is recommended to use one of prepared entity type specific - methods 'get_folder_thumbnail', 'get_version_thumbnail' or - 'get_workfile_thumbnail'. - We do recommend pass thumbnail id if you have access to it. Each - entity that allows thumbnails has 'thumbnailId' field, so it - can be queried. + version_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query version links from server. Args: - project_name (str): Project under which the entity is located. - entity_type (str): Entity type which passed entity id represents. - entity_id (str): Entity id for which thumbnail should be returned. - thumbnail_id (Optional[str]): DEPRECATED Use - 'get_thumbnail_by_id'. + project_name (str): Project where links are. + version_id (str): Version id for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + list[dict[str, Any]]: Link info of version. """ con = get_server_api_connection() - return con.get_thumbnail( + return con.get_version_links( project_name=project_name, - entity_type=entity_type, - entity_id=entity_id, - thumbnail_id=thumbnail_id, + version_id=version_id, + link_types=link_types, + link_direction=link_direction, ) -def get_folder_thumbnail( +def get_representations_links( project_name: str, - folder_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for folder entity. + representation_ids: Optional[Iterable[str]] = None, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> dict[str, list[dict[str, Any]]]: + """Query representations links from server. Args: - project_name (str): Project under which the entity is located. - folder_id (str): Folder id for which thumbnail should be returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. + project_name (str): Project where links are. + representation_ids (Optional[Iterable[str]]): Ids of + representations for which links should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + dict[str, list[dict[str, Any]]]: Link info by representation ids. """ con = get_server_api_connection() - return con.get_folder_thumbnail( + return con.get_representations_links( project_name=project_name, - folder_id=folder_id, - thumbnail_id=thumbnail_id, + representation_ids=representation_ids, + link_types=link_types, + link_direction=link_direction, ) -def get_task_thumbnail( +def get_representation_links( project_name: str, - task_id: str, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for task entity. + representation_id: str, + link_types: Optional[Iterable[str]] = None, + link_direction: Optional["LinkDirection"] = None, +) -> list[dict[str, Any]]: + """Query representation links from server. Args: - project_name (str): Project under which the entity is located. - task_id (str): Folder id for which thumbnail should be returned. + project_name (str): Project where links are. + representation_id (str): Representation id for which links + should be received. + link_types (Optional[Iterable[str]]): Link type filters. + link_direction (Optional[Literal["in", "out"]]): Link direction + filter. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + list[dict[str, Any]]: Link info of representation. """ con = get_server_api_connection() - return con.get_task_thumbnail( + return con.get_representation_links( project_name=project_name, - task_id=task_id, + representation_id=representation_id, + link_types=link_types, + link_direction=link_direction, ) -def get_version_thumbnail( +def get_entity_lists( project_name: str, - version_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for version entity. + *, + list_ids: Optional[Iterable[str]] = None, + active: Optional[bool] = None, + fields: Optional[Iterable[str]] = None, +) -> Generator[dict[str, Any], None, None]: + """Fetch entity lists from server. Args: - project_name (str): Project under which the entity is located. - version_id (str): Version id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. + project_name (str): Project name where entity lists are. + list_ids (Optional[Iterable[str]]): List of entity list ids to + fetch. + active (Optional[bool]): Filter by active state of entity lists. + fields (Optional[Iterable[str]]): Fields to fetch from server. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + Generator[dict[str, Any], None, None]: Entity list entities + matching defined filters. """ con = get_server_api_connection() - return con.get_version_thumbnail( + return con.get_entity_lists( project_name=project_name, - version_id=version_id, - thumbnail_id=thumbnail_id, + list_ids=list_ids, + active=active, + fields=fields, ) -def get_workfile_thumbnail( +def get_entity_list_rest( project_name: str, - workfile_id: str, - thumbnail_id: Optional[str] = None, -) -> ThumbnailContent: - """Prepared method to receive thumbnail for workfile entity. + list_id: str, +) -> Optional[dict[str, Any]]: + """Get entity list by id using REST API. Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Worfile id for which thumbnail should be - returned. - thumbnail_id (Optional[str]): Prepared thumbnail id from entity. - Used only to check if thumbnail was already cached. + project_name (str): Project name. + list_id (str): Entity list id. Returns: - ThumbnailContent: Thumbnail content wrapper. Does not have to be - valid. + Optional[dict[str, Any]]: Entity list data or None if not found. """ con = get_server_api_connection() - return con.get_workfile_thumbnail( + return con.get_entity_list_rest( project_name=project_name, - workfile_id=workfile_id, - thumbnail_id=thumbnail_id, + list_id=list_id, ) -def create_thumbnail( +def get_entity_list_by_id( project_name: str, - src_filepath: str, - thumbnail_id: Optional[str] = None, -) -> str: - """Create new thumbnail on server from passed path. + list_id: str, + fields: Optional[Iterable[str]] = None, +) -> Optional[dict[str, Any]]: + """Get entity list by id using GraphQl. Args: - project_name (str): Project where the thumbnail will be created - and can be used. - src_filepath (str): Filepath to thumbnail which should be uploaded. - thumbnail_id (Optional[str]): Prepared if of thumbnail. + project_name (str): Project name. + list_id (str): Entity list id. + fields (Optional[Iterable[str]]): Fields to fetch from server. Returns: - str: Created thumbnail id. - - Raises: - ValueError: When thumbnail source cannot be processed. + Optional[dict[str, Any]]: Entity list data or None if not found. """ con = get_server_api_connection() - return con.create_thumbnail( + return con.get_entity_list_by_id( project_name=project_name, - src_filepath=src_filepath, - thumbnail_id=thumbnail_id, + list_id=list_id, + fields=fields, ) -def update_thumbnail( +def create_entity_list( project_name: str, - thumbnail_id: str, - src_filepath: str, -): - """Change thumbnail content by id. - - Update can be also used to create new thumbnail. + entity_type: "EntityListEntityType", + label: str, + *, + list_type: Optional[str] = None, + access: Optional[dict[str, Any]] = None, + attrib: Optional[list[dict[str, Any]]] = None, + data: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[str]] = None, + template: Optional[dict[str, Any]] = None, + owner: Optional[str] = None, + active: Optional[bool] = None, + items: Optional[list[dict[str, Any]]] = None, + list_id: Optional[str] = None, +) -> str: + """Create entity list. Args: - project_name (str): Project where the thumbnail will be created - and can be used. - thumbnail_id (str): Thumbnail id to update. - src_filepath (str): Filepath to thumbnail which should be uploaded. - - Raises: - ValueError: When thumbnail source cannot be processed. + project_name (str): Project name where entity list lives. + entity_type (EntityListEntityType): Which entity types can be + used in list. + label (str): Entity list label. + list_type (Optional[str]): Entity list type. + access (Optional[dict[str, Any]]): Access control for entity list. + attrib (Optional[dict[str, Any]]): Attribute values of + entity list. + data (Optional[dict[str, Any]]): Custom data of entity list. + tags (Optional[list[str]]): Entity list tags. + template (Optional[dict[str, Any]]): Dynamic list template. + owner (Optional[str]): New owner of the list. + active (Optional[bool]): Change active state of entity list. + items (Optional[list[dict[str, Any]]]): Initial items in + entity list. + list_id (Optional[str]): Entity list id. """ con = get_server_api_connection() - return con.update_thumbnail( + return con.create_entity_list( project_name=project_name, - thumbnail_id=thumbnail_id, - src_filepath=src_filepath, + entity_type=entity_type, + label=label, + list_type=list_type, + access=access, + attrib=attrib, + data=data, + tags=tags, + template=template, + owner=owner, + active=active, + items=items, + list_id=list_id, ) -def get_workfiles_info( +def update_entity_list( project_name: str, - workfile_ids: Optional[Iterable[str]] = None, - task_ids: Optional[Iterable[str]] = None, - paths: Optional[Iterable[str]] = None, - path_regex: Optional[str] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - has_links: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> "Generator[WorkfileInfoDict, None, None]": - """Workfile info entities by passed filters. + list_id: str, + *, + label: Optional[str] = None, + access: Optional[dict[str, Any]] = None, + attrib: Optional[list[dict[str, Any]]] = None, + data: Optional[list[dict[str, Any]]] = None, + tags: Optional[list[str]] = None, + owner: Optional[str] = None, + active: Optional[bool] = None, +) -> None: + """Update entity list. Args: - project_name (str): Project under which the entity is located. - workfile_ids (Optional[Iterable[str]]): Workfile ids. - task_ids (Optional[Iterable[str]]): Task ids. - paths (Optional[Iterable[str]]): Rootless workfiles paths. - path_regex (Optional[str]): Regex filter for workfile path. - statuses (Optional[Iterable[str]]): Workfile info statuses used - for filtering. - tags (Optional[Iterable[str]]): Workfile info tags used - for filtering. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Generator[WorkfileInfoDict, None, None]: Queried workfile info - entites. + project_name (str): Project name where entity list lives. + list_id (str): Entity list id that will be updated. + label (Optional[str]): New label of entity list. + access (Optional[dict[str, Any]]): Access control for entity list. + attrib (Optional[dict[str, Any]]): Attribute values of + entity list. + data (Optional[dict[str, Any]]): Custom data of entity list. + tags (Optional[list[str]]): Entity list tags. + owner (Optional[str]): New owner of the list. + active (Optional[bool]): Change active state of entity list. """ con = get_server_api_connection() - return con.get_workfiles_info( + return con.update_entity_list( project_name=project_name, - workfile_ids=workfile_ids, - task_ids=task_ids, - paths=paths, - path_regex=path_regex, - statuses=statuses, + list_id=list_id, + label=label, + access=access, + attrib=attrib, + data=data, tags=tags, - has_links=has_links, - fields=fields, - own_attributes=own_attributes, + owner=owner, + active=active, ) -def get_workfile_info( +def delete_entity_list( project_name: str, - task_id: str, - path: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by task id and workfile path. + list_id: str, +) -> None: + """Delete entity list from project. Args: - project_name (str): Project under which the entity is located. - task_id (str): Task id. - path (str): Rootless workfile path. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. - - Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. + project_name (str): Project name. + list_id (str): Entity list id that will be removed. """ con = get_server_api_connection() - return con.get_workfile_info( + return con.delete_entity_list( project_name=project_name, - task_id=task_id, - path=path, - fields=fields, - own_attributes=own_attributes, + list_id=list_id, ) -def get_workfile_info_by_id( +def get_entity_list_attribute_definitions( project_name: str, - workfile_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: - """Workfile info entity by id. + list_id: str, +) -> list["EntityListAttributeDefinitionDict"]: + """Get attribute definitioins on entity list. Args: - project_name (str): Project under which the entity is located. - workfile_id (str): Workfile info id. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - workfiles. + project_name (str): Project name. + list_id (str): Entity list id. Returns: - Optional[WorkfileInfoDict]: Workfile info entity or None. + list[EntityListAttributeDefinitionDict]: List of attribute + definitions. """ con = get_server_api_connection() - return con.get_workfile_info_by_id( + return con.get_entity_list_attribute_definitions( project_name=project_name, - workfile_id=workfile_id, - fields=fields, - own_attributes=own_attributes, + list_id=list_id, ) -def get_rest_representation( +def set_entity_list_attribute_definitions( project_name: str, - representation_id: str, -) -> Optional["RepresentationDict"]: + list_id: str, + attribute_definitions: list["EntityListAttributeDefinitionDict"], +) -> None: + """Set attribute definitioins on entity list. + + Args: + project_name (str): Project name. + list_id (str): Entity list id. + attribute_definitions (list[EntityListAttributeDefinitionDict]): + List of attribute definitions. + + """ con = get_server_api_connection() - return con.get_rest_representation( + return con.set_entity_list_attribute_definitions( project_name=project_name, - representation_id=representation_id, + list_id=list_id, + attribute_definitions=attribute_definitions, ) -def get_representations( +def create_entity_list_item( project_name: str, - representation_ids: Optional[Iterable[str]] = None, - representation_names: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, - names_by_version_ids: Optional[dict[str, Iterable[str]]] = None, - statuses: Optional[Iterable[str]] = None, - tags: Optional[Iterable[str]] = None, - active: Optional[bool] = True, - has_links: Optional[str] = None, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Generator["RepresentationDict", None, None]: - """Get representation entities based on passed filters from server. - - .. todo:: - - Add separated function for 'names_by_version_ids' filtering. - Because can't be combined with others. + list_id: str, + *, + position: Optional[int] = None, + label: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, + item_id: Optional[str] = None, +) -> str: + """Create entity list item. Args: - project_name (str): Name of project where to look for versions. - representation_ids (Optional[Iterable[str]]): Representation ids - used for representation filtering. - representation_names (Optional[Iterable[str]]): Representation - names used for representation filtering. - version_ids (Optional[Iterable[str]]): Version ids used for - representation filtering. Versions are parents of - representations. - names_by_version_ids (Optional[dict[str, Iterable[str]]]): Find - representations by names and version ids. This filter - discards all other filters. - statuses (Optional[Iterable[str]]): Representation statuses used - for filtering. - tags (Optional[Iterable[str]]): Representation tags used - for filtering. - active (Optional[bool]): Receive active/inactive entities. - Both are returned when 'None' is passed. - has_links (Optional[Literal[IN, OUT, ANY]]): Filter - representations with IN/OUT/ANY links. - fields (Optional[Iterable[str]]): Fields to be queried for - representation. All possible fields are returned if 'None' is - passed. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + project_name (str): Project name where entity list lives. + list_id (str): Entity list id where item will be added. + position (Optional[int]): Position of item in entity list. + label (Optional[str]): Label of item in entity list. + attrib (Optional[dict[str, Any]]): Item attribute values. + data (Optional[dict[str, Any]]): Item data. + tags (Optional[list[str]]): Tags of item in entity list. + item_id (Optional[str]): Id of item that will be created. Returns: - Generator[RepresentationDict, None, None]: Queried - representation entities. + str: Item id. """ con = get_server_api_connection() - return con.get_representations( + return con.create_entity_list_item( project_name=project_name, - representation_ids=representation_ids, - representation_names=representation_names, - version_ids=version_ids, - names_by_version_ids=names_by_version_ids, - statuses=statuses, + list_id=list_id, + position=position, + label=label, + attrib=attrib, + data=data, tags=tags, - active=active, - has_links=has_links, - fields=fields, - own_attributes=own_attributes, + item_id=item_id, ) -def get_representation_by_id( +def update_entity_list_items( project_name: str, - representation_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["RepresentationDict"]: - """Query representation entity from server based on id filter. + list_id: str, + items: list[dict[str, Any]], + mode: "EntityListItemMode", +) -> None: + """Update items in entity list. Args: - project_name (str): Project where to look for representation. - representation_id (str): Id of representation. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. - - Returns: - Optional[RepresentationDict]: Queried representation - entity or None. + project_name (str): Project name where entity list live. + list_id (str): Entity list id. + items (list[dict[str, Any]]): Entity list items. + mode (EntityListItemMode): Mode of items update. """ con = get_server_api_connection() - return con.get_representation_by_id( + return con.update_entity_list_items( project_name=project_name, - representation_id=representation_id, - fields=fields, - own_attributes=own_attributes, + list_id=list_id, + items=items, + mode=mode, ) -def get_representation_by_name( +def update_entity_list_item( project_name: str, - representation_name: str, - version_id: str, - fields: Optional[Iterable[str]] = None, - own_attributes=_PLACEHOLDER, -) -> Optional["RepresentationDict"]: - """Query representation entity by name and version id. + list_id: str, + item_id: str, + *, + new_list_id: Optional[str], + position: Optional[int] = None, + label: Optional[str] = None, + attrib: Optional[dict[str, Any]] = None, + data: Optional[dict[str, Any]] = None, + tags: Optional[list[str]] = None, +) -> None: + """Update item in entity list. Args: - project_name (str): Project where to look for representation. - representation_name (str): Representation name. - version_id (str): Version id. - fields (Optional[Iterable[str]]): fields to be queried - for representations. - own_attributes (Optional[bool]): DEPRECATED: Not supported for - representations. + project_name (str): Project name where entity list live. + list_id (str): Entity list id where item lives. + item_id (str): Item id that will be removed from entity list. + new_list_id (Optional[str]): New entity list id where item will be + added. + position (Optional[int]): Position of item in entity list. + label (Optional[str]): Label of item in entity list. + attrib (Optional[dict[str, Any]]): Attributes of item in entity + list. + data (Optional[dict[str, Any]]): Custom data of item in + entity list. + tags (Optional[list[str]]): Tags of item in entity list. - Returns: - Optional[RepresentationDict]: Queried representation entity - or None. + """ + con = get_server_api_connection() + return con.update_entity_list_item( + project_name=project_name, + list_id=list_id, + item_id=item_id, + new_list_id=new_list_id, + position=position, + label=label, + attrib=attrib, + data=data, + tags=tags, + ) + + +def delete_entity_list_item( + project_name: str, + list_id: str, + item_id: str, +) -> None: + """Delete item from entity list. + + Args: + project_name (str): Project name where entity list live. + list_id (str): Entity list id from which item will be removed. + item_id (str): Item id that will be removed from entity list. """ con = get_server_api_connection() - return con.get_representation_by_name( + return con.delete_entity_list_item( project_name=project_name, - representation_name=representation_name, - version_id=version_id, - fields=fields, - own_attributes=own_attributes, + list_id=list_id, + item_id=item_id, ) -def get_representations_hierarchy( +def get_thumbnail_by_id( project_name: str, - representation_ids: Iterable[str], - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - task_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - representation_fields: Optional[Iterable[str]] = None, -) -> dict[str, RepresentationHierarchy]: - """Find representation with parents by representation id. + thumbnail_id: str, +) -> ThumbnailContent: + """Get thumbnail from server by id. - Representation entity with parent entities up to project. + Warnings: + Please keep in mind that used endpoint is allowed only for admins + and managers. Use 'get_thumbnail' with entity type and id + to allow access for artists. - Default fields are used when any fields are set to `None`. But it is - possible to pass in empty iterable (list, set, tuple) to skip - entity. + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. Args: - project_name (str): Project where to look for entities. - representation_ids (Iterable[str]): Representation ids. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - task_fields (Optional[Iterable[str]]): Task fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - representation_fields (Optional[Iterable[str]]): Representation - fields. + project_name (str): Project under which the entity is located. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. Returns: - dict[str, RepresentationHierarchy]: Parent entities by - representation id. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_representations_hierarchy( + return con.get_thumbnail_by_id( project_name=project_name, - representation_ids=representation_ids, - project_fields=project_fields, - folder_fields=folder_fields, - task_fields=task_fields, - product_fields=product_fields, - version_fields=version_fields, - representation_fields=representation_fields, + thumbnail_id=thumbnail_id, ) -def get_representation_hierarchy( +def get_thumbnail( project_name: str, - representation_id: str, - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - task_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, - representation_fields: Optional[Iterable[str]] = None, -) -> Optional[RepresentationHierarchy]: - """Find representation parents by representation id. + entity_type: str, + entity_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Get thumbnail from server. - Representation parent entities up to project. + Permissions of thumbnails are related to entities so thumbnails must + be queried per entity. So an entity type and entity id is required + to be passed. + + Notes: + It is recommended to use one of prepared entity type specific + methods 'get_folder_thumbnail', 'get_version_thumbnail' or + 'get_workfile_thumbnail'. + We do recommend pass thumbnail id if you have access to it. Each + entity that allows thumbnails has 'thumbnailId' field, so it + can be queried. Args: - project_name (str): Project where to look for entities. - representation_id (str): Representation id. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - task_fields (Optional[Iterable[str]]): Task fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. - representation_fields (Optional[Iterable[str]]): Representation - fields. + project_name (str): Project under which the entity is located. + entity_type (str): Entity type which passed entity id represents. + entity_id (str): Entity id for which thumbnail should be returned. + thumbnail_id (Optional[str]): DEPRECATED Use + 'get_thumbnail_by_id'. Returns: - RepresentationHierarchy: Representation hierarchy entities. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_representation_hierarchy( + return con.get_thumbnail( project_name=project_name, - representation_id=representation_id, - project_fields=project_fields, - folder_fields=folder_fields, - task_fields=task_fields, - product_fields=product_fields, - version_fields=version_fields, - representation_fields=representation_fields, + entity_type=entity_type, + entity_id=entity_id, + thumbnail_id=thumbnail_id, ) - -def get_representations_parents( - project_name: str, - representation_ids: Iterable[str], - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, -) -> dict[str, RepresentationParents]: - """Find representations parents by representation id. - - Representation parent entities up to project. + +def get_folder_thumbnail( + project_name: str, + folder_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for folder entity. Args: - project_name (str): Project where to look for entities. - representation_ids (Iterable[str]): Representation ids. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. + project_name (str): Project under which the entity is located. + folder_id (str): Folder id for which thumbnail should be returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. Returns: - dict[str, RepresentationParents]: Parent entities by - representation id. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_representations_parents( + return con.get_folder_thumbnail( project_name=project_name, - representation_ids=representation_ids, - project_fields=project_fields, - folder_fields=folder_fields, - product_fields=product_fields, - version_fields=version_fields, + folder_id=folder_id, + thumbnail_id=thumbnail_id, ) -def get_representation_parents( +def get_task_thumbnail( project_name: str, - representation_id: str, - project_fields: Optional[Iterable[str]] = None, - folder_fields: Optional[Iterable[str]] = None, - product_fields: Optional[Iterable[str]] = None, - version_fields: Optional[Iterable[str]] = None, -) -> Optional["RepresentationParents"]: - """Find representation parents by representation id. - - Representation parent entities up to project. + task_id: str, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for task entity. Args: - project_name (str): Project where to look for entities. - representation_id (str): Representation id. - project_fields (Optional[Iterable[str]]): Project fields. - folder_fields (Optional[Iterable[str]]): Folder fields. - product_fields (Optional[Iterable[str]]): Product fields. - version_fields (Optional[Iterable[str]]): Version fields. + project_name (str): Project under which the entity is located. + task_id (str): Folder id for which thumbnail should be returned. Returns: - RepresentationParents: Representation parent entities. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_representation_parents( + return con.get_task_thumbnail( project_name=project_name, - representation_id=representation_id, - project_fields=project_fields, - folder_fields=folder_fields, - product_fields=product_fields, - version_fields=version_fields, + task_id=task_id, ) -def get_repre_ids_by_context_filters( +def get_version_thumbnail( project_name: str, - context_filters: Optional[dict[str, Iterable[str]]], - representation_names: Optional[Iterable[str]] = None, - version_ids: Optional[Iterable[str]] = None, -) -> list[str]: - """Find representation ids which match passed context filters. - - Each representation has context integrated on representation entity in - database. The context may contain project, folder, task name or - product name, product type and many more. This implementation gives - option to quickly filter representation based on representation data - in database. - - Context filters have defined structure. To define filter of nested - subfield use dot '.' as delimiter (For example 'task.name'). - Filter values can be regex filters. String or ``re.Pattern`` can - be used. + version_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for version entity. Args: - project_name (str): Project where to look for representations. - context_filters (dict[str, list[str]]): Filters of context fields. - representation_names (Optional[Iterable[str]]): Representation - names, can be used as additional filter for representations - by their names. - version_ids (Optional[Iterable[str]]): Version ids, can be used - as additional filter for representations by their parent ids. + project_name (str): Project under which the entity is located. + version_id (str): Version id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. Returns: - list[str]: Representation ids that match passed filters. - - Example: - The function returns just representation ids so if entities are - required for funtionality they must be queried afterwards by - their ids. - >>> from ayon_api import get_repre_ids_by_context_filters - >>> from ayon_api import get_representations - >>> project_name = "testProject" - >>> filters = { - ... "task.name": ["[aA]nimation"], - ... "product": [".*[Mm]ain"] - ... } - >>> repre_ids = get_repre_ids_by_context_filters( - ... project_name, filters) - >>> repres = get_representations(project_name, repre_ids) + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.get_repre_ids_by_context_filters( + return con.get_version_thumbnail( project_name=project_name, - context_filters=context_filters, - representation_names=representation_names, - version_ids=version_ids, + version_id=version_id, + thumbnail_id=thumbnail_id, ) -def create_representation( +def get_workfile_thumbnail( project_name: str, - name: str, - version_id: str, - files: Optional[list[dict[str, Any]]] = None, - attrib: Optional[dict[str, Any]] = None, - data: Optional[dict[str, Any]] = None, - traits: Optional[dict[str, Any]] = None, - tags: Optional[list[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, - representation_id: Optional[str] = None, -) -> str: - """Create new representation. + workfile_id: str, + thumbnail_id: Optional[str] = None, +) -> ThumbnailContent: + """Prepared method to receive thumbnail for workfile entity. Args: - project_name (str): Project name. - name (str): Representation name. - version_id (str): Parent version id. - files (Optional[list[dict]]): Representation files information. - attrib (Optional[dict[str, Any]]): Representation attributes. - data (Optional[dict[str, Any]]): Representation data. - traits (Optional[dict[str, Any]]): Representation traits - serialized data as dict. - tags (Optional[Iterable[str]]): Representation tags. - status (Optional[str]): Representation status. - active (Optional[bool]): Representation active state. - representation_id (Optional[str]): Representation id. If not - passed new id is generated. + project_name (str): Project under which the entity is located. + workfile_id (str): Worfile id for which thumbnail should be + returned. + thumbnail_id (Optional[str]): Prepared thumbnail id from entity. + Used only to check if thumbnail was already cached. Returns: - str: Representation id. + ThumbnailContent: Thumbnail content wrapper. Does not have to be + valid. """ con = get_server_api_connection() - return con.create_representation( + return con.get_workfile_thumbnail( project_name=project_name, - name=name, - version_id=version_id, - files=files, - attrib=attrib, - data=data, - traits=traits, - tags=tags, - status=status, - active=active, - representation_id=representation_id, + workfile_id=workfile_id, + thumbnail_id=thumbnail_id, ) -def update_representation( +def create_thumbnail( project_name: str, - representation_id: str, - name: Optional[str] = None, - version_id: Optional[str] = None, - files: Optional[list[dict[str, Any]]] = None, - attrib: Optional[dict[str, Any]] = None, - data: Optional[dict[str, Any]] = None, - traits: Optional[dict[str, Any]] = None, - tags: Optional[list[str]] = None, - status: Optional[str] = None, - active: Optional[bool] = None, -): - """Update representation entity on server. + src_filepath: str, + thumbnail_id: Optional[str] = None, +) -> str: + """Create new thumbnail on server from passed path. - Update of ``data`` will override existing value on folder entity. + Args: + project_name (str): Project where the thumbnail will be created + and can be used. + src_filepath (str): Filepath to thumbnail which should be uploaded. + thumbnail_id (Optional[str]): Prepared if of thumbnail. - Update of ``attrib`` does change only passed attributes. If you want - to unset value, use ``None``. + Returns: + str: Created thumbnail id. - Args: - project_name (str): Project name. - representation_id (str): Representation id. - name (Optional[str]): New name. - version_id (Optional[str]): New version id. - files (Optional[list[dict]]): New files - information. - attrib (Optional[dict[str, Any]]): New attributes. - data (Optional[dict[str, Any]]): New data. - traits (Optional[dict[str, Any]]): New traits. - tags (Optional[Iterable[str]]): New tags. - status (Optional[str]): New status. - active (Optional[bool]): New active state. + Raises: + ValueError: When thumbnail source cannot be processed. """ con = get_server_api_connection() - return con.update_representation( + return con.create_thumbnail( project_name=project_name, - representation_id=representation_id, - name=name, - version_id=version_id, - files=files, - attrib=attrib, - data=data, - traits=traits, - tags=tags, - status=status, - active=active, + src_filepath=src_filepath, + thumbnail_id=thumbnail_id, ) -def delete_representation( +def update_thumbnail( project_name: str, - representation_id: str, + thumbnail_id: str, + src_filepath: str, ): - """Delete representation. + """Change thumbnail content by id. + + Update can be also used to create new thumbnail. Args: - project_name (str): Project name. - representation_id (str): Representation id to delete. + project_name (str): Project where the thumbnail will be created + and can be used. + thumbnail_id (str): Thumbnail id to update. + src_filepath (str): Filepath to thumbnail which should be uploaded. + + Raises: + ValueError: When thumbnail source cannot be processed. """ con = get_server_api_connection() - return con.delete_representation( + return con.update_thumbnail( project_name=project_name, - representation_id=representation_id, + thumbnail_id=thumbnail_id, + src_filepath=src_filepath, ) From d9994be92d42d2c87dccf6ce9f319552d7510ae5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:08:52 +0200 Subject: [PATCH 44/53] ruff fixes --- ayon_api/_api.py | 10 ++++++---- ayon_api/_api_helpers/actions.py | 6 +++--- ayon_api/_api_helpers/events.py | 2 +- ayon_api/_api_helpers/folders.py | 2 +- ayon_api/_api_helpers/links.py | 2 +- ayon_api/_api_helpers/projects.py | 2 +- ayon_api/_api_helpers/tasks.py | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 475396d29..4ef2be426 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -12,7 +12,8 @@ import os import socket import typing -from typing import Optional, Set, List, Tuple, Dict, Iterable, Generator, Any +from typing import Optional, Iterable, Generator, Any + import requests from .constants import ( @@ -40,6 +41,7 @@ from typing import Union from .typing import ( ServerVersion, + ActionManifestDict, ActivityType, ActivityReferenceType, EntityListEntityType, @@ -307,7 +309,7 @@ def get_service_addon_settings(project_name=None): project_name (Optional[str]): Project name. Returns: - Dict[str, Any]: Addon settings. + dict[str, Any]: Addon settings. Raises: ValueError: When service was not initialized. @@ -1688,7 +1690,7 @@ def get_actions( *, variant: Optional[str] = None, mode: Optional["ActionModeType"] = None, -) -> list["ActionManifestdict"]: +) -> list["ActionManifestDict"]: """Get actions for a context. Args: @@ -1705,7 +1707,7 @@ def get_actions( mode (Optional[ActionModeType]): Action modes. Returns: - list[ActionManifestdict]: list of action manifests. + list[ActionManifestDict]: list of action manifests. """ con = get_server_api_connection() diff --git a/ayon_api/_api_helpers/actions.py b/ayon_api/_api_helpers/actions.py index 6cbe9b7bd..136bf29cb 100644 --- a/ayon_api/_api_helpers/actions.py +++ b/ayon_api/_api_helpers/actions.py @@ -10,7 +10,7 @@ if typing.TYPE_CHECKING: from ayon_api.typing import ( ActionEntityTypes, - ActionManifestdict, + ActionManifestDict, ActionTriggerResponse, ActionTakeResponse, ActionConfigResponse, @@ -30,7 +30,7 @@ def get_actions( *, variant: Optional[str] = None, mode: Optional["ActionModeType"] = None, - ) -> list["ActionManifestdict"]: + ) -> list["ActionManifestDict"]: """Get actions for a context. Args: @@ -47,7 +47,7 @@ def get_actions( mode (Optional[ActionModeType]): Action modes. Returns: - list[ActionManifestdict]: list of action manifests. + list[ActionManifestDict]: list of action manifests. """ if variant is None: diff --git a/ayon_api/_api_helpers/events.py b/ayon_api/_api_helpers/events.py index 3c7d61273..f028d5c9e 100644 --- a/ayon_api/_api_helpers/events.py +++ b/ayon_api/_api_helpers/events.py @@ -382,4 +382,4 @@ def enroll_event_job( self.log.error(response.text) return None - return response.data \ No newline at end of file + return response.data diff --git a/ayon_api/_api_helpers/folders.py b/ayon_api/_api_helpers/folders.py index 7f6b3ca94..a0805b73f 100644 --- a/ayon_api/_api_helpers/folders.py +++ b/ayon_api/_api_helpers/folders.py @@ -635,4 +635,4 @@ def delete_folder( if force: url += "?force=true" response = self.delete(url) - response.raise_for_status() \ No newline at end of file + response.raise_for_status() diff --git a/ayon_api/_api_helpers/links.py b/ayon_api/_api_helpers/links.py index 7e75db966..f0a5f6580 100644 --- a/ayon_api/_api_helpers/links.py +++ b/ayon_api/_api_helpers/links.py @@ -654,4 +654,4 @@ def get_representation_links( """ return self.get_representations_links( project_name, [representation_id], link_types, link_direction - )[representation_id] \ No newline at end of file + )[representation_id] diff --git a/ayon_api/_api_helpers/projects.py b/ayon_api/_api_helpers/projects.py index f24a67f3c..c54340458 100644 --- a/ayon_api/_api_helpers/projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -734,4 +734,4 @@ def _get_project_roots_values( f"projects/{project_name}/siteRoots{query}" ) response.raise_for_status() - return response.data \ No newline at end of file + return response.data diff --git a/ayon_api/_api_helpers/tasks.py b/ayon_api/_api_helpers/tasks.py index 0e825de01..7be323c30 100644 --- a/ayon_api/_api_helpers/tasks.py +++ b/ayon_api/_api_helpers/tasks.py @@ -512,4 +512,4 @@ def delete_task(self, project_name: str, task_id: str): response = self.delete( f"projects/{project_name}/tasks/{task_id}" ) - response.raise_for_status() \ No newline at end of file + response.raise_for_status() From d6b112c0ea1aaf79cbeb5b87eead22159718a460 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:09:28 +0200 Subject: [PATCH 45/53] added annotations import to public api --- ayon_api/_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 4ef2be426..46a04fadb 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -9,6 +9,8 @@ automatically, and changing them manually can cause issues. """ +from __future__ import annotations + import os import socket import typing From 8a2571e4a8ce6fc341544655c4dd3697dbe04cfc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:17:14 +0200 Subject: [PATCH 46/53] more ruff fixes --- ayon_api/_api.py | 1 - ayon_api/exceptions.py | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 46a04fadb..0b44346ed 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -43,7 +43,6 @@ from typing import Union from .typing import ( ServerVersion, - ActionManifestDict, ActivityType, ActivityReferenceType, EntityListEntityType, diff --git a/ayon_api/exceptions.py b/ayon_api/exceptions.py index 55343b1e8..6a44e9d9c 100644 --- a/ayon_api/exceptions.py +++ b/ayon_api/exceptions.py @@ -2,14 +2,16 @@ try: # This should be used if 'requests' have it available - from requests.exceptions import JSONDecodeError as RequestsJSONDecodeError + from requests.exceptions import JSONDecodeError except ImportError: # Older versions of 'requests' don't have custom exception for json # decode error try: - from simplejson import JSONDecodeError as RequestsJSONDecodeError + from simplejson import JSONDecodeError except ImportError: - from json import JSONDecodeError as RequestsJSONDecodeError + from json import JSONDecodeError + +RequestsJSONDecodeError = JSONDecodeError class UrlError(Exception): From 699d3748264448efbd359b932084028640b194c8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:48:13 +0200 Subject: [PATCH 47/53] don't use wrapped typehins --- automated_api.py | 4 +- ayon_api/_api.py | 287 ++++++++++--------- ayon_api/_api_helpers/actions.py | 24 +- ayon_api/_api_helpers/activities.py | 12 +- ayon_api/_api_helpers/attributes.py | 18 +- ayon_api/_api_helpers/base.py | 8 +- ayon_api/_api_helpers/bundles_addons.py | 19 +- ayon_api/_api_helpers/dependency_packages.py | 11 +- ayon_api/_api_helpers/events.py | 15 +- ayon_api/_api_helpers/folders.py | 20 +- ayon_api/_api_helpers/installers.py | 22 +- ayon_api/_api_helpers/links.py | 124 ++++---- ayon_api/_api_helpers/lists.py | 8 +- ayon_api/_api_helpers/products.py | 24 +- ayon_api/_api_helpers/projects.py | 22 +- ayon_api/_api_helpers/representations.py | 18 +- ayon_api/_api_helpers/secrets.py | 4 +- ayon_api/_api_helpers/tasks.py | 18 +- ayon_api/_api_helpers/thumbnails.py | 2 +- ayon_api/_api_helpers/versions.py | 24 +- ayon_api/_api_helpers/workfiles.py | 6 +- 21 files changed, 350 insertions(+), 340 deletions(-) diff --git a/automated_api.py b/automated_api.py index b32863cd0..db9348686 100644 --- a/automated_api.py +++ b/automated_api.py @@ -213,12 +213,12 @@ def _get_typehint(annotation, api_globals): _typehing_parents.append(parent) if _typehing_parents: - typehint = f'"{_typehint}"' + typehint = f'{_typehint}' for parent in reversed(_typehing_parents): typehint = f"{parent}[{typehint}]" return typehint - return f'"{typehint}"' + return f'{typehint}' def _get_param_typehint(param, api_globals): diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 0b44346ed..24a1bda94 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -1252,7 +1252,7 @@ def send_batch_operations( def get_installers( version: Optional[str] = None, platform_name: Optional[str] = None, -) -> "InstallersInfoDict": +) -> InstallersInfoDict: """Information about desktop application installers on server. Desktop application installers are helpers to download/update AYON @@ -1284,7 +1284,7 @@ def create_installer( checksum_algorithm: str, file_size: int, sources: Optional[list[dict[str, Any]]] = None, -): +) -> None: """Create new installer information on server. This step will create only metadata. Make sure to upload installer @@ -1328,7 +1328,7 @@ def create_installer( def update_installer( filename: str, sources: list[dict[str, Any]], -): +) -> None: """Update installer information on server. Args: @@ -1346,7 +1346,7 @@ def update_installer( def delete_installer( filename: str, -): +) -> None: """Delete installer from server. Args: @@ -1364,7 +1364,7 @@ def download_installer( dst_filepath: str, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, -): +) -> TransferProgress: """Download installer file from server. Args: @@ -1374,6 +1374,9 @@ def download_installer( progress (Optional[TransferProgress]): Object that gives ability to track download progress. + Returns: + TransferProgress: Progress object. + """ con = get_server_api_connection() return con.download_installer( @@ -1388,7 +1391,7 @@ def upload_installer( src_filepath: str, dst_filename: str, progress: Optional[TransferProgress] = None, -): +) -> requests.Response: """Upload installer file to server. Args: @@ -1409,7 +1412,7 @@ def upload_installer( ) -def get_dependency_packages() -> "DependencyPackagesDict": +def get_dependency_packages() -> DependencyPackagesDict: """Information about dependency packages on server. To download dependency package, use 'download_dependency_package' @@ -1451,7 +1454,7 @@ def create_dependency_package( file_size: int, sources: Optional[list[dict[str, Any]]] = None, platform_name: Optional[str] = None, -): +) -> None: """Create dependency package on server. The package will be created on a server, it is also required to upload @@ -1498,7 +1501,7 @@ def create_dependency_package( def update_dependency_package( filename: str, sources: list[dict[str, Any]], -): +) -> None: """Update dependency package metadata on server. Args: @@ -1518,7 +1521,7 @@ def update_dependency_package( def delete_dependency_package( filename: str, platform_name: Optional[str] = None, -): +) -> None: """Remove dependency package for specific platform. Args: @@ -1577,7 +1580,7 @@ def upload_dependency_package( dst_filename: str, platform_name: Optional[str] = None, progress: Optional[TransferProgress] = None, -): +) -> None: """Upload dependency package to server. Args: @@ -1598,7 +1601,7 @@ def upload_dependency_package( ) -def get_secrets() -> list["SecretDict"]: +def get_secrets() -> list[SecretDict]: """Get all secrets. Example output:: @@ -1624,7 +1627,7 @@ def get_secrets() -> list["SecretDict"]: def get_secret( secret_name: str, -) -> "SecretDict": +) -> SecretDict: """Get secret by name. Example output:: @@ -1650,7 +1653,7 @@ def get_secret( def save_secret( secret_name: str, secret_value: str, -): +) -> None: """Save secret. This endpoint can create and update secret. @@ -1669,7 +1672,7 @@ def save_secret( def delete_secret( secret_name: str, -): +) -> None: """Delete secret by name. Args: @@ -1684,14 +1687,14 @@ def delete_secret( def get_actions( project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, + entity_type: Optional[ActionEntityTypes] = None, entity_ids: Optional[list[str]] = None, entity_subtypes: Optional[list[str]] = None, form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, - mode: Optional["ActionModeType"] = None, -) -> list["ActionManifestDict"]: + mode: Optional[ActionModeType] = None, +) -> list[ActionManifestDict]: """Get actions for a context. Args: @@ -1728,13 +1731,13 @@ def trigger_action( addon_name: str, addon_version: str, project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, + entity_type: Optional[ActionEntityTypes] = None, entity_ids: Optional[list[str]] = None, entity_subtypes: Optional[list[str]] = None, form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, -) -> "ActionTriggerResponse": +) -> ActionTriggerResponse: """Trigger action. Args: @@ -1772,13 +1775,13 @@ def get_action_config( addon_name: str, addon_version: str, project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, + entity_type: Optional[ActionEntityTypes] = None, entity_ids: Optional[list[str]] = None, entity_subtypes: Optional[list[str]] = None, form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, -) -> "ActionConfigResponse": +) -> ActionConfigResponse: """Get action configuration. Args: @@ -1820,13 +1823,13 @@ def set_action_config( addon_version: str, value: dict[str, Any], project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, + entity_type: Optional[ActionEntityTypes] = None, entity_ids: Optional[list[str]] = None, entity_subtypes: Optional[list[str]] = None, form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, -) -> "ActionConfigResponse": +) -> ActionConfigResponse: """Set action configuration. Args: @@ -1867,7 +1870,7 @@ def set_action_config( def take_action( action_token: str, -) -> "ActionTakeResponse": +) -> ActionTakeResponse: """Take action metadata using an action token. Args: @@ -1905,13 +1908,13 @@ def abort_action( def get_activities( project_name: str, activity_ids: Optional[Iterable[str]] = None, - activity_types: Optional[Iterable["ActivityType"]] = None, + activity_types: Optional[Iterable[ActivityType]] = None, entity_ids: Optional[Iterable[str]] = None, entity_names: Optional[Iterable[str]] = None, entity_type: Optional[str] = None, changed_after: Optional[str] = None, changed_before: Optional[str] = None, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + reference_types: Optional[Iterable[ActivityReferenceType]] = None, fields: Optional[Iterable[str]] = None, limit: Optional[int] = None, order: Optional[SortOrder] = None, @@ -1962,7 +1965,7 @@ def get_activities( def get_activity_by_id( project_name: str, activity_id: str, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + reference_types: Optional[Iterable[ActivityReferenceType]] = None, fields: Optional[Iterable[str]] = None, ) -> Optional[dict[str, Any]]: """Get activity by id. @@ -1993,7 +1996,7 @@ def create_activity( project_name: str, entity_id: str, entity_type: str, - activity_type: "ActivityType", + activity_type: ActivityType, activity_id: Optional[str] = None, body: Optional[str] = None, file_ids: Optional[list[str]] = None, @@ -2039,7 +2042,7 @@ def update_activity( file_ids: Optional[list[str]] = None, append_file_ids: Optional[bool] = False, data: Optional[dict[str, Any]] = None, -): +) -> None: """Update activity by id. Args: @@ -2067,7 +2070,7 @@ def update_activity( def delete_activity( project_name: str, activity_id: str, -): +) -> None: """Delete activity by id. Args: @@ -2122,7 +2125,7 @@ def send_activities_batch_operations( ) -def get_bundles() -> "BundlesInfoDict": +def get_bundles() -> BundlesInfoDict: """Server bundles with basic information. This is example output:: @@ -2166,8 +2169,8 @@ def create_bundle( is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, -): + dev_addons_config: Optional[dict[str, DevBundleAddonInfoDict]] = None, +) -> None: """Create bundle on server. Bundle cannot be changed once is created. Only isProduction, isStaging @@ -2229,8 +2232,8 @@ def update_bundle( is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, -): + dev_addons_config: Optional[dict[str, DevBundleAddonInfoDict]] = None, +) -> None: """Update bundle on server. Dependency packages can be update only for single platform. Others @@ -2278,7 +2281,7 @@ def check_bundle_compatibility( is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[dict[str, "DevBundleAddonInfoDict"]] = None, + dev_addons_config: Optional[dict[str, DevBundleAddonInfoDict]] = None, ) -> dict[str, Any]: """Check bundle compatibility. @@ -2320,7 +2323,7 @@ def check_bundle_compatibility( def delete_bundle( bundle_name: str, -): +) -> None: """Delete bundle from server. Args: @@ -2367,7 +2370,7 @@ def get_addon_endpoint( def get_addons_info( details: bool = True, -) -> "AddonsInfoDict": +) -> AddonsInfoDict: """Get information about addons available on server. Args: @@ -2940,7 +2943,7 @@ def get_events( topics: Optional[Iterable[str]] = None, event_ids: Optional[Iterable[str]] = None, project_names: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[EventStatus]] = None, users: Optional[Iterable[str]] = None, include_logs: Optional[bool] = None, has_children: Optional[bool] = None, @@ -2961,7 +2964,7 @@ def get_events( event_ids (Optional[Iterable[str]]): Event ids. project_names (Optional[Iterable[str]]): Project on which event happened. - statuses (Optional[Iterable[str]]): Filtering by statuses. + statuses (Optional[Iterable[EventStatus]]): Filtering by statuses. users (Optional[Iterable[str]]): Filtering by users who created/triggered an event. include_logs (Optional[bool]): Query also log events. @@ -3007,13 +3010,13 @@ def update_event( sender: Optional[str] = None, project_name: Optional[str] = None, username: Optional[str] = None, - status: Optional[str] = None, + status: Optional[EventStatus] = None, description: Optional[str] = None, summary: Optional[dict[str, Any]] = None, payload: Optional[dict[str, Any]] = None, progress: Optional[int] = None, retries: Optional[int] = None, -): +) -> None: """Update event data. Args: @@ -3021,7 +3024,7 @@ def update_event( sender (Optional[str]): New sender of event. project_name (Optional[str]): New project name. username (Optional[str]): New username. - status (Optional[str]): New event status. Enum: "pending", + status (Optional[EventStatus]): New event status. Enum: "pending", "in_progress", "finished", "failed", "aborted", "restarted" description (Optional[str]): New description. summary (Optional[dict[str, Any]]): New summary. @@ -3058,7 +3061,7 @@ def dispatch_event( finished: bool = True, store: bool = True, dependencies: Optional[list[str]] = None, -): +) -> RestApiResponse: """Dispatch event to server. Args: @@ -3073,8 +3076,8 @@ def dispatch_event( be used for simple filtering on listeners. payload (Optional[dict[str, Any]]): Full payload of event data with all details. - finished (Optional[bool]): Mark event as finished on dispatch. - store (Optional[bool]): Store event in event queue for possible + finished (bool): Mark event as finished on dispatch. + store (bool): Store event in event queue for possible future processing otherwise is event send only to active listeners. dependencies (Optional[list[str]]): Deprecated. @@ -3103,7 +3106,7 @@ def dispatch_event( def delete_event( event_id: str, -): +) -> None: """Delete event by id. Supported since AYON server 1.6.0. @@ -3122,16 +3125,16 @@ def delete_event( def enroll_event_job( - source_topic: "Union[str, list[str]]", + source_topic: Union[str, list[str]], target_topic: str, sender: str, description: Optional[str] = None, sequential: Optional[bool] = None, - events_filter: Optional["EventFilter"] = None, + events_filter: Optional[EventFilter] = None, max_retries: Optional[int] = None, ignore_older_than: Optional[str] = None, ignore_sender_types: Optional[str] = None, -): +) -> Optional[EnrollEventData]: """Enroll job based on events. Enroll will find first unprocessed event with 'source_topic' and will @@ -3186,7 +3189,7 @@ def enroll_event_job( by given sender types. Returns: - Optional[dict[str, Any]]: None if there is no event matching + Optional[EnrollEventData]: None if there is no event matching filters. Created event with 'target_topic'. """ @@ -3206,25 +3209,25 @@ def enroll_event_job( def get_attributes_schema( use_cache: bool = True, -) -> "AttributesSchemaDict": +) -> AttributesSchemaDict: con = get_server_api_connection() return con.get_attributes_schema( use_cache=use_cache, ) -def reset_attributes_schema(): +def reset_attributes_schema() -> None: con = get_server_api_connection() return con.reset_attributes_schema() def set_attribute_config( attribute_name: str, - data: "AttributeSchemaDataDict", - scope: list["AttributeScope"], + data: AttributeSchemaDataDict, + scope: list[AttributeScope], position: Optional[int] = None, builtin: bool = False, -): +) -> None: con = get_server_api_connection() return con.set_attribute_config( attribute_name=attribute_name, @@ -3237,7 +3240,7 @@ def set_attribute_config( def remove_attribute_config( attribute_name: str, -): +) -> None: """Remove attribute from server. This can't be un-done, please use carefully. @@ -3253,8 +3256,8 @@ def remove_attribute_config( def get_attributes_for_type( - entity_type: "AttributeScope", -) -> dict[str, "AttributeSchemaDict"]: + entity_type: AttributeScope, +) -> dict[str, AttributeSchemaDict]: """Get attribute schemas available for an entity type. Example:: @@ -3298,7 +3301,7 @@ def get_attributes_for_type( def get_attributes_fields_for_type( - entity_type: "AttributeScope", + entity_type: AttributeScope, ) -> set[str]: """Prepare attribute fields for entity type. @@ -3312,7 +3315,7 @@ def get_attributes_fields_for_type( ) -def get_project_anatomy_presets() -> list["AnatomyPresetDict"]: +def get_project_anatomy_presets() -> list[AnatomyPresetDict]: """Anatomy presets available on server. Content has basic information about presets. Example output:: @@ -3354,7 +3357,7 @@ def get_default_anatomy_preset_name() -> str: def get_project_anatomy_preset( preset_name: Optional[str] = None, -) -> "AnatomyPresetDict": +) -> AnatomyPresetDict: """Anatomy preset values by name. Get anatomy preset values by preset name. Primary preset is returned @@ -3373,7 +3376,7 @@ def get_project_anatomy_preset( ) -def get_built_in_anatomy_preset() -> "AnatomyPresetDict": +def get_built_in_anatomy_preset() -> AnatomyPresetDict: """Get built-in anatomy preset. Returns: @@ -3384,14 +3387,14 @@ def get_built_in_anatomy_preset() -> "AnatomyPresetDict": return con.get_built_in_anatomy_preset() -def get_build_in_anatomy_preset() -> "AnatomyPresetDict": +def get_build_in_anatomy_preset() -> AnatomyPresetDict: con = get_server_api_connection() return con.get_build_in_anatomy_preset() def get_rest_project( project_name: str, -) -> Optional["ProjectDict"]: +) -> Optional[ProjectDict]: """Query project by name. This call returns project with anatomy data. @@ -3413,7 +3416,7 @@ def get_rest_project( def get_rest_projects( active: Optional[bool] = True, library: Optional[bool] = None, -) -> Generator["ProjectDict", None, None]: +) -> Generator[ProjectDict, None, None]: """Query available project entities. User must be logged in. @@ -3465,7 +3468,7 @@ def get_projects( library: Optional[bool] = None, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Generator["ProjectDict", None, None]: +) -> Generator[ProjectDict, None, None]: """Get projects. Args: @@ -3495,7 +3498,7 @@ def get_project( project_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["ProjectDict"]: +) -> Optional[ProjectDict]: """Get project. Args: @@ -3523,7 +3526,7 @@ def create_project( project_code: str, library_project: bool = False, preset_name: Optional[str] = None, -) -> "ProjectDict": +) -> ProjectDict: """Create project using AYON settings. This project creation function is not validating project entity on @@ -3573,7 +3576,7 @@ def update_project( active: Optional[bool] = None, project_code: Optional[str] = None, **changes, -): +) -> None: """Update project entity on server. Args: @@ -3796,7 +3799,7 @@ def get_project_roots_by_platform( def get_rest_folder( project_name: str, folder_id: str, -) -> Optional["FolderDict"]: +) -> Optional[FolderDict]: con = get_server_api_connection() return con.get_rest_folder( project_name=project_name, @@ -3807,7 +3810,7 @@ def get_rest_folder( def get_rest_folders( project_name: str, include_attrib: bool = False, -) -> list["FlatFolderDict"]: +) -> list[FlatFolderDict]: """Get simplified flat list of all project folders. Get all project folders in single REST call. This can be faster than @@ -3859,7 +3862,7 @@ def get_folders_hierarchy( project_name: str, search_string: Optional[str] = None, folder_types: Optional[Iterable[str]] = None, -) -> "ProjectHierarchyDict": +) -> ProjectHierarchyDict: """Get project hierarchy. All folders in project in hierarchy data structure. @@ -3903,7 +3906,7 @@ def get_folders_hierarchy( def get_folders_rest( project_name: str, include_attrib: bool = False, -) -> list["FlatFolderDict"]: +) -> list[FlatFolderDict]: """Get simplified flat list of all project folders. Get all project folders in single REST call. This can be faster than @@ -3975,7 +3978,7 @@ def get_folders( has_links: Optional[bool] = None, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Generator["FolderDict", None, None]: +) -> Generator[FolderDict, None, None]: """Query folders from server. Todos: @@ -4051,7 +4054,7 @@ def get_folder_by_id( folder_id: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["FolderDict"]: +) -> Optional[FolderDict]: """Query folder entity by id. Args: @@ -4082,7 +4085,7 @@ def get_folder_by_path( folder_path: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["FolderDict"]: +) -> Optional[FolderDict]: """Query folder entity by path. Folder path is a path to folder with all parent names joined by slash. @@ -4115,7 +4118,7 @@ def get_folder_by_name( folder_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["FolderDict"]: +) -> Optional[FolderDict]: """Query folder entity by path. Warnings: @@ -4238,7 +4241,7 @@ def update_folder( status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = NOT_SET, -): +) -> None: """Update folder entity on server. Do not pass ``parent_id``, ``label`` amd ``thumbnail_id`` if you don't @@ -4286,7 +4289,7 @@ def delete_folder( project_name: str, folder_id: str, force: bool = False, -): +) -> None: """Delete folder. Args: @@ -4307,7 +4310,7 @@ def delete_folder( def get_rest_task( project_name: str, task_id: str, -) -> Optional["TaskDict"]: +) -> Optional[TaskDict]: con = get_server_api_connection() return con.get_rest_task( project_name=project_name, @@ -4328,7 +4331,7 @@ def get_tasks( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Generator["TaskDict", None, None]: +) -> Generator[TaskDict, None, None]: """Query task entities from server. Args: @@ -4383,7 +4386,7 @@ def get_task_by_name( task_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["TaskDict"]: +) -> Optional[TaskDict]: """Query task entity by name and folder id. Args: @@ -4415,7 +4418,7 @@ def get_task_by_id( task_id: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["TaskDict"]: +) -> Optional[TaskDict]: """Query task entity by id. Args: @@ -4452,7 +4455,7 @@ def get_tasks_by_folder_paths( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> dict[str, list["TaskDict"]]: +) -> dict[str, list[TaskDict]]: """Query task entities from server by folder paths. Args: @@ -4511,7 +4514,7 @@ def get_tasks_by_folder_path( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> list["TaskDict"]: +) -> list[TaskDict]: """Query task entities from server by folder path. Args: @@ -4560,7 +4563,7 @@ def get_task_by_folder_path( task_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, -) -> Optional["TaskDict"]: +) -> Optional[TaskDict]: """Query task entity by folder path and task name. Args: @@ -4655,7 +4658,7 @@ def update_task( status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = NOT_SET, -): +) -> None: """Update task entity on server. Do not pass ``label`` amd ``thumbnail_id`` if you don't @@ -4704,7 +4707,7 @@ def update_task( def delete_task( project_name: str, task_id: str, -): +) -> None: """Delete task. Args: @@ -4722,7 +4725,7 @@ def delete_task( def get_rest_product( project_name: str, product_id: str, -) -> Optional["ProductDict"]: +) -> Optional[ProductDict]: con = get_server_api_connection() return con.get_rest_product( project_name=project_name, @@ -4744,7 +4747,7 @@ def get_products( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["ProductDict", None, None]: +) -> Generator[ProductDict, None, None]: """Query products from server. Todos: @@ -4804,7 +4807,7 @@ def get_product_by_id( product_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["ProductDict"]: +) -> Optional[ProductDict]: """Query product entity by id. Args: @@ -4836,7 +4839,7 @@ def get_product_by_name( folder_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["ProductDict"]: +) -> Optional[ProductDict]: """Query product entity by name and folder id. Args: @@ -4866,7 +4869,7 @@ def get_product_by_name( def get_product_types( fields: Optional[Iterable[str]] = None, -) -> list["ProductTypeDict"]: +) -> list[ProductTypeDict]: """Types of products. This is server wide information. Product types have 'name', 'icon' and @@ -4888,7 +4891,7 @@ def get_product_types( def get_project_product_types( project_name: str, fields: Optional[Iterable[str]] = None, -) -> list["ProductTypeDict"]: +) -> list[ProductTypeDict]: """DEPRECATED Types of products available in a project. Filter only product types available in a project. @@ -4993,7 +4996,7 @@ def update_product( tags: Optional[Iterable[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, -): +) -> None: """Update product entity on server. Update of ``data`` will override existing value on folder entity. @@ -5032,7 +5035,7 @@ def update_product( def delete_product( project_name: str, product_id: str, -): +) -> None: """Delete product. Args: @@ -5050,7 +5053,7 @@ def delete_product( def get_rest_version( project_name: str, version_id: str, -) -> Optional["VersionDict"]: +) -> Optional[VersionDict]: con = get_server_api_connection() return con.get_rest_version( project_name=project_name, @@ -5072,7 +5075,7 @@ def get_versions( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["VersionDict", None, None]: +) -> Generator[VersionDict, None, None]: """Get version entities based on passed filters from server. Args: @@ -5129,7 +5132,7 @@ def get_version_by_id( version_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: +) -> Optional[VersionDict]: """Query version entity by id. Args: @@ -5161,7 +5164,7 @@ def get_version_by_name( product_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: +) -> Optional[VersionDict]: """Query version entity by version and product id. Args: @@ -5194,7 +5197,7 @@ def get_hero_version_by_id( version_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: +) -> Optional[VersionDict]: """Query hero version entity by id. Args: @@ -5225,7 +5228,7 @@ def get_hero_version_by_product_id( product_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: +) -> Optional[VersionDict]: """Query hero version entity by product id. Only one hero version is available on a product. @@ -5260,7 +5263,7 @@ def get_hero_versions( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["VersionDict", None, None]: +) -> Generator[VersionDict, None, None]: """Query hero versions by multiple filters. Only one hero version is available on a product. @@ -5299,7 +5302,7 @@ def get_last_versions( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> dict[str, Optional["VersionDict"]]: +) -> dict[str, Optional[VersionDict]]: """Query last version entities by product ids. Args: @@ -5332,7 +5335,7 @@ def get_last_version_by_product_id( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: +) -> Optional[VersionDict]: """Query last version entity by product id. Args: @@ -5366,7 +5369,7 @@ def get_last_version_by_product_name( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["VersionDict"]: +) -> Optional[VersionDict]: """Query last version entity by product name and folder id. Args: @@ -5481,7 +5484,7 @@ def update_version( status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = NOT_SET, -): +) -> None: """Update version entity on server. Do not pass ``task_id`` amd ``thumbnail_id`` if you don't @@ -5528,7 +5531,7 @@ def update_version( def delete_version( project_name: str, version_id: str, -): +) -> None: """Delete version. Args: @@ -5546,7 +5549,7 @@ def delete_version( def get_rest_representation( project_name: str, representation_id: str, -) -> Optional["RepresentationDict"]: +) -> Optional[RepresentationDict]: con = get_server_api_connection() return con.get_rest_representation( project_name=project_name, @@ -5566,7 +5569,7 @@ def get_representations( has_links: Optional[str] = None, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["RepresentationDict", None, None]: +) -> Generator[RepresentationDict, None, None]: """Get representation entities based on passed filters from server. .. todo:: @@ -5626,7 +5629,7 @@ def get_representation_by_id( representation_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["RepresentationDict"]: +) -> Optional[RepresentationDict]: """Query representation entity from server based on id filter. Args: @@ -5657,7 +5660,7 @@ def get_representation_by_name( version_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["RepresentationDict"]: +) -> Optional[RepresentationDict]: """Query representation entity by name and version id. Args: @@ -5816,7 +5819,7 @@ def get_representation_parents( folder_fields: Optional[Iterable[str]] = None, product_fields: Optional[Iterable[str]] = None, version_fields: Optional[Iterable[str]] = None, -) -> Optional["RepresentationParents"]: +) -> Optional[RepresentationParents]: """Find representation parents by representation id. Representation parent entities up to project. @@ -5962,7 +5965,7 @@ def update_representation( tags: Optional[list[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, -): +) -> None: """Update representation entity on server. Update of ``data`` will override existing value on folder entity. @@ -6004,7 +6007,7 @@ def update_representation( def delete_representation( project_name: str, representation_id: str, -): +) -> None: """Delete representation. Args: @@ -6030,7 +6033,7 @@ def get_workfiles_info( has_links: Optional[str] = None, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Generator["WorkfileInfoDict", None, None]: +) -> Generator[WorkfileInfoDict, None, None]: """Workfile info entities by passed filters. Args: @@ -6077,7 +6080,7 @@ def get_workfile_info( path: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: +) -> Optional[WorkfileInfoDict]: """Workfile info entity by task id and workfile path. Args: @@ -6109,7 +6112,7 @@ def get_workfile_info_by_id( workfile_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, -) -> Optional["WorkfileInfoDict"]: +) -> Optional[WorkfileInfoDict]: """Workfile info entity by id. Args: @@ -6232,7 +6235,7 @@ def create_link_type( input_type: str, output_type: str, data: Optional[dict[str, Any]] = None, -): +) -> None: """Create or update link type on server. Warning: @@ -6264,7 +6267,7 @@ def delete_link_type( link_type_name: str, input_type: str, output_type: str, -): +) -> None: """Remove link type from project. Args: @@ -6292,7 +6295,7 @@ def make_sure_link_type_exists( input_type: str, output_type: str, data: Optional[dict[str, Any]] = None, -): +) -> None: """Make sure link type exists on a project. Args: @@ -6321,7 +6324,7 @@ def create_link( output_id: str, output_type: str, link_name: Optional[str] = None, -): +) -> CreateLinkData: """Create link between 2 entities. Link has a type which must already exists on a project. @@ -6343,7 +6346,7 @@ def create_link( Available from server version '1.0.0-rc.6'. Returns: - dict[str, str]: Information about link. + CreateLinkData: Information about link. Raises: HTTPRequestError: Server error happened. @@ -6364,7 +6367,7 @@ def create_link( def delete_link( project_name: str, link_id: str, -): +) -> None: """Remove link by id. Args: @@ -6387,7 +6390,7 @@ def get_entities_links( entity_type: str, entity_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, link_names: Optional[Iterable[str]] = None, link_name_regex: Optional[str] = None, ) -> dict[str, list[dict[str, Any]]]: @@ -6446,7 +6449,7 @@ def get_folders_links( project_name: str, folder_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query folders links from server. @@ -6475,7 +6478,7 @@ def get_folder_links( project_name: str, folder_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query folder links from server. @@ -6503,7 +6506,7 @@ def get_tasks_links( project_name: str, task_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query tasks links from server. @@ -6532,7 +6535,7 @@ def get_task_links( project_name: str, task_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query task links from server. @@ -6560,7 +6563,7 @@ def get_products_links( project_name: str, product_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query products links from server. @@ -6589,7 +6592,7 @@ def get_product_links( project_name: str, product_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query product links from server. @@ -6617,7 +6620,7 @@ def get_versions_links( project_name: str, version_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query versions links from server. @@ -6646,7 +6649,7 @@ def get_version_links( project_name: str, version_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query version links from server. @@ -6674,7 +6677,7 @@ def get_representations_links( project_name: str, representation_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query representations links from server. @@ -6703,7 +6706,7 @@ def get_representation_links( project_name: str, representation_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query representation links from server. @@ -6805,7 +6808,7 @@ def get_entity_list_by_id( def create_entity_list( project_name: str, - entity_type: "EntityListEntityType", + entity_type: EntityListEntityType, label: str, *, list_type: Optional[str] = None, @@ -6920,7 +6923,7 @@ def delete_entity_list( def get_entity_list_attribute_definitions( project_name: str, list_id: str, -) -> list["EntityListAttributeDefinitionDict"]: +) -> list[EntityListAttributeDefinitionDict]: """Get attribute definitioins on entity list. Args: @@ -6942,7 +6945,7 @@ def get_entity_list_attribute_definitions( def set_entity_list_attribute_definitions( project_name: str, list_id: str, - attribute_definitions: list["EntityListAttributeDefinitionDict"], + attribute_definitions: list[EntityListAttributeDefinitionDict], ) -> None: """Set attribute definitioins on entity list. @@ -7005,7 +7008,7 @@ def update_entity_list_items( project_name: str, list_id: str, items: list[dict[str, Any]], - mode: "EntityListItemMode", + mode: EntityListItemMode, ) -> None: """Update items in entity list. @@ -7300,7 +7303,7 @@ def update_thumbnail( project_name: str, thumbnail_id: str, src_filepath: str, -): +) -> None: """Change thumbnail content by id. Update can be also used to create new thumbnail. diff --git a/ayon_api/_api_helpers/actions.py b/ayon_api/_api_helpers/actions.py index 136bf29cb..37ec2f519 100644 --- a/ayon_api/_api_helpers/actions.py +++ b/ayon_api/_api_helpers/actions.py @@ -23,14 +23,14 @@ class ActionsAPI(BaseServerAPI): def get_actions( self, project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, + entity_type: Optional[ActionEntityTypes] = None, entity_ids: Optional[list[str]] = None, entity_subtypes: Optional[list[str]] = None, form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, - mode: Optional["ActionModeType"] = None, - ) -> list["ActionManifestDict"]: + mode: Optional[ActionModeType] = None, + ) -> list[ActionManifestDict]: """Get actions for a context. Args: @@ -77,13 +77,13 @@ def trigger_action( addon_name: str, addon_version: str, project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, + entity_type: Optional[ActionEntityTypes] = None, entity_ids: Optional[list[str]] = None, entity_subtypes: Optional[list[str]] = None, form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, - ) -> "ActionTriggerResponse": + ) -> ActionTriggerResponse: """Trigger action. Args: @@ -134,13 +134,13 @@ def get_action_config( addon_name: str, addon_version: str, project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, + entity_type: Optional[ActionEntityTypes] = None, entity_ids: Optional[list[str]] = None, entity_subtypes: Optional[list[str]] = None, form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, - ) -> "ActionConfigResponse": + ) -> ActionConfigResponse: """Get action configuration. Args: @@ -182,13 +182,13 @@ def set_action_config( addon_version: str, value: dict[str, Any], project_name: Optional[str] = None, - entity_type: Optional["ActionEntityTypes"] = None, + entity_type: Optional[ActionEntityTypes] = None, entity_ids: Optional[list[str]] = None, entity_subtypes: Optional[list[str]] = None, form_data: Optional[dict[str, Any]] = None, *, variant: Optional[str] = None, - ) -> "ActionConfigResponse": + ) -> ActionConfigResponse: """Set action configuration. Args: @@ -225,7 +225,7 @@ def set_action_config( variant, ) - def take_action(self, action_token: str) -> "ActionTakeResponse": + def take_action(self, action_token: str) -> ActionTakeResponse: """Take action metadata using an action token. Args: @@ -267,12 +267,12 @@ def _send_config_request( addon_version: str, value: Optional[dict[str, Any]], project_name: Optional[str], - entity_type: Optional["ActionEntityTypes"], + entity_type: Optional[ActionEntityTypes], entity_ids: Optional[list[str]], entity_subtypes: Optional[list[str]], form_data: Optional[dict[str, Any]], variant: Optional[str], - ) -> "ActionConfigResponse": + ) -> ActionConfigResponse: """Set and get action configuration.""" if variant is None: variant = self.get_default_settings_variant() diff --git a/ayon_api/_api_helpers/activities.py b/ayon_api/_api_helpers/activities.py index c0c58406d..f7ae3d4f7 100644 --- a/ayon_api/_api_helpers/activities.py +++ b/ayon_api/_api_helpers/activities.py @@ -24,13 +24,13 @@ def get_activities( self, project_name: str, activity_ids: Optional[Iterable[str]] = None, - activity_types: Optional[Iterable["ActivityType"]] = None, + activity_types: Optional[Iterable[ActivityType]] = None, entity_ids: Optional[Iterable[str]] = None, entity_names: Optional[Iterable[str]] = None, entity_type: Optional[str] = None, changed_after: Optional[str] = None, changed_before: Optional[str] = None, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + reference_types: Optional[Iterable[ActivityReferenceType]] = None, fields: Optional[Iterable[str]] = None, limit: Optional[int] = None, order: Optional[SortOrder] = None, @@ -109,7 +109,7 @@ def get_activity_by_id( self, project_name: str, activity_id: str, - reference_types: Optional[Iterable["ActivityReferenceType"]] = None, + reference_types: Optional[Iterable[ActivityReferenceType]] = None, fields: Optional[Iterable[str]] = None, ) -> Optional[dict[str, Any]]: """Get activity by id. @@ -141,7 +141,7 @@ def create_activity( project_name: str, entity_id: str, entity_type: str, - activity_type: "ActivityType", + activity_type: ActivityType, activity_id: Optional[str] = None, body: Optional[str] = None, file_ids: Optional[list[str]] = None, @@ -194,7 +194,7 @@ def update_activity( file_ids: Optional[list[str]] = None, append_file_ids: Optional[bool] = False, data: Optional[dict[str, Any]] = None, - ): + ) -> None: """Update activity by id. Args: @@ -242,7 +242,7 @@ def update_activity( ) response.raise_for_status() - def delete_activity(self, project_name: str, activity_id: str): + def delete_activity(self, project_name: str, activity_id: str) -> None: """Delete activity by id. Args: diff --git a/ayon_api/_api_helpers/attributes.py b/ayon_api/_api_helpers/attributes.py index 9a9216d0f..26db47484 100644 --- a/ayon_api/_api_helpers/attributes.py +++ b/ayon_api/_api_helpers/attributes.py @@ -21,7 +21,7 @@ class AttributesAPI(BaseServerAPI): def get_attributes_schema( self, use_cache: bool = True - ) -> "AttributesSchemaDict": + ) -> AttributesSchemaDict: if not use_cache: self.reset_attributes_schema() @@ -31,18 +31,18 @@ def get_attributes_schema( self._attributes_schema = result.data return copy.deepcopy(self._attributes_schema) - def reset_attributes_schema(self): + def reset_attributes_schema(self) -> None: self._attributes_schema = None self._entity_type_attributes_cache = {} def set_attribute_config( self, attribute_name: str, - data: "AttributeSchemaDataDict", - scope: list["AttributeScope"], + data: AttributeSchemaDataDict, + scope: list[AttributeScope], position: Optional[int] = None, builtin: bool = False, - ): + ) -> None: if position is None: attributes = self.get("attributes").data["attributes"] origin_attr = next( @@ -73,7 +73,7 @@ def set_attribute_config( self.reset_attributes_schema() - def remove_attribute_config(self, attribute_name: str): + def remove_attribute_config(self, attribute_name: str) -> None: """Remove attribute from server. This can't be un-done, please use carefully. @@ -91,8 +91,8 @@ def remove_attribute_config(self, attribute_name: str): self.reset_attributes_schema() def get_attributes_for_type( - self, entity_type: "AttributeScope" - ) -> dict[str, "AttributeSchemaDict"]: + self, entity_type: AttributeScope + ) -> dict[str, AttributeSchemaDict]: """Get attribute schemas available for an entity type. Example:: @@ -144,7 +144,7 @@ def get_attributes_for_type( return copy.deepcopy(attributes) def get_attributes_fields_for_type( - self, entity_type: "AttributeScope" + self, entity_type: AttributeScope ) -> set[str]: """Prepare attribute fields for entity type. diff --git a/ayon_api/_api_helpers/base.py b/ayon_api/_api_helpers/base.py index f785080ab..7c792d42d 100644 --- a/ayon_api/_api_helpers/base.py +++ b/ayon_api/_api_helpers/base.py @@ -21,7 +21,7 @@ class BaseServerAPI: def get_server_version(self) -> str: raise NotImplementedError() - def get_server_version_tuple(self) -> "ServerVersion": + def get_server_version_tuple(self) -> ServerVersion: raise NotImplementedError() def get_base_url(self) -> str: @@ -93,7 +93,7 @@ def get_rest_entity_by_id( project_name: str, entity_type: str, entity_id: str, - ) -> Optional["AnyEntityDict"]: + ) -> Optional[AnyEntityDict]: raise NotImplementedError() def get_project( @@ -101,7 +101,7 @@ def get_project( project_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, - ) -> Optional["ProjectDict"]: + ) -> Optional[ProjectDict]: raise NotImplementedError() def _prepare_fields( @@ -112,7 +112,7 @@ def _prepare_fields( ): raise NotImplementedError() - def _convert_entity_data(self, entity: "AnyEntityDict"): + def _convert_entity_data(self, entity: AnyEntityDict): raise NotImplementedError() def _send_batch_operations( diff --git a/ayon_api/_api_helpers/bundles_addons.py b/ayon_api/_api_helpers/bundles_addons.py index dfdd3b591..1b70967ec 100644 --- a/ayon_api/_api_helpers/bundles_addons.py +++ b/ayon_api/_api_helpers/bundles_addons.py @@ -21,7 +21,7 @@ class BundlesAddonsAPI(BaseServerAPI): - def get_bundles(self) -> "BundlesInfoDict": + def get_bundles(self) -> BundlesInfoDict: """Server bundles with basic information. This is example output:: @@ -66,9 +66,8 @@ def create_bundle( is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[ - dict[str, "DevBundleAddonInfoDict"]] = None, - ): + dev_addons_config: Optional[dict[str, DevBundleAddonInfoDict]] = None, + ) -> None: """Create bundle on server. Bundle cannot be changed once is created. Only isProduction, isStaging @@ -137,9 +136,8 @@ def update_bundle( is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[ - dict[str, "DevBundleAddonInfoDict"]] = None, - ): + dev_addons_config: Optional[dict[str, DevBundleAddonInfoDict]] = None, + ) -> None: """Update bundle on server. Dependency packages can be update only for single platform. Others @@ -195,8 +193,7 @@ def check_bundle_compatibility( is_staging: Optional[bool] = None, is_dev: Optional[bool] = None, dev_active_user: Optional[str] = None, - dev_addons_config: Optional[ - dict[str, "DevBundleAddonInfoDict"]] = None, + dev_addons_config: Optional[dict[str, DevBundleAddonInfoDict]] = None, ) -> dict[str, Any]: """Check bundle compatibility. @@ -243,7 +240,7 @@ def check_bundle_compatibility( response.raise_for_status() return response.data - def delete_bundle(self, bundle_name: str): + def delete_bundle(self, bundle_name: str) -> None: """Delete bundle from server. Args: @@ -283,7 +280,7 @@ def get_addon_endpoint( ending = f"/{'/'.join(subpaths)}" return f"addons/{addon_name}/{addon_version}{ending}" - def get_addons_info(self, details: bool = True) -> "AddonsInfoDict": + def get_addons_info(self, details: bool = True) -> AddonsInfoDict: """Get information about addons available on server. Args: diff --git a/ayon_api/_api_helpers/dependency_packages.py b/ayon_api/_api_helpers/dependency_packages.py index 7268d8a77..dc1f43e94 100644 --- a/ayon_api/_api_helpers/dependency_packages.py +++ b/ayon_api/_api_helpers/dependency_packages.py @@ -15,7 +15,7 @@ class DependencyPackagesAPI(BaseServerAPI): - def get_dependency_packages(self) -> "DependencyPackagesDict": + def get_dependency_packages(self) -> DependencyPackagesDict: """Information about dependency packages on server. To download dependency package, use 'download_dependency_package' @@ -59,7 +59,7 @@ def create_dependency_package( file_size: int, sources: Optional[list[dict[str, Any]]] = None, platform_name: Optional[str] = None, - ): + ) -> None: """Create dependency package on server. The package will be created on a server, it is also required to upload @@ -108,7 +108,7 @@ def create_dependency_package( def update_dependency_package( self, filename: str, sources: list[dict[str, Any]] - ): + ) -> None: """Update dependency package metadata on server. Args: @@ -126,7 +126,7 @@ def update_dependency_package( def delete_dependency_package( self, filename: str, platform_name: Optional[str] = None - ): + ) -> None: """Remove dependency package for specific platform. Args: @@ -147,7 +147,6 @@ def delete_dependency_package( route = self._get_dependency_package_route(filename) response = self.delete(route) response.raise_for_status("Failed to delete dependency file") - return response.data def download_dependency_package( self, @@ -203,7 +202,7 @@ def upload_dependency_package( dst_filename: str, platform_name: Optional[str] = None, progress: Optional[TransferProgress] = None, - ): + ) -> None: """Upload dependency package to server. Args: diff --git a/ayon_api/_api_helpers/events.py b/ayon_api/_api_helpers/events.py index f028d5c9e..8f2267f77 100644 --- a/ayon_api/_api_helpers/events.py +++ b/ayon_api/_api_helpers/events.py @@ -145,7 +145,7 @@ def update_event( payload: Optional[dict[str, Any]] = None, progress: Optional[int] = None, retries: Optional[int] = None, - ): + ) -> None: """Update event data. Args: @@ -198,7 +198,7 @@ def dispatch_event( finished: bool = True, store: bool = True, dependencies: Optional[list[str]] = None, - ): + ) -> RestApiResponse: """Dispatch event to server. Args: @@ -213,8 +213,8 @@ def dispatch_event( be used for simple filtering on listeners. payload (Optional[dict[str, Any]]): Full payload of event data with all details. - finished (Optional[bool]): Mark event as finished on dispatch. - store (Optional[bool]): Store event in event queue for possible + finished (bool): Mark event as finished on dispatch. + store (bool): Store event in event queue for possible future processing otherwise is event send only to active listeners. dependencies (Optional[list[str]]): Deprecated. @@ -256,7 +256,7 @@ def dispatch_event( response.raise_for_status() return response - def delete_event(self, event_id: str): + def delete_event(self, event_id: str) -> None: """Delete event by id. Supported since AYON server 1.6.0. @@ -270,16 +270,15 @@ def delete_event(self, event_id: str): """ response = self.delete(f"events/{event_id}") response.raise_for_status() - return response def enroll_event_job( self, - source_topic: "Union[str, list[str]]", + source_topic: Union[str, list[str]], target_topic: str, sender: str, description: Optional[str] = None, sequential: Optional[bool] = None, - events_filter: Optional["EventFilter"] = None, + events_filter: Optional[EventFilter] = None, max_retries: Optional[int] = None, ignore_older_than: Optional[str] = None, ignore_sender_types: Optional[str] = None, diff --git a/ayon_api/_api_helpers/folders.py b/ayon_api/_api_helpers/folders.py index a0805b73f..fbef4e485 100644 --- a/ayon_api/_api_helpers/folders.py +++ b/ayon_api/_api_helpers/folders.py @@ -27,14 +27,14 @@ class FoldersAPI(BaseServerAPI): def get_rest_folder( self, project_name: str, folder_id: str - ) -> Optional["FolderDict"]: + ) -> Optional[FolderDict]: return self.get_rest_entity_by_id( project_name, "folder", folder_id ) def get_rest_folders( self, project_name: str, include_attrib: bool = False - ) -> list["FlatFolderDict"]: + ) -> list[FlatFolderDict]: """Get simplified flat list of all project folders. Get all project folders in single REST call. This can be faster than @@ -95,7 +95,7 @@ def get_folders_hierarchy( project_name: str, search_string: Optional[str] = None, folder_types: Optional[Iterable[str]] = None - ) -> "ProjectHierarchyDict": + ) -> ProjectHierarchyDict: """Get project hierarchy. All folders in project in hierarchy data structure. @@ -143,7 +143,7 @@ def get_folders_hierarchy( def get_folders_rest( self, project_name: str, include_attrib: bool = False - ) -> list["FlatFolderDict"]: + ) -> list[FlatFolderDict]: """Get simplified flat list of all project folders. Get all project folders in single REST call. This can be faster than @@ -218,7 +218,7 @@ def get_folders( has_links: Optional[bool] = None, fields: Optional[Iterable[str]] = None, own_attributes: bool = False - ) -> Generator["FolderDict", None, None]: + ) -> Generator[FolderDict, None, None]: """Query folders from server. Todos: @@ -348,7 +348,7 @@ def get_folder_by_id( folder_id: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, - ) -> Optional["FolderDict"]: + ) -> Optional[FolderDict]: """Query folder entity by id. Args: @@ -382,7 +382,7 @@ def get_folder_by_path( folder_path: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, - ) -> Optional["FolderDict"]: + ) -> Optional[FolderDict]: """Query folder entity by path. Folder path is a path to folder with all parent names joined by slash. @@ -418,7 +418,7 @@ def get_folder_by_name( folder_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, - ) -> Optional["FolderDict"]: + ) -> Optional[FolderDict]: """Query folder entity by path. Warnings: @@ -565,7 +565,7 @@ def update_folder( status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = NOT_SET, - ): + ) -> None: """Update folder entity on server. Do not pass ``parent_id``, ``label`` amd ``thumbnail_id`` if you don't @@ -621,7 +621,7 @@ def update_folder( def delete_folder( self, project_name: str, folder_id: str, force: bool = False - ): + ) -> None: """Delete folder. Args: diff --git a/ayon_api/_api_helpers/installers.py b/ayon_api/_api_helpers/installers.py index f6d7ec7f5..be2bcaaec 100644 --- a/ayon_api/_api_helpers/installers.py +++ b/ayon_api/_api_helpers/installers.py @@ -1,9 +1,10 @@ from __future__ import annotations - import typing from typing import Optional, Any +import requests + from ayon_api.utils import prepare_query_string, TransferProgress from .base import BaseServerAPI @@ -17,7 +18,7 @@ def get_installers( self, version: Optional[str] = None, platform_name: Optional[str] = None, - ) -> "InstallersInfoDict": + ) -> InstallersInfoDict: """Information about desktop application installers on server. Desktop application installers are helpers to download/update AYON @@ -51,7 +52,7 @@ def create_installer( checksum_algorithm: str, file_size: int, sources: Optional[list[dict[str, Any]]] = None, - ): + ) -> None: """Create new installer information on server. This step will create only metadata. Make sure to upload installer @@ -94,7 +95,9 @@ def create_installer( response = self.post("desktop/installers", **body) response.raise_for_status() - def update_installer(self, filename: str, sources: list[dict[str, Any]]): + def update_installer( + self, filename: str, sources: list[dict[str, Any]] + ) -> None: """Update installer information on server. Args: @@ -109,7 +112,7 @@ def update_installer(self, filename: str, sources: list[dict[str, Any]]): ) response.raise_for_status() - def delete_installer(self, filename: str): + def delete_installer(self, filename: str) -> None: """Delete installer from server. Args: @@ -125,7 +128,7 @@ def download_installer( dst_filepath: str, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None - ): + ) -> TransferProgress: """Download installer file from server. Args: @@ -135,8 +138,11 @@ def download_installer( progress (Optional[TransferProgress]): Object that gives ability to track download progress. + Returns: + TransferProgress: Progress object. + """ - self.download_file( + return self.download_file( f"desktop/installers/{filename}", dst_filepath, chunk_size=chunk_size, @@ -148,7 +154,7 @@ def upload_installer( src_filepath: str, dst_filename: str, progress: Optional[TransferProgress] = None, - ): + ) -> requests.Response: """Upload installer file to server. Args: diff --git a/ayon_api/_api_helpers/links.py b/ayon_api/_api_helpers/links.py index f0a5f6580..6c23b504c 100644 --- a/ayon_api/_api_helpers/links.py +++ b/ayon_api/_api_helpers/links.py @@ -15,8 +15,12 @@ from .base import BaseServerAPI if typing.TYPE_CHECKING: + from typing import TypedDict from ayon_api.typing import LinkDirection + class CreateLinkData(TypedDict): + id: str + class LinksAPI(BaseServerAPI): def get_full_link_type_name( @@ -106,7 +110,7 @@ def create_link_type( input_type: str, output_type: str, data: Optional[dict[str, Any]] = None, - ): + ) -> None: """Create or update link type on server. Warning: @@ -140,7 +144,7 @@ def delete_link_type( link_type_name: str, input_type: str, output_type: str, - ): + ) -> None: """Remove link type from project. Args: @@ -168,7 +172,7 @@ def make_sure_link_type_exists( input_type: str, output_type: str, data: Optional[dict[str, Any]] = None, - ): + ) -> None: """Make sure link type exists on a project. Args: @@ -199,7 +203,7 @@ def create_link( output_id: str, output_type: str, link_name: Optional[str] = None, - ): + ) -> CreateLinkData: """Create link between 2 entities. Link has a type which must already exists on a project. @@ -221,7 +225,7 @@ def create_link( Available from server version '1.0.0-rc.6'. Returns: - dict[str, str]: Information about link. + CreateLinkData: Information about link. Raises: HTTPRequestError: Server error happened. @@ -244,7 +248,7 @@ def create_link( response.raise_for_status() return response.data - def delete_link(self, project_name: str, link_id: str): + def delete_link(self, project_name: str, link_id: str) -> None: """Remove link by id. Args: @@ -260,56 +264,13 @@ def delete_link(self, project_name: str, link_id: str): ) response.raise_for_status() - def _prepare_link_filters( - self, - filters: dict[str, Any], - link_types: Optional[Iterable[str], None], - link_direction: Optional["LinkDirection"], - link_names: Optional[Iterable[str]], - link_name_regex: Optional[str], - ) -> bool: - """Add links filters for GraphQl queries. - - Args: - filters (dict[str, Any]): Object where filters will be added. - link_types (Optional[Iterable[str]]): Link types filters. - link_direction (Optional[Literal["in", "out"]]): Direction of - link "in", "out" or 'None' for both. - link_names (Optional[Iterable[str]]): Link name filters. - link_name_regex (Optional[str]): Regex filter for link name. - - Returns: - bool: Links are valid, and query from server can happen. - - """ - if link_types is not None: - link_types = set(link_types) - if not link_types: - return False - filters["linkTypes"] = list(link_types) - - if link_names is not None: - link_names = set(link_names) - if not link_names: - return False - filters["linkNames"] = list(link_names) - - if link_direction is not None: - if link_direction not in ("in", "out"): - return False - filters["linkDirection"] = link_direction - - if link_name_regex is not None: - filters["linkNameRegex"] = link_name_regex - return True - def get_entities_links( self, project_name: str, entity_type: str, entity_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, link_names: Optional[Iterable[str]] = None, link_name_regex: Optional[str] = None, ) -> dict[str, list[dict[str, Any]]]: @@ -411,7 +372,7 @@ def get_folders_links( project_name: str, folder_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query folders links from server. @@ -436,7 +397,7 @@ def get_folder_links( project_name: str, folder_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query folder links from server. @@ -460,7 +421,7 @@ def get_tasks_links( project_name: str, task_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query tasks links from server. @@ -485,7 +446,7 @@ def get_task_links( project_name: str, task_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query task links from server. @@ -509,7 +470,7 @@ def get_products_links( project_name: str, product_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query products links from server. @@ -534,7 +495,7 @@ def get_product_links( project_name: str, product_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query product links from server. @@ -558,7 +519,7 @@ def get_versions_links( project_name: str, version_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query versions links from server. @@ -583,7 +544,7 @@ def get_version_links( project_name: str, version_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> list[dict[str, Any]]: """Query version links from server. @@ -607,7 +568,7 @@ def get_representations_links( project_name: str, representation_ids: Optional[Iterable[str]] = None, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None, + link_direction: Optional[LinkDirection] = None, ) -> dict[str, list[dict[str, Any]]]: """Query representations links from server. @@ -636,7 +597,7 @@ def get_representation_links( project_name: str, representation_id: str, link_types: Optional[Iterable[str]] = None, - link_direction: Optional["LinkDirection"] = None + link_direction: Optional[LinkDirection] = None ) -> list[dict[str, Any]]: """Query representation links from server. @@ -655,3 +616,46 @@ def get_representation_links( return self.get_representations_links( project_name, [representation_id], link_types, link_direction )[representation_id] + + def _prepare_link_filters( + self, + filters: dict[str, Any], + link_types: Optional[Iterable[str], None], + link_direction: Optional[LinkDirection], + link_names: Optional[Iterable[str]], + link_name_regex: Optional[str], + ) -> bool: + """Add links filters for GraphQl queries. + + Args: + filters (dict[str, Any]): Object where filters will be added. + link_types (Optional[Iterable[str]]): Link types filters. + link_direction (Optional[Literal["in", "out"]]): Direction of + link "in", "out" or 'None' for both. + link_names (Optional[Iterable[str]]): Link name filters. + link_name_regex (Optional[str]): Regex filter for link name. + + Returns: + bool: Links are valid, and query from server can happen. + + """ + if link_types is not None: + link_types = set(link_types) + if not link_types: + return False + filters["linkTypes"] = list(link_types) + + if link_names is not None: + link_names = set(link_names) + if not link_names: + return False + filters["linkNames"] = list(link_names) + + if link_direction is not None: + if link_direction not in ("in", "out"): + return False + filters["linkDirection"] = link_direction + + if link_name_regex is not None: + filters["linkNameRegex"] = link_name_regex + return True diff --git a/ayon_api/_api_helpers/lists.py b/ayon_api/_api_helpers/lists.py index f796723fd..b6bd79265 100644 --- a/ayon_api/_api_helpers/lists.py +++ b/ayon_api/_api_helpers/lists.py @@ -113,7 +113,7 @@ def get_entity_list_by_id( def create_entity_list( self, project_name: str, - entity_type: "EntityListEntityType", + entity_type: EntityListEntityType, label: str, *, list_type: Optional[str] = None, @@ -237,7 +237,7 @@ def delete_entity_list(self, project_name: str, list_id: str) -> None: def get_entity_list_attribute_definitions( self, project_name: str, list_id: str - ) -> list["EntityListAttributeDefinitionDict"]: + ) -> list[EntityListAttributeDefinitionDict]: """Get attribute definitioins on entity list. Args: @@ -259,7 +259,7 @@ def set_entity_list_attribute_definitions( self, project_name: str, list_id: str, - attribute_definitions: list["EntityListAttributeDefinitionDict"], + attribute_definitions: list[EntityListAttributeDefinitionDict], ) -> None: """Set attribute definitioins on entity list. @@ -332,7 +332,7 @@ def update_entity_list_items( project_name: str, list_id: str, items: list[dict[str, Any]], - mode: "EntityListItemMode", + mode: EntityListItemMode, ) -> None: """Update items in entity list. diff --git a/ayon_api/_api_helpers/products.py b/ayon_api/_api_helpers/products.py index 5ce78f97c..13fdb9b80 100644 --- a/ayon_api/_api_helpers/products.py +++ b/ayon_api/_api_helpers/products.py @@ -23,8 +23,10 @@ class ProductsAPI(BaseServerAPI): def get_rest_product( self, project_name: str, product_id: str - ) -> Optional["ProductDict"]: - return self.get_rest_entity_by_id(project_name, "product", product_id) + ) -> Optional[ProductDict]: + return self.get_rest_entity_by_id( + project_name, "product", product_id + ) def get_products( self, @@ -41,7 +43,7 @@ def get_products( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER - ) -> Generator["ProductDict", None, None]: + ) -> Generator[ProductDict, None, None]: """Query products from server. Todos: @@ -197,7 +199,7 @@ def get_product_by_id( product_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER - ) -> Optional["ProductDict"]: + ) -> Optional[ProductDict]: """Query product entity by id. Args: @@ -232,7 +234,7 @@ def get_product_by_name( folder_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER - ) -> Optional["ProductDict"]: + ) -> Optional[ProductDict]: """Query product entity by name and folder id. Args: @@ -264,7 +266,7 @@ def get_product_by_name( def get_product_types( self, fields: Optional[Iterable[str]] = None - ) -> list["ProductTypeDict"]: + ) -> list[ProductTypeDict]: """Types of products. This is server wide information. Product types have 'name', 'icon' and @@ -288,7 +290,7 @@ def get_product_types( def get_project_product_types( self, project_name: str, fields: Optional[Iterable[str]] = None - ) -> list["ProductTypeDict"]: + ) -> list[ProductTypeDict]: """DEPRECATED Types of products available in a project. Filter only product types available in a project. @@ -434,7 +436,7 @@ def update_product( tags: Optional[Iterable[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, - ): + ) -> None: """Update product entity on server. Update of ``data`` will override existing value on folder entity. @@ -475,7 +477,7 @@ def update_product( ) response.raise_for_status() - def delete_product(self, project_name: str, product_id: str): + def delete_product(self, project_name: str, product_id: str) -> None: """Delete product. Args: @@ -491,9 +493,9 @@ def delete_product(self, project_name: str, product_id: str): def _filter_product( self, project_name: str, - product: "ProductDict", + product: ProductDict, active: Optional[bool], - ) -> Optional["ProductDict"]: + ) -> Optional[ProductDict]: if active is not None and product["active"] is not active: return None diff --git a/ayon_api/_api_helpers/projects.py b/ayon_api/_api_helpers/projects.py index c54340458..09fe66d98 100644 --- a/ayon_api/_api_helpers/projects.py +++ b/ayon_api/_api_helpers/projects.py @@ -17,7 +17,7 @@ class ProjectsAPI(BaseServerAPI): - def get_project_anatomy_presets(self) -> list["AnatomyPresetDict"]: + def get_project_anatomy_presets(self) -> list[AnatomyPresetDict]: """Anatomy presets available on server. Content has basic information about presets. Example output:: @@ -60,7 +60,7 @@ def get_default_anatomy_preset_name(self) -> str: def get_project_anatomy_preset( self, preset_name: Optional[str] = None - ) -> "AnatomyPresetDict": + ) -> AnatomyPresetDict: """Anatomy preset values by name. Get anatomy preset values by preset name. Primary preset is returned @@ -83,7 +83,7 @@ def get_project_anatomy_preset( result.raise_for_status() return result.data - def get_built_in_anatomy_preset(self) -> "AnatomyPresetDict": + def get_built_in_anatomy_preset(self) -> AnatomyPresetDict: """Get built-in anatomy preset. Returns: @@ -96,7 +96,7 @@ def get_built_in_anatomy_preset(self) -> "AnatomyPresetDict": preset_name = "_" return self.get_project_anatomy_preset(preset_name) - def get_build_in_anatomy_preset(self) -> "AnatomyPresetDict": + def get_build_in_anatomy_preset(self) -> AnatomyPresetDict: warnings.warn( ( "Used deprecated 'get_build_in_anatomy_preset' use" @@ -108,7 +108,7 @@ def get_build_in_anatomy_preset(self) -> "AnatomyPresetDict": def get_rest_project( self, project_name: str - ) -> Optional["ProjectDict"]: + ) -> Optional[ProjectDict]: """Query project by name. This call returns project with anatomy data. @@ -136,7 +136,7 @@ def get_rest_projects( self, active: Optional[bool] = True, library: Optional[bool] = None, - ) -> Generator["ProjectDict", None, None]: + ) -> Generator[ProjectDict, None, None]: """Query available project entities. User must be logged in. @@ -198,7 +198,7 @@ def get_projects( library: Optional[bool] = None, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, - ) -> Generator["ProjectDict", None, None]: + ) -> Generator[ProjectDict, None, None]: """Get projects. Args: @@ -244,7 +244,7 @@ def get_project( project_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, - ) -> Optional["ProjectDict"]: + ) -> Optional[ProjectDict]: """Get project. Args: @@ -287,7 +287,7 @@ def create_project( project_code: str, library_project: bool = False, preset_name: Optional[str] = None, - ) -> "ProjectDict": + ) -> ProjectDict: """Create project using AYON settings. This project creation function is not validating project entity on @@ -359,7 +359,7 @@ def update_project( active: Optional[bool] = None, project_code: Optional[str] = None, **changes - ): + ) -> None: """Update project entity on server. Args: @@ -669,7 +669,7 @@ def _get_graphql_projects( fields: set[str], own_attributes: bool, project_name: Optional[str] = None - ): + ) -> Generator[ProjectDict, None, None]: if active is not None: fields.add("active") diff --git a/ayon_api/_api_helpers/representations.py b/ayon_api/_api_helpers/representations.py index e46353d09..2eb9814cb 100644 --- a/ayon_api/_api_helpers/representations.py +++ b/ayon_api/_api_helpers/representations.py @@ -26,7 +26,7 @@ class RepresentationsAPI(BaseServerAPI): def get_rest_representation( self, project_name: str, representation_id: str - ) -> Optional["RepresentationDict"]: + ) -> Optional[RepresentationDict]: return self.get_rest_entity_by_id( project_name, "representation", representation_id ) @@ -44,7 +44,7 @@ def get_representations( has_links: Optional[str] = None, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Generator["RepresentationDict", None, None]: + ) -> Generator[RepresentationDict, None, None]: """Get representation entities based on passed filters from server. .. todo:: @@ -182,7 +182,7 @@ def get_representation_by_id( representation_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Optional["RepresentationDict"]: + ) -> Optional[RepresentationDict]: """Query representation entity from server based on id filter. Args: @@ -216,7 +216,7 @@ def get_representation_by_name( version_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Optional["RepresentationDict"]: + ) -> Optional[RepresentationDict]: """Query representation entity by name and version id. Args: @@ -474,7 +474,7 @@ def get_representation_parents( folder_fields: Optional[Iterable[str]] = None, product_fields: Optional[Iterable[str]] = None, version_fields: Optional[Iterable[str]] = None, - ) -> Optional["RepresentationParents"]: + ) -> Optional[RepresentationParents]: """Find representation parents by representation id. Representation parent entities up to project. @@ -668,7 +668,7 @@ def update_representation( tags: Optional[list[str]] = None, status: Optional[str] = None, active: Optional[bool] = None, - ): + ) -> None: """Update representation entity on server. Update of ``data`` will override existing value on folder entity. @@ -714,7 +714,7 @@ def update_representation( def delete_representation( self, project_name: str, representation_id: str - ): + ) -> None: """Delete representation. Args: @@ -728,8 +728,8 @@ def delete_representation( response.raise_for_status() def _representation_conversion( - self, representation: "RepresentationDict" - ): + self, representation: RepresentationDict + ) -> None: if "context" in representation: orig_context = representation["context"] context = {} diff --git a/ayon_api/_api_helpers/secrets.py b/ayon_api/_api_helpers/secrets.py index f02649fef..dbc9084bb 100644 --- a/ayon_api/_api_helpers/secrets.py +++ b/ayon_api/_api_helpers/secrets.py @@ -8,7 +8,7 @@ class SecretsAPI(BaseServerAPI): - def get_secrets(self) -> list["SecretDict"]: + def get_secrets(self) -> list[SecretDict]: """Get all secrets. Example output:: @@ -32,7 +32,7 @@ def get_secrets(self) -> list["SecretDict"]: response.raise_for_status() return response.data - def get_secret(self, secret_name: str) -> "SecretDict": + def get_secret(self, secret_name: str) -> SecretDict: """Get secret by name. Example output:: diff --git a/ayon_api/_api_helpers/tasks.py b/ayon_api/_api_helpers/tasks.py index 7be323c30..aa984032b 100644 --- a/ayon_api/_api_helpers/tasks.py +++ b/ayon_api/_api_helpers/tasks.py @@ -23,7 +23,7 @@ class TasksAPI(BaseServerAPI): def get_rest_task( self, project_name: str, task_id: str - ) -> Optional["TaskDict"]: + ) -> Optional[TaskDict]: return self.get_rest_entity_by_id(project_name, "task", task_id) def get_tasks( @@ -40,7 +40,7 @@ def get_tasks( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes: bool = False - ) -> Generator["TaskDict", None, None]: + ) -> Generator[TaskDict, None, None]: """Query task entities from server. Args: @@ -122,7 +122,7 @@ def get_task_by_name( task_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False, - ) -> Optional["TaskDict"]: + ) -> Optional[TaskDict]: """Query task entity by name and folder id. Args: @@ -156,7 +156,7 @@ def get_task_by_id( task_id: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False - ) -> Optional["TaskDict"]: + ) -> Optional[TaskDict]: """Query task entity by id. Args: @@ -195,7 +195,7 @@ def get_tasks_by_folder_paths( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes: bool = False - ) -> dict[str, list["TaskDict"]]: + ) -> dict[str, list[TaskDict]]: """Query task entities from server by folder paths. Args: @@ -289,7 +289,7 @@ def get_tasks_by_folder_path( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes: bool = False - ) -> list["TaskDict"]: + ) -> list[TaskDict]: """Query task entities from server by folder path. Args: @@ -337,7 +337,7 @@ def get_task_by_folder_path( task_name: str, fields: Optional[Iterable[str]] = None, own_attributes: bool = False - ) -> Optional["TaskDict"]: + ) -> Optional[TaskDict]: """Query task entity by folder path and task name. Args: @@ -445,7 +445,7 @@ def update_task( status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = NOT_SET, - ): + ) -> None: """Update task entity on server. Do not pass ``label`` amd ``thumbnail_id`` if you don't @@ -501,7 +501,7 @@ def update_task( ) response.raise_for_status() - def delete_task(self, project_name: str, task_id: str): + def delete_task(self, project_name: str, task_id: str) -> None: """Delete task. Args: diff --git a/ayon_api/_api_helpers/thumbnails.py b/ayon_api/_api_helpers/thumbnails.py index 4e2242f2e..e4b1c56d1 100644 --- a/ayon_api/_api_helpers/thumbnails.py +++ b/ayon_api/_api_helpers/thumbnails.py @@ -261,7 +261,7 @@ def create_thumbnail( def update_thumbnail( self, project_name: str, thumbnail_id: str, src_filepath: str - ): + ) -> None: """Change thumbnail content by id. Update can be also used to create new thumbnail. diff --git a/ayon_api/_api_helpers/versions.py b/ayon_api/_api_helpers/versions.py index 1302ed84b..fe8a02469 100644 --- a/ayon_api/_api_helpers/versions.py +++ b/ayon_api/_api_helpers/versions.py @@ -21,7 +21,7 @@ class VersionsAPI(BaseServerAPI): def get_rest_version( self, project_name: str, version_id: str - ) -> Optional["VersionDict"]: + ) -> Optional[VersionDict]: return self.get_rest_entity_by_id(project_name, "version", version_id) def get_versions( @@ -39,7 +39,7 @@ def get_versions( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER - ) -> Generator["VersionDict", None, None]: + ) -> Generator[VersionDict, None, None]: """Get version entities based on passed filters from server. Args: @@ -164,7 +164,7 @@ def get_version_by_id( version_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER - ) -> Optional["VersionDict"]: + ) -> Optional[VersionDict]: """Query version entity by id. Args: @@ -200,7 +200,7 @@ def get_version_by_name( product_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER - ) -> Optional["VersionDict"]: + ) -> Optional[VersionDict]: """Query version entity by version and product id. Args: @@ -236,7 +236,7 @@ def get_hero_version_by_id( version_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER - ) -> Optional["VersionDict"]: + ) -> Optional[VersionDict]: """Query hero version entity by id. Args: @@ -269,7 +269,7 @@ def get_hero_version_by_product_id( product_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER - ) -> Optional["VersionDict"]: + ) -> Optional[VersionDict]: """Query hero version entity by product id. Only one hero version is available on a product. @@ -306,7 +306,7 @@ def get_hero_versions( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Generator["VersionDict", None, None]: + ) -> Generator[VersionDict, None, None]: """Query hero versions by multiple filters. Only one hero version is available on a product. @@ -346,7 +346,7 @@ def get_last_versions( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> dict[str, Optional["VersionDict"]]: + ) -> dict[str, Optional[VersionDict]]: """Query last version entities by product ids. Args: @@ -391,7 +391,7 @@ def get_last_version_by_product_id( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Optional["VersionDict"]: + ) -> Optional[VersionDict]: """Query last version entity by product id. Args: @@ -429,7 +429,7 @@ def get_last_version_by_product_name( active: Optional[bool] = True, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Optional["VersionDict"]: + ) -> Optional[VersionDict]: """Query last version entity by product name and folder id. Args: @@ -572,7 +572,7 @@ def update_version( status: Optional[str] = None, active: Optional[bool] = None, thumbnail_id: Optional[str] = NOT_SET, - ): + ) -> None: """Update version entity on server. Do not pass ``task_id`` amd ``thumbnail_id`` if you don't @@ -626,7 +626,7 @@ def update_version( ) response.raise_for_status() - def delete_version(self, project_name: str, version_id: str): + def delete_version(self, project_name: str, version_id: str) -> None: """Delete version. Args: diff --git a/ayon_api/_api_helpers/workfiles.py b/ayon_api/_api_helpers/workfiles.py index 1be1c37a5..e27aab3c1 100644 --- a/ayon_api/_api_helpers/workfiles.py +++ b/ayon_api/_api_helpers/workfiles.py @@ -25,7 +25,7 @@ def get_workfiles_info( has_links: Optional[str]=None, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Generator["WorkfileInfoDict", None, None]: + ) -> Generator[WorkfileInfoDict, None, None]: """Workfile info entities by passed filters. Args: @@ -121,7 +121,7 @@ def get_workfile_info( path: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Optional["WorkfileInfoDict"]: + ) -> Optional[WorkfileInfoDict]: """Workfile info entity by task id and workfile path. Args: @@ -157,7 +157,7 @@ def get_workfile_info_by_id( workfile_id: str, fields: Optional[Iterable[str]] = None, own_attributes=_PLACEHOLDER, - ) -> Optional["WorkfileInfoDict"]: + ) -> Optional[WorkfileInfoDict]: """Workfile info entity by id. Args: From 5555eb8c0c50af3460d90a412f79962b86effd0c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:48:23 +0200 Subject: [PATCH 48/53] added log property do base --- ayon_api/_api_helpers/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ayon_api/_api_helpers/base.py b/ayon_api/_api_helpers/base.py index 7c792d42d..f49b10a9b 100644 --- a/ayon_api/_api_helpers/base.py +++ b/ayon_api/_api_helpers/base.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging import typing from typing import Optional, Any, Iterable @@ -18,6 +19,10 @@ class BaseServerAPI: + @property + def log(self) -> logging.Logger: + raise NotImplementedError() + def get_server_version(self) -> str: raise NotImplementedError() From 460e0ce888c1854f06ee70a5a325e99f95975a42 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:48:37 +0200 Subject: [PATCH 49/53] use 'get_server_version_tuple' --- ayon_api/_api_helpers/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ayon_api/_api_helpers/events.py b/ayon_api/_api_helpers/events.py index 8f2267f77..c4be8e8f7 100644 --- a/ayon_api/_api_helpers/events.py +++ b/ayon_api/_api_helpers/events.py @@ -118,7 +118,7 @@ def get_events( if not fields: fields = self.get_default_fields_for_type("event") - major, minor, patch, _, _ = self.server_version_tuple + major, minor, patch, _, _ = self.get_server_version_tuple() use_states = (major, minor, patch) <= (1, 5, 6) query = events_graphql_query(set(fields), order, use_states) From 2d5fb188cdcc70a176b96bdd826dd68704e3be10 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:48:51 +0200 Subject: [PATCH 50/53] added typehints to save and delete secret --- ayon_api/_api_helpers/secrets.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ayon_api/_api_helpers/secrets.py b/ayon_api/_api_helpers/secrets.py index dbc9084bb..188d696c9 100644 --- a/ayon_api/_api_helpers/secrets.py +++ b/ayon_api/_api_helpers/secrets.py @@ -53,7 +53,7 @@ def get_secret(self, secret_name: str) -> SecretDict: response.raise_for_status() return response.data - def save_secret(self, secret_name: str, secret_value: str): + def save_secret(self, secret_name: str, secret_value: str) -> None: """Save secret. This endpoint can create and update secret. @@ -69,9 +69,8 @@ def save_secret(self, secret_name: str, secret_value: str): value=secret_value, ) response.raise_for_status() - return response.data - def delete_secret(self, secret_name: str): + def delete_secret(self, secret_name: str) -> None: """Delete secret by name. Args: @@ -80,4 +79,3 @@ def delete_secret(self, secret_name: str): """ response = self.delete(f"secrets/{secret_name}") response.raise_for_status() - return response.data From 7d5d49e67c0ae2588e2e6f30206c557f210b5b8f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:49:14 +0200 Subject: [PATCH 51/53] better event typehints --- ayon_api/_api_helpers/events.py | 16 ++++++++-------- ayon_api/typing.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/ayon_api/_api_helpers/events.py b/ayon_api/_api_helpers/events.py index c4be8e8f7..c22ff6200 100644 --- a/ayon_api/_api_helpers/events.py +++ b/ayon_api/_api_helpers/events.py @@ -4,7 +4,7 @@ import typing from typing import Optional, Any, Iterable, Generator -from ayon_api.utils import SortOrder, prepare_list_filters +from ayon_api.utils import SortOrder, prepare_list_filters, RestApiResponse from ayon_api.graphql_queries import events_graphql_query from .base import BaseServerAPI @@ -12,7 +12,7 @@ if typing.TYPE_CHECKING: from typing import Union - from ayon_api.typing import EventFilter + from ayon_api.typing import EventFilter, EventStatus, EnrollEventData class EventsAPI(BaseServerAPI): @@ -38,7 +38,7 @@ def get_events( topics: Optional[Iterable[str]] = None, event_ids: Optional[Iterable[str]] = None, project_names: Optional[Iterable[str]] = None, - statuses: Optional[Iterable[str]] = None, + statuses: Optional[Iterable[EventStatus]] = None, users: Optional[Iterable[str]] = None, include_logs: Optional[bool] = None, has_children: Optional[bool] = None, @@ -59,7 +59,7 @@ def get_events( event_ids (Optional[Iterable[str]]): Event ids. project_names (Optional[Iterable[str]]): Project on which event happened. - statuses (Optional[Iterable[str]]): Filtering by statuses. + statuses (Optional[Iterable[EventStatus]]): Filtering by statuses. users (Optional[Iterable[str]]): Filtering by users who created/triggered an event. include_logs (Optional[bool]): Query also log events. @@ -139,7 +139,7 @@ def update_event( sender: Optional[str] = None, project_name: Optional[str] = None, username: Optional[str] = None, - status: Optional[str] = None, + status: Optional[EventStatus] = None, description: Optional[str] = None, summary: Optional[dict[str, Any]] = None, payload: Optional[dict[str, Any]] = None, @@ -153,7 +153,7 @@ def update_event( sender (Optional[str]): New sender of event. project_name (Optional[str]): New project name. username (Optional[str]): New username. - status (Optional[str]): New event status. Enum: "pending", + status (Optional[EventStatus]): New event status. Enum: "pending", "in_progress", "finished", "failed", "aborted", "restarted" description (Optional[str]): New description. summary (Optional[dict[str, Any]]): New summary. @@ -282,7 +282,7 @@ def enroll_event_job( max_retries: Optional[int] = None, ignore_older_than: Optional[str] = None, ignore_sender_types: Optional[str] = None, - ): + ) -> Optional[EnrollEventData]: """Enroll job based on events. Enroll will find first unprocessed event with 'source_topic' and will @@ -337,7 +337,7 @@ def enroll_event_job( by given sender types. Returns: - Optional[dict[str, Any]]: None if there is no event matching + Optional[EnrollEventData]: None if there is no event matching filters. Created event with 'target_topic'. """ diff --git a/ayon_api/typing.py b/ayon_api/typing.py index 9f45a5120..aa31065ea 100644 --- a/ayon_api/typing.py +++ b/ayon_api/typing.py @@ -342,6 +342,22 @@ class SecretDict(TypedDict): ActivityDict, ] +EventStatus = Literal[ + "pending", + "in_progress", + "finished", + "failed", + "aborted", + "restarted", +] + + +class EnrollEventData(TypedDict): + id: str + dependsOn: str + hash: str + status: EventStatus + class FlatFolderDict(TypedDict): id: str From 7b362b73f27aecf382100617902b7b78f055e3cb Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:52:16 +0200 Subject: [PATCH 52/53] added 'create_event' function that returns event id --- ayon_api/__init__.py | 2 ++ ayon_api/_api.py | 56 +++++++++++++++++++++++++++++++++ ayon_api/_api_helpers/events.py | 56 +++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 1d28e6736..a51f12bc4 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -134,6 +134,7 @@ get_events, update_event, dispatch_event, + create_event, delete_event, enroll_event_job, get_attributes_schema, @@ -398,6 +399,7 @@ "get_events", "update_event", "dispatch_event", + "create_event", "delete_event", "enroll_event_job", "get_attributes_schema", diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 24a1bda94..435dd3a0e 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -3104,6 +3104,62 @@ def dispatch_event( ) +def create_event( + topic: str, + sender: Optional[str] = None, + event_hash: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + depends_on: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + finished: bool = True, + store: bool = True, + dependencies: Optional[list[str]] = None, +) -> str: + """Dispatch event to server. + + Args: + topic (str): Event topic used for filtering of listeners. + sender (Optional[str]): Sender of event. + event_hash (Optional[str]): Event hash. + project_name (Optional[str]): Project name. + depends_on (Optional[str]): Add dependency to another event. + username (Optional[str]): Username which triggered event. + description (Optional[str]): Description of event. + summary (Optional[dict[str, Any]]): Summary of event that can + be used for simple filtering on listeners. + payload (Optional[dict[str, Any]]): Full payload of event data with + all details. + finished (bool): Mark event as finished on dispatch. + store (bool): Store event in event queue for possible + future processing otherwise is event send only + to active listeners. + dependencies (Optional[list[str]]): Deprecated. + List of event id dependencies. + + Returns: + str: Event id. + + """ + con = get_server_api_connection() + return con.create_event( + topic=topic, + sender=sender, + event_hash=event_hash, + project_name=project_name, + username=username, + depends_on=depends_on, + description=description, + summary=summary, + payload=payload, + finished=finished, + store=store, + dependencies=dependencies, + ) + + def delete_event( event_id: str, ) -> None: diff --git a/ayon_api/_api_helpers/events.py b/ayon_api/_api_helpers/events.py index c22ff6200..19e12c6b8 100644 --- a/ayon_api/_api_helpers/events.py +++ b/ayon_api/_api_helpers/events.py @@ -256,6 +256,62 @@ def dispatch_event( response.raise_for_status() return response + def create_event( + self, + topic: str, + sender: Optional[str] = None, + event_hash: Optional[str] = None, + project_name: Optional[str] = None, + username: Optional[str] = None, + depends_on: Optional[str] = None, + description: Optional[str] = None, + summary: Optional[dict[str, Any]] = None, + payload: Optional[dict[str, Any]] = None, + finished: bool = True, + store: bool = True, + dependencies: Optional[list[str]] = None, + ) -> str: + """Dispatch event to server. + + Args: + topic (str): Event topic used for filtering of listeners. + sender (Optional[str]): Sender of event. + event_hash (Optional[str]): Event hash. + project_name (Optional[str]): Project name. + depends_on (Optional[str]): Add dependency to another event. + username (Optional[str]): Username which triggered event. + description (Optional[str]): Description of event. + summary (Optional[dict[str, Any]]): Summary of event that can + be used for simple filtering on listeners. + payload (Optional[dict[str, Any]]): Full payload of event data with + all details. + finished (bool): Mark event as finished on dispatch. + store (bool): Store event in event queue for possible + future processing otherwise is event send only + to active listeners. + dependencies (Optional[list[str]]): Deprecated. + List of event id dependencies. + + Returns: + str: Event id. + + """ + result = self.dispatch_event( + topic, + sender, + event_hash, + project_name, + username, + depends_on, + description, + summary, + payload, + finished, + store, + dependencies, + ) + return result.data["id"] + def delete_event(self, event_id: str) -> None: """Delete event by id. From d7326ad6803a37f11b5aaebc9e3deebd748a28d8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:57:25 +0200 Subject: [PATCH 53/53] minor typehint fixes --- automated_api.py | 8 ++++---- ayon_api/_api.py | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/automated_api.py b/automated_api.py index db9348686..9565717bd 100644 --- a/automated_api.py +++ b/automated_api.py @@ -136,7 +136,7 @@ def _find_obj(obj_full, api_globals): def _get_typehint(annotation, api_globals): if isinstance(annotation, str): - annotation = annotation.replace("'", '"') + annotation = annotation.replace("'", "") if inspect.isclass(annotation): module_name = str(annotation.__module__) @@ -148,7 +148,7 @@ def _get_typehint(annotation, api_globals): return obj_name print("Unknown typehint:", full_name) - return f'"{full_name}"' + return full_name typehint = ( str(annotation) @@ -213,12 +213,12 @@ def _get_typehint(annotation, api_globals): _typehing_parents.append(parent) if _typehing_parents: - typehint = f'{_typehint}' + typehint = _typehint for parent in reversed(_typehing_parents): typehint = f"{parent}[{typehint}]" return typehint - return f'{typehint}' + return typehint def _get_param_typehint(param, api_globals): diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 435dd3a0e..500955995 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -32,6 +32,7 @@ get_default_settings_variant as _get_default_settings_variant, RepresentationParents, RepresentationHierarchy, + RestApiResponse, ) from .server_api import ( ServerAPI, @@ -49,6 +50,8 @@ EntityListItemMode, LinkDirection, EventFilter, + EventStatus, + EnrollEventData, AttributeScope, AttributeSchemaDataDict, AttributeSchemaDict, @@ -80,6 +83,7 @@ StreamType, EntityListAttributeDefinitionDict, ) + from ._api_helpers.links import CreateLinkData class GlobalServerAPI(ServerAPI): @@ -700,7 +704,7 @@ def get_server_version() -> str: return con.get_server_version() -def get_server_version_tuple() -> "ServerVersion": +def get_server_version_tuple() -> ServerVersion: """Get server version as tuple. Version should match semantic version (https://semver.org/). @@ -908,7 +912,7 @@ def delete( def download_file_to_stream( endpoint: str, - stream: "StreamType", + stream: StreamType, chunk_size: Optional[int] = None, progress: Optional[TransferProgress] = None, ) -> TransferProgress: @@ -981,7 +985,7 @@ def download_file( def upload_file_from_stream( endpoint: str, - stream: "StreamType", + stream: StreamType, progress: Optional[TransferProgress] = None, request_type: Optional[RequestType] = None, **kwargs, @@ -1188,7 +1192,7 @@ def get_rest_entity_by_id( project_name: str, entity_type: str, entity_id: str, -) -> Optional["AnyEntityDict"]: +) -> Optional[AnyEntityDict]: """Get entity using REST on a project by its id. Args: