From 5f68e8107e729eafabf111b5d04873b21af869d0 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:26:06 +0200 Subject: [PATCH 1/8] allow to implement 'get_app_information' by host --- client/ayon_core/host/__init__.py | 3 ++- client/ayon_core/host/abstract.py | 24 ++++++++++++++++++++++++ client/ayon_core/host/host.py | 14 +++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/host/__init__.py b/client/ayon_core/host/__init__.py index 950c14564e..7d5918b0ac 100644 --- a/client/ayon_core/host/__init__.py +++ b/client/ayon_core/host/__init__.py @@ -1,5 +1,5 @@ from .constants import ContextChangeReason -from .abstract import AbstractHost +from .abstract import AbstractHost, ApplicationInformation from .host import ( HostBase, ContextChangeData, @@ -21,6 +21,7 @@ __all__ = ( "ContextChangeReason", "AbstractHost", + "ApplicationInformation", "HostBase", "ContextChangeData", diff --git a/client/ayon_core/host/abstract.py b/client/ayon_core/host/abstract.py index 26771aaffa..7b4bb5b791 100644 --- a/client/ayon_core/host/abstract.py +++ b/client/ayon_core/host/abstract.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging from abc import ABC, abstractmethod +from dataclasses import dataclass import typing from typing import Optional, Any @@ -13,6 +14,19 @@ if typing.TYPE_CHECKING: from .typing import HostContextData +@dataclass +class ApplicationInformation: + """Application information. + + Attributes: + app_name (Optional[str]): Application name. e.g. Maya, NukeX, Nuke + app_version (Optional[str]): Application version. e.g. 15.2.1 + + """ + app_name: Optional[str] = None + app_version: Optional[str] = None + + class AbstractHost(ABC): """Abstract definition of host implementation.""" @property @@ -26,6 +40,16 @@ class AbstractHost(ABC): """Host name.""" pass + @abstractmethod + def get_app_information(self) -> ApplicationInformation: + """Information about the application where host is running. + + Returns: + ApplicationInformation: Application information. + + """ + pass + @abstractmethod def get_current_context(self) -> HostContextData: """Get the current context of the host. diff --git a/client/ayon_core/host/host.py b/client/ayon_core/host/host.py index 28cb6b0a09..7d6d3ddbe4 100644 --- a/client/ayon_core/host/host.py +++ b/client/ayon_core/host/host.py @@ -12,7 +12,7 @@ import ayon_api from ayon_core.lib import emit_event from .constants import ContextChangeReason -from .abstract import AbstractHost +from .abstract import AbstractHost, ApplicationInformation if typing.TYPE_CHECKING: from ayon_core.pipeline import Anatomy @@ -96,6 +96,18 @@ class HostBase(AbstractHost): pass + def get_app_information(self) -> ApplicationInformation: + """Running application information. + + Host integration should override this method and return correct + information. + + Returns: + ApplicationInformation: Application information. + + """ + return ApplicationInformation() + def install(self): """Install host specific functionality. From 154d8fd0876e1e0e6669c3a3a761ca966ef563a1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:30:17 +0200 Subject: [PATCH 2/8] allow to pass 'data' to save workfile info --- client/ayon_core/pipeline/workfile/utils.py | 72 ++++++++++++--------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/utils.py b/client/ayon_core/pipeline/workfile/utils.py index 6666853998..d22b7a1635 100644 --- a/client/ayon_core/pipeline/workfile/utils.py +++ b/client/ayon_core/pipeline/workfile/utils.py @@ -10,6 +10,7 @@ from ayon_api.operations import OperationsSession from ayon_core.lib import filter_profiles, get_ayon_username from ayon_core.settings import get_project_settings +from ayon_core.host import ApplicationInformation from ayon_core.host.interfaces import ( SaveWorkfileOptionalData, ListWorkfilesOptionalData, @@ -207,6 +208,7 @@ def save_workfile_info( comment: Optional[str] = None, description: Optional[str] = None, username: Optional[str] = None, + data: Optional[dict[str, Any]] = None, workfile_entities: Optional[list[dict[str, Any]]] = None, ) -> dict[str, Any]: """Save workfile info entity for a workfile path. @@ -221,6 +223,8 @@ def save_workfile_info( description (Optional[str]): Workfile description. username (Optional[str]): Username of user who saves the workfile. If not provided, current user is used. + app_info (Optional[ApplicationInformation]): Application information. + data (Optional[dict[str, Any]]): Additional workfile entity data. workfile_entities (Optional[list[dict[str, Any]]]): Pre-fetched workfile entities related to task. @@ -246,6 +250,18 @@ def save_workfile_info( if username is None: username = get_ayon_username() + attrib = {} + extension = os.path.splitext(rootless_path)[1] + for key, value in ( + ("extension", extension), + ("description", description), + ): + if value is not None: + attrib[key] = value + + if data is None: + data = {} + if not workfile_entity: return _create_workfile_info_entity( project_name, @@ -255,34 +271,38 @@ def save_workfile_info( username, version, comment, - description, + attrib, + data, ) - data = { - key: value - for key, value in ( - ("host_name", host_name), - ("version", version), - ("comment", comment), - ) - if value is not None - } - - old_data = workfile_entity["data"] + for key, value in ( + ("host_name", host_name), + ("version", version), + ("comment", comment), + ): + if value is not None: + data[key] = value changed_data = {} + old_data = workfile_entity["data"] for key, value in data.items(): if key not in old_data or old_data[key] != value: changed_data[key] = value + workfile_entity["data"][key] = value + + changed_attrib = {} + old_attrib = workfile_entity["attrib"] + for key, value in attrib.items(): + if key not in old_attrib or old_attrib[key] != value: + changed_attrib[key] = value + workfile_entity["attrib"][key] = value update_data = {} if changed_data: update_data["data"] = changed_data - old_description = workfile_entity["attrib"].get("description") - if description is not None and old_description != description: - update_data["attrib"] = {"description": description} - workfile_entity["attrib"]["description"] = description + if changed_attrib: + update_data["attrib"] = changed_attrib # Automatically fix 'createdBy' and 'updatedBy' fields # NOTE both fields were not automatically filled by server @@ -749,7 +769,8 @@ def _create_workfile_info_entity( username: str, version: Optional[int], comment: Optional[str], - description: Optional[str], + attrib: dict[str, Any], + data: dict[str, Any], ) -> dict[str, Any]: """Create workfile entity data. @@ -761,27 +782,18 @@ def _create_workfile_info_entity( username (str): Username. version (Optional[int]): Workfile version. comment (Optional[str]): Workfile comment. - description (Optional[str]): Workfile description. + attrib (dict[str, Any]): Workfile entity attributes. + data (dict[str, Any]): Workfile entity data. Returns: dict[str, Any]: Created workfile entity data. """ - extension = os.path.splitext(rootless_path)[1] - - attrib = {} - for key, value in ( - ("extension", extension), - ("description", description), - ): - if value is not None: - attrib[key] = value - - data = { + data.update({ "host_name": host_name, "version": version, "comment": comment, - } + }) workfile_info = { "id": uuid.uuid4().hex, From 27c1c30f2e94830e68b8f044fb23a1ff8fe4e424 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:30:35 +0200 Subject: [PATCH 3/8] add app information to workfile data --- client/ayon_core/host/interfaces/workfiles.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/client/ayon_core/host/interfaces/workfiles.py b/client/ayon_core/host/interfaces/workfiles.py index 93aad4c117..69b902c9e2 100644 --- a/client/ayon_core/host/interfaces/workfiles.py +++ b/client/ayon_core/host/interfaces/workfiles.py @@ -1554,6 +1554,22 @@ class IWorkfileHost(AbstractHost): if platform.system().lower() == "windows": rootless_path = rootless_path.replace("\\", "/") + # Get application information + app_info = self.get_app_information() + data = {} + if app_info.app_name: + data["app_name"] = app_info.app_name + if app_info.app_version: + data["app_version"] = app_info.app_version + + # Use app group and app variant from applications addon (if available) + app_addon_name = os.environ.get("AYON_APP_NAME") or "" + app_addon_name_parts = app_addon_name.split("/") + if len(app_addon_name_parts) == 2: + app_group, app_variant = app_addon_name_parts + data["app_group"] = app_group + data["app_variant"] = app_variant + workfile_info = save_workfile_info( project_name, save_workfile_context.task_entity["id"], @@ -1562,6 +1578,7 @@ class IWorkfileHost(AbstractHost): version, comment, description, + data=data, workfile_entities=save_workfile_context.workfile_entities, ) return workfile_info From eb3428e37169c20f8af8d9ae53cb75ab40f6a918 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:27:49 +0200 Subject: [PATCH 4/8] remove unused import --- client/ayon_core/pipeline/workfile/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/pipeline/workfile/utils.py b/client/ayon_core/pipeline/workfile/utils.py index d22b7a1635..c2b6fad660 100644 --- a/client/ayon_core/pipeline/workfile/utils.py +++ b/client/ayon_core/pipeline/workfile/utils.py @@ -10,7 +10,6 @@ from ayon_api.operations import OperationsSession from ayon_core.lib import filter_profiles, get_ayon_username from ayon_core.settings import get_project_settings -from ayon_core.host import ApplicationInformation from ayon_core.host.interfaces import ( SaveWorkfileOptionalData, ListWorkfilesOptionalData, @@ -223,7 +222,6 @@ def save_workfile_info( description (Optional[str]): Workfile description. username (Optional[str]): Username of user who saves the workfile. If not provided, current user is used. - app_info (Optional[ApplicationInformation]): Application information. data (Optional[dict[str, Any]]): Additional workfile entity data. workfile_entities (Optional[list[dict[str, Any]]]): Pre-fetched workfile entities related to task. From e416e3218968ea876202bb8ddd196d24f465aa50 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:13:43 +0200 Subject: [PATCH 5/8] use f-string --- client/ayon_core/host/interfaces/workfiles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/host/interfaces/workfiles.py b/client/ayon_core/host/interfaces/workfiles.py index 69b902c9e2..b908e7167e 100644 --- a/client/ayon_core/host/interfaces/workfiles.py +++ b/client/ayon_core/host/interfaces/workfiles.py @@ -55,7 +55,7 @@ class _WorkfileOptionalData: ): if kwargs: cls_name = self.__class__.__name__ - keys = ", ".join(['"{}"'.format(k) for k in kwargs.keys()]) + keys = ", ".join([f'"{k}"' for k in kwargs.keys()]) warnings.warn( f"Unknown keywords passed to {cls_name}: {keys}", ) From 9d55c1e9021e36ff32f8305dc4a196fb37d2a906 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 18 Sep 2025 11:44:18 +0200 Subject: [PATCH 6/8] keep ayon application full name as is --- client/ayon_core/host/interfaces/workfiles.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/host/interfaces/workfiles.py b/client/ayon_core/host/interfaces/workfiles.py index b908e7167e..4f7926e239 100644 --- a/client/ayon_core/host/interfaces/workfiles.py +++ b/client/ayon_core/host/interfaces/workfiles.py @@ -1563,12 +1563,13 @@ class IWorkfileHost(AbstractHost): data["app_version"] = app_info.app_version # Use app group and app variant from applications addon (if available) - app_addon_name = os.environ.get("AYON_APP_NAME") or "" - app_addon_name_parts = app_addon_name.split("/") - if len(app_addon_name_parts) == 2: - app_group, app_variant = app_addon_name_parts - data["app_group"] = app_group - data["app_variant"] = app_variant + app_addon_name = os.environ.get("AYON_APP_NAME") + if app_addon_name: + data["app_addon_name"] = app_addon_name + + app_addon_tools = os.environ.get("AYON_APP_TOOLS") + if app_addon_tools: + data["app_addon_tools"] = app_addon_tools.split(";") workfile_info = save_workfile_info( project_name, From a1b863e6a67f222948108ed54383b4872a76946d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:58:05 +0200 Subject: [PATCH 7/8] use ayon prefix for ayon app name and tools --- client/ayon_core/host/interfaces/workfiles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/host/interfaces/workfiles.py b/client/ayon_core/host/interfaces/workfiles.py index 4f7926e239..587962351d 100644 --- a/client/ayon_core/host/interfaces/workfiles.py +++ b/client/ayon_core/host/interfaces/workfiles.py @@ -1565,11 +1565,11 @@ class IWorkfileHost(AbstractHost): # Use app group and app variant from applications addon (if available) app_addon_name = os.environ.get("AYON_APP_NAME") if app_addon_name: - data["app_addon_name"] = app_addon_name + data["ayon_app_name"] = app_addon_name app_addon_tools = os.environ.get("AYON_APP_TOOLS") if app_addon_tools: - data["app_addon_tools"] = app_addon_tools.split(";") + data["ayon_app_tools"] = app_addon_tools.split(";") workfile_info = save_workfile_info( project_name, From 73641b5055e0e5b692f7691c82ca0fdf182dc625 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 23 Sep 2025 14:09:38 +0200 Subject: [PATCH 8/8] always fill ayon app name and tools --- client/ayon_core/host/interfaces/workfiles.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/host/interfaces/workfiles.py b/client/ayon_core/host/interfaces/workfiles.py index 587962351d..5dbf29bd7b 100644 --- a/client/ayon_core/host/interfaces/workfiles.py +++ b/client/ayon_core/host/interfaces/workfiles.py @@ -1564,12 +1564,16 @@ class IWorkfileHost(AbstractHost): # Use app group and app variant from applications addon (if available) app_addon_name = os.environ.get("AYON_APP_NAME") - if app_addon_name: - data["ayon_app_name"] = app_addon_name + if not app_addon_name: + app_addon_name = None - app_addon_tools = os.environ.get("AYON_APP_TOOLS") - if app_addon_tools: - data["ayon_app_tools"] = app_addon_tools.split(";") + app_addon_tools_s = os.environ.get("AYON_APP_TOOLS") + app_addon_tools = [] + if app_addon_tools_s: + app_addon_tools = app_addon_tools_s.split(";") + + data["ayon_app_name"] = app_addon_name + data["ayon_app_tools"] = app_addon_tools workfile_info = save_workfile_info( project_name,