From 6b6c93376e18e16ebe3ef589345c121e0e2c2b06 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 Aug 2025 11:08:27 +0200 Subject: [PATCH 1/8] implemented abstract host class --- client/ayon_core/host/abstract.py | 96 +++++++++++++++++++++++++++++++ client/ayon_core/host/typing.py | 7 +++ 2 files changed, 103 insertions(+) create mode 100644 client/ayon_core/host/abstract.py create mode 100644 client/ayon_core/host/typing.py diff --git a/client/ayon_core/host/abstract.py b/client/ayon_core/host/abstract.py new file mode 100644 index 0000000000..26771aaffa --- /dev/null +++ b/client/ayon_core/host/abstract.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import logging +from abc import ABC, abstractmethod +import typing +from typing import Optional, Any + +from .constants import ContextChangeReason + +if typing.TYPE_CHECKING: + from ayon_core.pipeline import Anatomy + + from .typing import HostContextData + + +class AbstractHost(ABC): + """Abstract definition of host implementation.""" + @property + @abstractmethod + def log(self) -> logging.Logger: + pass + + @property + @abstractmethod + def name(self) -> str: + """Host name.""" + pass + + @abstractmethod + def get_current_context(self) -> HostContextData: + """Get the current context of the host. + + Current context is defined by project name, folder path and task name. + + Returns: + HostContextData: The current context of the host. + + """ + pass + + @abstractmethod + def set_current_context( + self, + folder_entity: dict[str, Any], + task_entity: dict[str, Any], + *, + reason: ContextChangeReason = ContextChangeReason.undefined, + project_entity: Optional[dict[str, Any]] = None, + anatomy: Optional[Anatomy] = None, + ) -> HostContextData: + """Change context of the host. + + Args: + folder_entity (dict[str, Any]): Folder entity. + task_entity (dict[str, Any]): Task entity. + reason (ContextChangeReason): Reason for change. + project_entity (dict[str, Any]): Project entity. + anatomy (Anatomy): Anatomy entity. + + """ + pass + + @abstractmethod + def get_current_project_name(self) -> str: + """Get the current project name. + + Returns: + Optional[str]: The current project name. + + """ + pass + + @abstractmethod + def get_current_folder_path(self) -> Optional[str]: + """Get the current folder path. + + Returns: + Optional[str]: The current folder path. + + """ + pass + + @abstractmethod + def get_current_task_name(self) -> Optional[str]: + """Get the current task name. + + Returns: + Optional[str]: The current task name. + + """ + pass + + @abstractmethod + def get_context_title(self) -> str: + """Get the context title used in UIs.""" + pass diff --git a/client/ayon_core/host/typing.py b/client/ayon_core/host/typing.py new file mode 100644 index 0000000000..a51460713b --- /dev/null +++ b/client/ayon_core/host/typing.py @@ -0,0 +1,7 @@ +from typing import Optional, TypedDict + + +class HostContextData(TypedDict): + project_name: str + folder_path: Optional[str] + task_name: Optional[str] From 044e41471810b06e28ab4051775169896916a455 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 Aug 2025 11:09:01 +0200 Subject: [PATCH 2/8] use AbstractHost for interfaces and HotBase --- client/ayon_core/host/host.py | 13 ++++--------- client/ayon_core/host/interfaces/interfaces.py | 6 ++++-- client/ayon_core/host/interfaces/workfiles.py | 3 ++- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/client/ayon_core/host/host.py b/client/ayon_core/host/host.py index 7fc4b19bdd..7fd63a5864 100644 --- a/client/ayon_core/host/host.py +++ b/client/ayon_core/host/host.py @@ -3,26 +3,21 @@ from __future__ import annotations import os import logging import contextlib -from abc import ABC, abstractmethod -from dataclasses import dataclass import typing from typing import Optional, Any +from dataclasses import dataclass import ayon_api from ayon_core.lib import emit_event from .constants import ContextChangeReason +from .abstract import AbstractHost if typing.TYPE_CHECKING: from ayon_core.pipeline import Anatomy - from typing import TypedDict - - class HostContextData(TypedDict): - project_name: str - folder_path: Optional[str] - task_name: Optional[str] + from .typing import HostContextData @dataclass @@ -34,7 +29,7 @@ class ContextChangeData: anatomy: Anatomy -class HostBase(ABC): +class HostBase(AbstractHost): """Base of host implementation class. Host is pipeline implementation of DCC application. This class should help diff --git a/client/ayon_core/host/interfaces/interfaces.py b/client/ayon_core/host/interfaces/interfaces.py index a41dffe92a..8b7005085e 100644 --- a/client/ayon_core/host/interfaces/interfaces.py +++ b/client/ayon_core/host/interfaces/interfaces.py @@ -1,9 +1,11 @@ from abc import abstractmethod +from ayon_core.host.abstract import AbstractHost + from .exceptions import MissingMethodsError -class ILoadHost: +class ILoadHost(AbstractHost): """Implementation requirements to be able use reference of representations. The load plugins can do referencing even without implementation of methods @@ -83,7 +85,7 @@ class ILoadHost: return self.get_containers() -class IPublishHost: +class IPublishHost(AbstractHost): """Functions related to new creation system in new publisher. New publisher is not storing information only about each created instance diff --git a/client/ayon_core/host/interfaces/workfiles.py b/client/ayon_core/host/interfaces/workfiles.py index 82d71d152a..93aad4c117 100644 --- a/client/ayon_core/host/interfaces/workfiles.py +++ b/client/ayon_core/host/interfaces/workfiles.py @@ -15,6 +15,7 @@ import arrow from ayon_core.lib import emit_event from ayon_core.settings import get_project_settings +from ayon_core.host.abstract import AbstractHost from ayon_core.host.constants import ContextChangeReason if typing.TYPE_CHECKING: @@ -821,7 +822,7 @@ class PublishedWorkfileInfo: return PublishedWorkfileInfo(**data) -class IWorkfileHost: +class IWorkfileHost(AbstractHost): """Implementation requirements to be able to use workfiles utils and tool. Some of the methods are pre-implemented as they generally do the same in From 89e92f555684382bce822165213e0b8cadee2f40 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 Aug 2025 11:09:33 +0200 Subject: [PATCH 3/8] remove name abstraction --- client/ayon_core/host/host.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/client/ayon_core/host/host.py b/client/ayon_core/host/host.py index 7fd63a5864..9b7d43be94 100644 --- a/client/ayon_core/host/host.py +++ b/client/ayon_core/host/host.py @@ -114,13 +114,6 @@ class HostBase(AbstractHost): self._log = logging.getLogger(self.__class__.__name__) return self._log - @property - @abstractmethod - def name(self) -> str: - """Host name.""" - - pass - def get_current_project_name(self): """ Returns: From 77383fea1e3e23354e182e8637e94f51e4d11765 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 Aug 2025 11:09:54 +0200 Subject: [PATCH 4/8] updated docstrings and type hints --- client/ayon_core/host/host.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/client/ayon_core/host/host.py b/client/ayon_core/host/host.py index 9b7d43be94..28cb6b0a09 100644 --- a/client/ayon_core/host/host.py +++ b/client/ayon_core/host/host.py @@ -104,41 +104,41 @@ class HostBase(AbstractHost): It is called automatically when 'ayon_core.pipeline.install_host' is triggered. - """ + """ pass @property - def log(self): + def log(self) -> logging.Logger: if self._log is None: self._log = logging.getLogger(self.__class__.__name__) return self._log - def get_current_project_name(self): + def get_current_project_name(self) -> str: """ Returns: - Union[str, None]: Current project name. - """ + str: Current project name. - return os.environ.get("AYON_PROJECT_NAME") + """ + return os.environ["AYON_PROJECT_NAME"] def get_current_folder_path(self) -> Optional[str]: """ Returns: - Union[str, None]: Current asset name. - """ + Optional[str]: Current asset name. + """ return os.environ.get("AYON_FOLDER_PATH") def get_current_task_name(self) -> Optional[str]: """ Returns: - Union[str, None]: Current task name. - """ + Optional[str]: Current task name. + """ return os.environ.get("AYON_TASK_NAME") - def get_current_context(self) -> "HostContextData": + def get_current_context(self) -> HostContextData: """Get current context information. This method should be used to get current context of host. Usage of @@ -147,10 +147,10 @@ class HostBase(AbstractHost): can't be caught properly. Returns: - Dict[str, Union[str, None]]: Context with 3 keys 'project_name', - 'folder_path' and 'task_name'. All of them can be 'None'. - """ + HostContextData: Current context with 'project_name', + 'folder_path' and 'task_name'. + """ return { "project_name": self.get_current_project_name(), "folder_path": self.get_current_folder_path(), @@ -165,7 +165,7 @@ class HostBase(AbstractHost): reason: ContextChangeReason = ContextChangeReason.undefined, project_entity: Optional[dict[str, Any]] = None, anatomy: Optional[Anatomy] = None, - ) -> "HostContextData": + ) -> HostContextData: """Set current context information. This method should be used to set current context of host. Usage of @@ -278,7 +278,7 @@ class HostBase(AbstractHost): project_name: str, folder_path: Optional[str], task_name: Optional[str], - ) -> "HostContextData": + ) -> HostContextData: """Emit context change event. Args: @@ -290,7 +290,7 @@ class HostBase(AbstractHost): HostContextData: Data send to context change event. """ - data = { + data: HostContextData = { "project_name": project_name, "folder_path": folder_path, "task_name": task_name, From 2bd18c4d9614e502d2a774093a2b4e8aa1b42397 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:39:32 +0200 Subject: [PATCH 5/8] added some of the classes to host init --- client/ayon_core/host/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/host/__init__.py b/client/ayon_core/host/__init__.py index ef5c324028..a20165bce2 100644 --- a/client/ayon_core/host/__init__.py +++ b/client/ayon_core/host/__init__.py @@ -1,6 +1,8 @@ from .constants import ContextChangeReason +from .abstract import AbstractHost from .host import ( HostBase, + HostContextData, ) from .interfaces import ( @@ -18,7 +20,10 @@ from .dirmap import HostDirmap __all__ = ( "ContextChangeReason", + "AbstractHost", + "HostBase", + "HostContextData", "IWorkfileHost", "WorkfileInfo", From 53d0d4985a1d1d1dfbcc8d9ec63006afca5bf10e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:47:45 +0200 Subject: [PATCH 6/8] use 'AbstractHost' for type checking --- .../ayon_core/host/interfaces/interfaces.py | 8 ++++---- client/ayon_core/pipeline/context_tools.py | 20 +++++++++---------- .../workfile/workfile_template_builder.py | 16 +++++++-------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/client/ayon_core/host/interfaces/interfaces.py b/client/ayon_core/host/interfaces/interfaces.py index 8b7005085e..6f9a3d8c87 100644 --- a/client/ayon_core/host/interfaces/interfaces.py +++ b/client/ayon_core/host/interfaces/interfaces.py @@ -26,7 +26,7 @@ class ILoadHost(AbstractHost): loading. Checks only existence of methods. Args: - Union[ModuleType, HostBase]: Object of host where to look for + Union[ModuleType, AbstractHost]: Object of host where to look for required methods. Returns: @@ -48,7 +48,7 @@ class ILoadHost(AbstractHost): """Validate implemented methods of "old type" host for load workflow. Args: - Union[ModuleType, HostBase]: Object of host to validate. + Union[ModuleType, AbstractHost]: Object of host to validate. Raises: MissingMethodsError: If there are missing methods on host @@ -101,7 +101,7 @@ class IPublishHost(AbstractHost): new publish creation. Checks only existence of methods. Args: - Union[ModuleType, HostBase]: Host module where to look for + Union[ModuleType, AbstractHost]: Host module where to look for required methods. Returns: @@ -129,7 +129,7 @@ class IPublishHost(AbstractHost): """Validate implemented methods of "old type" host. Args: - Union[ModuleType, HostBase]: Host module to validate. + Union[ModuleType, AbstractHost]: Host module to validate. Raises: MissingMethodsError: If there are missing methods on host diff --git a/client/ayon_core/pipeline/context_tools.py b/client/ayon_core/pipeline/context_tools.py index 423e8f7216..0589eeb49f 100644 --- a/client/ayon_core/pipeline/context_tools.py +++ b/client/ayon_core/pipeline/context_tools.py @@ -13,7 +13,7 @@ import pyblish.api from pyblish.lib import MessageHandler from ayon_core import AYON_CORE_ROOT -from ayon_core.host import HostBase +from ayon_core.host import AbstractHost from ayon_core.lib import ( is_in_tests, initialize_ayon_connection, @@ -100,16 +100,16 @@ def registered_root(): return _registered_root["_"] -def install_host(host: HostBase) -> None: +def install_host(host: AbstractHost) -> None: """Install `host` into the running Python session. Args: - host (HostBase): A host interface object. + host (AbstractHost): A host interface object. """ - if not isinstance(host, HostBase): + if not isinstance(host, AbstractHost): log.error( - f"Host must be a subclass of 'HostBase', got '{type(host)}'." + f"Host must be a subclass of 'AbstractHost', got '{type(host)}'." ) global _is_installed @@ -310,7 +310,7 @@ def get_current_host_name(): """ host = registered_host() - if isinstance(host, HostBase): + if isinstance(host, AbstractHost): return host.name return os.environ.get("AYON_HOST_NAME") @@ -346,28 +346,28 @@ def get_global_context(): def get_current_context(): host = registered_host() - if isinstance(host, HostBase): + if isinstance(host, AbstractHost): return host.get_current_context() return get_global_context() def get_current_project_name(): host = registered_host() - if isinstance(host, HostBase): + if isinstance(host, AbstractHost): return host.get_current_project_name() return get_global_context()["project_name"] def get_current_folder_path(): host = registered_host() - if isinstance(host, HostBase): + if isinstance(host, AbstractHost): return host.get_current_folder_path() return get_global_context()["folder_path"] def get_current_task_name(): host = registered_host() - if isinstance(host, HostBase): + if isinstance(host, AbstractHost): return host.get_current_task_name() return get_global_context()["task_name"] diff --git a/client/ayon_core/pipeline/workfile/workfile_template_builder.py b/client/ayon_core/pipeline/workfile/workfile_template_builder.py index e2add99752..4349585b82 100644 --- a/client/ayon_core/pipeline/workfile/workfile_template_builder.py +++ b/client/ayon_core/pipeline/workfile/workfile_template_builder.py @@ -30,7 +30,7 @@ from ayon_api import ( ) from ayon_core.settings import get_project_settings -from ayon_core.host import IWorkfileHost, HostBase +from ayon_core.host import IWorkfileHost, AbstractHost from ayon_core.lib import ( Logger, StringTemplate, @@ -127,7 +127,7 @@ class AbstractTemplateBuilder(ABC): placeholder population. Args: - host (Union[HostBase, ModuleType]): Implementation of host. + host (Union[AbstractHost, ModuleType]): Implementation of host. """ _log = None @@ -135,7 +135,7 @@ class AbstractTemplateBuilder(ABC): def __init__(self, host): # Get host name - if isinstance(host, HostBase): + if isinstance(host, AbstractHost): host_name = host.name else: host_name = os.environ.get("AYON_HOST_NAME") @@ -163,24 +163,24 @@ class AbstractTemplateBuilder(ABC): @property def project_name(self): - if isinstance(self._host, HostBase): + if isinstance(self._host, AbstractHost): return self._host.get_current_project_name() return os.getenv("AYON_PROJECT_NAME") @property def current_folder_path(self): - if isinstance(self._host, HostBase): + if isinstance(self._host, AbstractHost): return self._host.get_current_folder_path() return os.getenv("AYON_FOLDER_PATH") @property def current_task_name(self): - if isinstance(self._host, HostBase): + if isinstance(self._host, AbstractHost): return self._host.get_current_task_name() return os.getenv("AYON_TASK_NAME") def get_current_context(self): - if isinstance(self._host, HostBase): + if isinstance(self._host, AbstractHost): return self._host.get_current_context() return { "project_name": self.project_name, @@ -256,7 +256,7 @@ class AbstractTemplateBuilder(ABC): """Access to host implementation. Returns: - Union[HostBase, ModuleType]: Implementation of host. + Union[AbstractHost, ModuleType]: Implementation of host. """ return self._host From ec92be4cae509a5baa31b71725da4bd5c1d68c54 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:48:10 +0200 Subject: [PATCH 7/8] simplified few type hints --- client/ayon_core/pipeline/create/context.py | 7 ++----- client/ayon_core/tools/publisher/abstract.py | 4 ++-- client/ayon_core/tools/sceneinventory/control.py | 4 ++-- client/ayon_core/tools/workfiles/models/workfiles.py | 9 ++------- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/client/ayon_core/pipeline/create/context.py b/client/ayon_core/pipeline/create/context.py index 929cc59d2a..bd7dd4414f 100644 --- a/client/ayon_core/pipeline/create/context.py +++ b/client/ayon_core/pipeline/create/context.py @@ -49,15 +49,12 @@ from .creator_plugins import ( discover_convertor_plugins, ) if typing.TYPE_CHECKING: - from ayon_core.host import HostBase from ayon_core.lib import AbstractAttrDef from ayon_core.lib.events import EventCallback, Event from .structures import CreatedInstance from .creator_plugins import BaseCreator - class PublishHost(HostBase, IPublishHost): - pass # Import of functions and classes that were moved to different file # TODO Should be removed in future release - Added 24/08/28, 0.4.3-dev.1 @@ -163,7 +160,7 @@ class CreateContext: context which should be handled by host. Args: - host (PublishHost): Host implementation which handles implementation + host (IPublishHost): Host implementation which handles implementation and global metadata. headless (bool): Context is created out of UI (Current not used). reset (bool): Reset context on initialization. @@ -173,7 +170,7 @@ class CreateContext: def __init__( self, - host: "PublishHost", + host: IPublishHost, headless: bool = False, reset: bool = True, discover_publish_plugins: bool = True, diff --git a/client/ayon_core/tools/publisher/abstract.py b/client/ayon_core/tools/publisher/abstract.py index 6d0027d35d..14da15793d 100644 --- a/client/ayon_core/tools/publisher/abstract.py +++ b/client/ayon_core/tools/publisher/abstract.py @@ -13,7 +13,7 @@ from typing import ( ) from ayon_core.lib import AbstractAttrDef -from ayon_core.host import HostBase +from ayon_core.host import AbstractHost from ayon_core.pipeline.create import ( CreateContext, ConvertorItem, @@ -176,7 +176,7 @@ class AbstractPublisherBackend(AbstractPublisherCommon): pass @abstractmethod - def get_host(self) -> HostBase: + def get_host(self) -> AbstractHost: pass @abstractmethod diff --git a/client/ayon_core/tools/sceneinventory/control.py b/client/ayon_core/tools/sceneinventory/control.py index 60d9bc77a9..45f76a54ac 100644 --- a/client/ayon_core/tools/sceneinventory/control.py +++ b/client/ayon_core/tools/sceneinventory/control.py @@ -1,7 +1,7 @@ import ayon_api from ayon_core.lib.events import QueuedEventSystem -from ayon_core.host import HostBase +from ayon_core.host import ILoadHost from ayon_core.pipeline import ( registered_host, get_current_context, @@ -35,7 +35,7 @@ class SceneInventoryController: self._projects_model = ProjectsModel(self) self._event_system = self._create_event_system() - def get_host(self) -> HostBase: + def get_host(self) -> ILoadHost: return self._host def emit_event(self, topic, data=None, source=None): diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index d33a532222..5b5591fe43 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -14,7 +14,6 @@ from ayon_core.lib import ( Logger, ) from ayon_core.host import ( - HostBase, IWorkfileHost, WorkfileInfo, PublishedWorkfileInfo, @@ -49,19 +48,15 @@ if typing.TYPE_CHECKING: _NOT_SET = object() -class HostType(HostBase, IWorkfileHost): - pass - - class WorkfilesModel: """Workfiles model.""" def __init__( self, - host: HostType, + host: IWorkfileHost, controller: AbstractWorkfilesBackend ): - self._host: HostType = host + self._host: IWorkfileHost = host self._controller: AbstractWorkfilesBackend = controller self._log = Logger.get_logger("WorkfilesModel") From 644130ad7a4a09e9b51652773ec9cb83c12424a9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 15 Aug 2025 12:51:33 +0200 Subject: [PATCH 8/8] fix imported class --- client/ayon_core/host/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/host/__init__.py b/client/ayon_core/host/__init__.py index a20165bce2..950c14564e 100644 --- a/client/ayon_core/host/__init__.py +++ b/client/ayon_core/host/__init__.py @@ -2,7 +2,7 @@ from .constants import ContextChangeReason from .abstract import AbstractHost from .host import ( HostBase, - HostContextData, + ContextChangeData, ) from .interfaces import ( @@ -23,7 +23,7 @@ __all__ = ( "AbstractHost", "HostBase", - "HostContextData", + "ContextChangeData", "IWorkfileHost", "WorkfileInfo",