From a09358d3ecac99acadf669918318afbaf17a4707 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:14:32 +0200 Subject: [PATCH 1/7] implemented actions functionality --- ayon_api/_actions.py | 300 +++++++++++++++++++++++++++++++++++++++++ ayon_api/server_api.py | 3 +- ayon_api/typing.py | 119 ++++++++++++++++ 3 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 ayon_api/_actions.py diff --git a/ayon_api/_actions.py b/ayon_api/_actions.py new file mode 100644 index 000000000..39b554e6f --- /dev/null +++ b/ayon_api/_actions.py @@ -0,0 +1,300 @@ +import typing +from typing import Optional, Dict, List, Any + +from .utils import prepare_query_string +from ._base import _BaseServerAPI + +if typing.TYPE_CHECKING: + from .typing import ( + ActionEntityTypes, + ActionManifestDict, + ActionTriggerResponse, + ActionTakeResponse, + ActionConfigResponse, + ActionModeType, + ) + + +class _ActionsAPI(_BaseServerAPI): + """Implementation of actions API for ServerAPI.""" + 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, + *, + 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. ('simple', 'dynamic', 'all') + + Returns: + List[ActionManifestDict]: List of action manifests. + + """ + if variant is None: + variant = self.get_default_settings_variant() + query_data = {"variant": variant} + if mode: + query_data["mode"] = mode + query = prepare_query_string(query_data) + kwargs = { + key: value + for key, value in ( + ("projectName", project_name), + ("entityType", entity_type), + ("entityIds", entity_ids), + ("entitySubtypes", entity_subtypes), + ("formData", form_data), + ) + if value is not None + } + response = self.post(f"actions/list{query}", **kwargs) + response.raise_for_status() + return response.data["actions"] + + def trigger_action( + self, + 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. + + """ + if variant is None: + variant = self.get_default_settings_variant() + query_data = { + "addonName": addon_name, + "addonVersion": addon_version, + "identifier": identifier, + "variant": variant, + } + query = prepare_query_string(query_data) + + kwargs = { + key: value + for key, value in ( + ("projectName", project_name), + ("entityType", entity_type), + ("entityIds", entity_ids), + ("entitySubtypes", entity_subtypes), + ("formData", form_data), + ) + if value is not None + } + + response = self.post(f"actions/execute{query}", **kwargs) + response.raise_for_status() + return response.data + + def get_action_config( + self, + 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. + + """ + return self._send_config_request( + identifier, + addon_name, + addon_version, + None, + project_name, + entity_type, + entity_ids, + entity_subtypes, + form_data, + variant, + ) + + def set_action_config( + self, + 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. + + """ + return self._send_config_request( + identifier, + addon_name, + addon_version, + value, + project_name, + entity_type, + entity_ids, + entity_subtypes, + form_data, + variant, + ) + + def take_action(self, 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. + + """ + response = self.get(f"actions/abort/{action_token}") + response.raise_for_status() + return response.data + + def abort_action( + self, + 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. + + """ + if message is None: + message = "Action aborted" + response = self.post( + f"actions/abort/{action_token}", + message=message, + ) + response.raise_for_status() + + def _send_config_request( + self, + identifier: str, + addon_name: str, + addon_version: str, + 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]], + variant: Optional[str], + ) -> "ActionConfigResponse": + """Set and get action configuration.""" + if variant is None: + variant = self.get_default_settings_variant() + query_data = { + "addonName": addon_name, + "addonVersion": addon_version, + "identifier": identifier, + "variant": variant, + } + query = prepare_query_string(query_data) + + kwargs = { + query_key: query_value + for query_key, query_value in ( + ("projectName", project_name), + ("entityType", entity_type), + ("entityIds", entity_ids), + ("entitySubtypes", entity_subtypes), + ("formData", form_data), + ) + if query_value is not None + } + if value is not None: + kwargs["value"] = value + + response = self.post(f"actions/config{query}", **kwargs) + response.raise_for_status() + return response.data diff --git a/ayon_api/server_api.py b/ayon_api/server_api.py index c1b3e7f97..023b70e95 100644 --- a/ayon_api/server_api.py +++ b/ayon_api/server_api.py @@ -99,6 +99,7 @@ SortOrder, get_machine_name, ) +from ._actions import _ActionsAPI if typing.TYPE_CHECKING: from typing import Union @@ -423,7 +424,7 @@ def as_user(self, username): self._last_user = new_last_user -class ServerAPI(object): +class ServerAPI(_ActionsAPI): """Base handler of connection to server. Requires url to server which is used as base for api and graphql calls. diff --git a/ayon_api/typing.py b/ayon_api/typing.py index 07f041aab..81d854de5 100644 --- a/ayon_api/typing.py +++ b/ayon_api/typing.py @@ -10,6 +10,7 @@ BinaryIO, ) + ActivityType = Literal[ "comment", "watch", @@ -35,6 +36,16 @@ ] +IconType = Literal["material-symbols", "url"] + + +class IconDefType(TypedDict): + type: IconType + name: Optional[str] + color: Optional[str] + icon: Optional[str] + + class EventFilterCondition(TypedDict): key: str value: EventFilterValueType @@ -352,4 +363,112 @@ class ProductTypeDict(TypedDict): icon: Optional[str] +ActionEntityTypes = Literal[ + "project", + "folder", + "task", + "product", + "version", + "representation", + "workfile", + "list", +] + + +class ActionManifestDict(TypedDict): + identifier: str + label: str + groupLabel: Optional[str] + category: str + order: int + icon: Optional[IconDefType] + adminOnly: bool + managerOnly: bool + configFields: List[Dict[str, Any]] + featured: bool + addonName: str + addonVersion: str + variant: str + + +ActionResponseType = Literal[ + "form", + "launcher", + "navigate", + "query", + "redirect", + "simple", +] + +ActionModeType = Literal["simple", "dynamic", "all"] + + +class BaseActionPayload(TypedDict): + extra_clipboard: str + extra_download: str + + +class ActionLauncherPayload(BaseActionPayload): + uri: str + + +class ActionNavigatePayload(BaseActionPayload): + uri: str + + +class ActionRedirectPayload(BaseActionPayload): + uri: str + new_tab: bool + + +class ActionQueryPayload(BaseActionPayload): + query: str + + +class ActionFormPayload(BaseActionPayload): + title: str + fields: List[Dict[str, Any]] + submit_label: str + submit_icon: str + cancel_label: str + cancel_icon: str + show_cancel_button: bool + show_submit_button: bool + + +ActionPayload = Union[ + ActionLauncherPayload, + ActionNavigatePayload, + ActionRedirectPayload, + ActionQueryPayload, + ActionFormPayload, +] + +class ActionTriggerResponse(TypedDict): + type: ActionResponseType + success: bool + message: Optional[str] + payload: Optional[ActionPayload] + + +class ActionTakeResponse(TypedDict): + eventId: str + actionIdentifier: str + args: List[str] + context: Dict[str, Any] + addonName: str + addonVersion: str + variant: str + userName: str + + +class ActionConfigResponse(TypedDict): + projectName: str + entityType: str + entitySubtypes: List[str] + entityIds: List[str] + formData: Dict[str, Any] + value: Dict[str, Any] + + StreamType = Union[io.BytesIO, BinaryIO] From 641fed411103a7d1b35abea229d7c5efe0c99d70 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:16:01 +0200 Subject: [PATCH 2/7] added public functions --- ayon_api/__init__.py | 12 +++ ayon_api/_api.py | 226 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 238 insertions(+) diff --git a/ayon_api/__init__.py b/ayon_api/__init__.py index 373cbad88..cb4f97248 100644 --- a/ayon_api/__init__.py +++ b/ayon_api/__init__.py @@ -243,6 +243,12 @@ get_representation_links, send_batch_operations, send_activities_batch_operations, + get_actions, + trigger_action, + get_action_config, + set_action_config, + take_action, + abort_action, ) @@ -489,4 +495,10 @@ "get_representation_links", "send_batch_operations", "send_activities_batch_operations", + "get_actions", + "trigger_action", + "get_action_config", + "set_action_config", + "take_action", + "abort_action", ) diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 0bfe3ee63..15ea3d9f4 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -65,6 +65,12 @@ FlatFolderDict, ProjectHierarchyDict, ProductTypeDict, + ActionEntityTypes, + ActionManifestDict, + ActionTriggerResponse, + ActionTakeResponse, + ActionConfigResponse, + ActionModeType, StreamType, ) @@ -6726,3 +6732,223 @@ def send_activities_batch_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. ('simple', 'dynamic', 'all') + + 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. + + """ + 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, + ) From c1534c4cc55523fb2b9dad12f49f666ccca01311 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:16:33 +0200 Subject: [PATCH 3/7] include '_ActionsAPI' in automated api script --- automated_api.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/automated_api.py b/automated_api.py index 329c6c858..a11dc885a 100644 --- a/automated_api.py +++ b/automated_api.py @@ -30,7 +30,11 @@ sys.modules["unidecode"] = type(sys)("unidecode") import ayon_api # noqa: E402 -from ayon_api.server_api import ServerAPI, _PLACEHOLDER # noqa: E402 +from ayon_api.server_api import ( + ServerAPI, + _PLACEHOLDER, + _ActionsAPI, +) # noqa: E402 from ayon_api.utils import NOT_SET # noqa: E402 EXCLUDED_METHODS = { @@ -285,7 +289,9 @@ def sig_params_to_str(sig, param_names, api_globals, indent=0): def prepare_api_functions(api_globals): functions = [] - for attr_name, attr in ServerAPI.__dict__.items(): + _items = list(ServerAPI.__dict__.items()) + _items.extend(_ActionsAPI.__dict__.items()) + for attr_name, attr in _items: if ( attr_name.startswith("_") or attr_name in EXCLUDED_METHODS From 54949925f7ed45b20a6c77e037c3c4078e6c538d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:21:30 +0200 Subject: [PATCH 4/7] added missing base class --- ayon_api/_base.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 ayon_api/_base.py diff --git a/ayon_api/_base.py b/ayon_api/_base.py new file mode 100644 index 000000000..a87d5800b --- /dev/null +++ b/ayon_api/_base.py @@ -0,0 +1,9 @@ +class _BaseServerAPI: + def get_default_settings_variant(self) -> str: + raise NotImplementedError() + + def get(self, entrypoint: str, **kwargs): + pass + + def post(self, entrypoint: str, **kwargs): + pass From 27490c6e56169f0e9708c2e6261a8c79a04e8ded Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:22:10 +0200 Subject: [PATCH 5/7] fix line length --- ayon_api/_actions.py | 2 +- ayon_api/_api.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ayon_api/_actions.py b/ayon_api/_actions.py index 39b554e6f..a52c711f4 100644 --- a/ayon_api/_actions.py +++ b/ayon_api/_actions.py @@ -41,7 +41,7 @@ def get_actions( 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. ('simple', 'dynamic', 'all') + mode (Optional[ActionModeType]): Action modes. Returns: List[ActionManifestDict]: List of action manifests. diff --git a/ayon_api/_api.py b/ayon_api/_api.py index 15ea3d9f4..bf16f2848 100644 --- a/ayon_api/_api.py +++ b/ayon_api/_api.py @@ -6757,7 +6757,7 @@ def get_actions( 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. ('simple', 'dynamic', 'all') + mode (Optional[ActionModeType]): Action modes. Returns: List[ActionManifestDict]: List of action manifests. From 2209c1d6c2949656266d6a3f83faad075c0b93a2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 1 Aug 2025 18:28:10 +0200 Subject: [PATCH 6/7] move noqa elsewhere --- automated_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/automated_api.py b/automated_api.py index 69998a7b2..da9fc8af0 100644 --- a/automated_api.py +++ b/automated_api.py @@ -30,11 +30,11 @@ sys.modules["unidecode"] = type(sys)("unidecode") import ayon_api # noqa: E402 -from ayon_api.server_api import ( +from ayon_api.server_api import ( # noqa: E402 ServerAPI, _PLACEHOLDER, _ActionsAPI, -) # noqa: E402 +) from ayon_api.utils import NOT_SET # noqa: E402 EXCLUDED_METHODS = { From dc44738191d39849223878bd607e45247e3433ff Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 4 Aug 2025 17:34:39 +0200 Subject: [PATCH 7/7] Fix indentation --- ayon_api/_actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ayon_api/_actions.py b/ayon_api/_actions.py index a52c711f4..b575189dd 100644 --- a/ayon_api/_actions.py +++ b/ayon_api/_actions.py @@ -137,7 +137,7 @@ def get_action_config( form_data: Optional[Dict[str, Any]] = None, *, variant: Optional[str] = None, -) -> "ActionConfigResponse": + ) -> "ActionConfigResponse": """Get action configuration. Args: