mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge remote-tracking branch 'origin/feature/909-define-basic-trait-type-using-dataclasses' into feature/911-new-traits-based-integrator
This commit is contained in:
commit
40f11ef0b6
3 changed files with 175 additions and 85 deletions
|
|
@ -6,6 +6,7 @@ from .interfaces import (
|
|||
ITrayAction,
|
||||
ITrayService,
|
||||
IHostAddon,
|
||||
ITraits,
|
||||
)
|
||||
|
||||
from .base import (
|
||||
|
|
@ -30,6 +31,7 @@ __all__ = (
|
|||
"ITrayAction",
|
||||
"ITrayService",
|
||||
"IHostAddon",
|
||||
"ITraits",
|
||||
|
||||
"ProcessPreparationError",
|
||||
"ProcessContext",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from typing import TYPE_CHECKING, Callable, Optional, Type
|
||||
|
||||
from ayon_core import resources
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qtpy import QtWidgets
|
||||
|
||||
from ayon_core.addon import AddonsManager
|
||||
from ayon_core.pipeline.traits import TraitBase
|
||||
from ayon_core.tools.tray import TrayManager
|
||||
|
||||
|
||||
class _AYONInterfaceMeta(ABCMeta):
|
||||
"""AYONInterface meta class to print proper string."""
|
||||
"""AYONInterface metaclass to print proper string."""
|
||||
|
||||
def __str__(self):
|
||||
return "<'AYONInterface.{}'>".format(self.__name__)
|
||||
return f"<'AYONInterface.{self.__name__}'>"
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
|
@ -24,7 +35,11 @@ class AYONInterface(metaclass=_AYONInterfaceMeta):
|
|||
in the interface. By default, interface does not have any abstract parts.
|
||||
"""
|
||||
|
||||
pass
|
||||
log = None
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize interface."""
|
||||
self.log = logging.getLogger(self.__class__.__name__)
|
||||
|
||||
|
||||
class IPluginPaths(AYONInterface):
|
||||
|
|
@ -38,10 +53,25 @@ class IPluginPaths(AYONInterface):
|
|||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_plugin_paths(self):
|
||||
pass
|
||||
def get_plugin_paths(self) -> dict[str, list[str]]:
|
||||
"""Return plugin paths for addon.
|
||||
|
||||
def _get_plugin_paths_by_type(self, plugin_type):
|
||||
Returns:
|
||||
dict[str, list[str]]: Plugin paths for addon.
|
||||
|
||||
"""
|
||||
|
||||
def _get_plugin_paths_by_type(
|
||||
self, plugin_type: str) -> list[str]:
|
||||
"""Get plugin paths by type.
|
||||
|
||||
Args:
|
||||
plugin_type (str): Type of plugin paths to get.
|
||||
|
||||
Returns:
|
||||
list[str]: List of plugin paths.
|
||||
|
||||
"""
|
||||
paths = self.get_plugin_paths()
|
||||
if not paths or plugin_type not in paths:
|
||||
return []
|
||||
|
|
@ -54,7 +84,7 @@ class IPluginPaths(AYONInterface):
|
|||
paths = [paths]
|
||||
return paths
|
||||
|
||||
def get_create_plugin_paths(self, host_name):
|
||||
def get_create_plugin_paths(self, host_name: str) -> list[str]:
|
||||
"""Receive create plugin paths.
|
||||
|
||||
Give addons ability to add create plugin paths based on host name.
|
||||
|
|
@ -65,11 +95,11 @@ class IPluginPaths(AYONInterface):
|
|||
|
||||
Args:
|
||||
host_name (str): For which host are the plugins meant.
|
||||
"""
|
||||
|
||||
"""
|
||||
return self._get_plugin_paths_by_type("create")
|
||||
|
||||
def get_load_plugin_paths(self, host_name):
|
||||
def get_load_plugin_paths(self, host_name: str) -> list[str]:
|
||||
"""Receive load plugin paths.
|
||||
|
||||
Give addons ability to add load plugin paths based on host name.
|
||||
|
|
@ -80,11 +110,11 @@ class IPluginPaths(AYONInterface):
|
|||
|
||||
Args:
|
||||
host_name (str): For which host are the plugins meant.
|
||||
"""
|
||||
|
||||
"""
|
||||
return self._get_plugin_paths_by_type("load")
|
||||
|
||||
def get_publish_plugin_paths(self, host_name):
|
||||
def get_publish_plugin_paths(self, host_name: str) -> list[str]:
|
||||
"""Receive publish plugin paths.
|
||||
|
||||
Give addons ability to add publish plugin paths based on host name.
|
||||
|
|
@ -95,11 +125,11 @@ class IPluginPaths(AYONInterface):
|
|||
|
||||
Args:
|
||||
host_name (str): For which host are the plugins meant.
|
||||
"""
|
||||
|
||||
"""
|
||||
return self._get_plugin_paths_by_type("publish")
|
||||
|
||||
def get_inventory_action_paths(self, host_name):
|
||||
def get_inventory_action_paths(self, host_name: str) -> list[str]:
|
||||
"""Receive inventory action paths.
|
||||
|
||||
Give addons ability to add inventory action plugin paths.
|
||||
|
|
@ -110,76 +140,84 @@ class IPluginPaths(AYONInterface):
|
|||
|
||||
Args:
|
||||
host_name (str): For which host are the plugins meant.
|
||||
"""
|
||||
|
||||
"""
|
||||
return self._get_plugin_paths_by_type("inventory")
|
||||
|
||||
|
||||
class ITrayAddon(AYONInterface):
|
||||
"""Addon has special procedures when used in Tray tool.
|
||||
|
||||
IMPORTANT:
|
||||
The addon. still must be usable if is not used in tray even if
|
||||
would do nothing.
|
||||
"""
|
||||
Important:
|
||||
The addon. still must be usable if is not used in tray even if it
|
||||
would do nothing.
|
||||
|
||||
"""
|
||||
tray_initialized = False
|
||||
_tray_manager = None
|
||||
manager: AddonsManager = None
|
||||
_tray_manager: TrayManager = None
|
||||
|
||||
@abstractmethod
|
||||
def tray_init(self):
|
||||
def tray_init(self) -> None:
|
||||
"""Initialization part of tray implementation.
|
||||
|
||||
Triggered between `initialization` and `connect_with_addons`.
|
||||
|
||||
This is where GUIs should be loaded or tray specific parts should be
|
||||
prepared.
|
||||
prepared
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def tray_menu(self, tray_menu):
|
||||
def tray_menu(self, tray_menu: QtWidgets.QMenu) -> None:
|
||||
"""Add addon's action to tray menu."""
|
||||
raise NotImplementedError
|
||||
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def tray_start(self):
|
||||
def tray_start(self) -> None:
|
||||
"""Start procedure in tray tool."""
|
||||
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def tray_exit(self):
|
||||
def tray_exit(self) -> None:
|
||||
"""Cleanup method which is executed on tray shutdown.
|
||||
|
||||
This is place where all threads should be shut.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
pass
|
||||
def execute_in_main_thread(self, callback: Callable) -> None:
|
||||
"""Pushes callback to the queue or process 'callback' on a main thread.
|
||||
|
||||
def execute_in_main_thread(self, callback):
|
||||
""" Pushes callback to the queue or process 'callback' on a main thread
|
||||
Some callbacks need to be processed on main thread (menu actions
|
||||
must be added on main thread else they won't get triggered etc.)
|
||||
|
||||
Args:
|
||||
callback (Callable): Function to be executed on main thread
|
||||
|
||||
Some callbacks need to be processed on main thread (menu actions
|
||||
must be added on main thread or they won't get triggered etc.)
|
||||
"""
|
||||
|
||||
if not self.tray_initialized:
|
||||
# TODO Called without initialized tray, still main thread needed
|
||||
# TODO: Called without initialized tray, still main thread needed
|
||||
try:
|
||||
callback()
|
||||
|
||||
except Exception:
|
||||
except Exception: # noqa: BLE001
|
||||
self.log.warning(
|
||||
"Failed to execute {} in main thread".format(callback),
|
||||
exc_info=True)
|
||||
"Failed to execute %s callback in main thread",
|
||||
str(callback), exc_info=True)
|
||||
|
||||
return
|
||||
self.manager.tray_manager.execute_in_main_thread(callback)
|
||||
self._tray_manager.tray_manager.execute_in_main_thread(callback)
|
||||
|
||||
def show_tray_message(self, title, message, icon=None, msecs=None):
|
||||
def show_tray_message(
|
||||
self,
|
||||
title: str,
|
||||
message: str,
|
||||
icon: Optional[QtWidgets.QSystemTrayIcon]=None,
|
||||
msecs: Optional[int]=None) -> None:
|
||||
"""Show tray message.
|
||||
|
||||
Args:
|
||||
|
|
@ -190,11 +228,11 @@ class ITrayAddon(AYONInterface):
|
|||
msecs (int): Duration of message visibility in milliseconds.
|
||||
Default is 10000 msecs, may differ by Qt version.
|
||||
"""
|
||||
|
||||
if self._tray_manager:
|
||||
self._tray_manager.show_tray_message(title, message, icon, msecs)
|
||||
|
||||
def add_doubleclick_callback(self, callback):
|
||||
def add_doubleclick_callback(self, callback: Callable) -> None:
|
||||
"""Add callback to be triggered on tray icon double click."""
|
||||
if hasattr(self.manager, "add_doubleclick_callback"):
|
||||
self.manager.add_doubleclick_callback(self, callback)
|
||||
|
||||
|
|
@ -216,16 +254,17 @@ class ITrayAction(ITrayAddon):
|
|||
|
||||
@property
|
||||
@abstractmethod
|
||||
def label(self):
|
||||
def label(self) -> str:
|
||||
"""Service label showed in menu."""
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def on_action_trigger(self):
|
||||
def on_action_trigger(self) -> None:
|
||||
"""What happens on actions click."""
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
def tray_menu(self, tray_menu):
|
||||
def tray_menu(self, tray_menu: QtWidgets.QMenu) -> None:
|
||||
"""Add action to tray menu."""
|
||||
from qtpy import QtWidgets
|
||||
|
||||
if self.admin_action:
|
||||
|
|
@ -242,14 +281,17 @@ class ITrayAction(ITrayAddon):
|
|||
action.triggered.connect(self.on_action_trigger)
|
||||
self._action_item = action
|
||||
|
||||
def tray_start(self):
|
||||
def tray_start(self) -> None:
|
||||
"""Start procedure in tray tool."""
|
||||
return
|
||||
|
||||
def tray_exit(self):
|
||||
def tray_exit(self) -> None:
|
||||
"""Cleanup method which is executed on tray shutdown."""
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def admin_submenu(tray_menu):
|
||||
def admin_submenu(tray_menu: QtWidgets.QMenu) -> QtWidgets.QMenu:
|
||||
"""Get or create admin submenu."""
|
||||
if ITrayAction._admin_submenu is None:
|
||||
from qtpy import QtWidgets
|
||||
|
||||
|
|
@ -260,20 +302,21 @@ class ITrayAction(ITrayAddon):
|
|||
|
||||
|
||||
class ITrayService(ITrayAddon):
|
||||
"""Tray service Interface."""
|
||||
# Module's property
|
||||
menu_action = None
|
||||
menu_action: QtWidgets.QAction = None
|
||||
|
||||
# Class properties
|
||||
_services_submenu = None
|
||||
_icon_failed = None
|
||||
_icon_running = None
|
||||
_icon_idle = None
|
||||
_services_submenu: QtWidgets.QMenu = None
|
||||
_icon_failed: QtWidgets.QIcon = None
|
||||
_icon_running: QtWidgets.QIcon = None
|
||||
_icon_idle: QtWidgets.QIcon = None
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def label(self):
|
||||
def label(self) -> str:
|
||||
"""Service label showed in menu."""
|
||||
pass
|
||||
raise NotImplementedError
|
||||
|
||||
# TODO be able to get any sort of information to show/print
|
||||
# @abstractmethod
|
||||
|
|
@ -281,7 +324,8 @@ class ITrayService(ITrayAddon):
|
|||
# pass
|
||||
|
||||
@staticmethod
|
||||
def services_submenu(tray_menu):
|
||||
def services_submenu(tray_menu: QtWidgets.QMenu) -> QtWidgets.QMenu:
|
||||
"""Get or create services submenu."""
|
||||
if ITrayService._services_submenu is None:
|
||||
from qtpy import QtWidgets
|
||||
|
||||
|
|
@ -291,13 +335,15 @@ class ITrayService(ITrayAddon):
|
|||
return ITrayService._services_submenu
|
||||
|
||||
@staticmethod
|
||||
def add_service_action(action):
|
||||
def add_service_action(action: QtWidgets.QAction) -> None:
|
||||
"""Add service action to services submenu."""
|
||||
ITrayService._services_submenu.addAction(action)
|
||||
if not ITrayService._services_submenu.menuAction().isVisible():
|
||||
ITrayService._services_submenu.menuAction().setVisible(True)
|
||||
|
||||
@staticmethod
|
||||
def _load_service_icons():
|
||||
def _load_service_icons() -> None:
|
||||
"""Load service icons."""
|
||||
from qtpy import QtGui
|
||||
|
||||
ITrayService._failed_icon = QtGui.QIcon(
|
||||
|
|
@ -311,24 +357,28 @@ class ITrayService(ITrayAddon):
|
|||
)
|
||||
|
||||
@staticmethod
|
||||
def get_icon_running():
|
||||
def get_icon_running() -> QtWidgets.QIcon:
|
||||
"""Get running icon."""
|
||||
if ITrayService._icon_running is None:
|
||||
ITrayService._load_service_icons()
|
||||
return ITrayService._icon_running
|
||||
|
||||
@staticmethod
|
||||
def get_icon_idle():
|
||||
def get_icon_idle() -> QtWidgets.QIcon:
|
||||
"""Get idle icon."""
|
||||
if ITrayService._icon_idle is None:
|
||||
ITrayService._load_service_icons()
|
||||
return ITrayService._icon_idle
|
||||
|
||||
@staticmethod
|
||||
def get_icon_failed():
|
||||
if ITrayService._failed_icon is None:
|
||||
def get_icon_failed() -> QtWidgets.QIcon:
|
||||
"""Get failed icon."""
|
||||
if ITrayService._icon_failed is None:
|
||||
ITrayService._load_service_icons()
|
||||
return ITrayService._failed_icon
|
||||
return ITrayService._icon_failed
|
||||
|
||||
def tray_menu(self, tray_menu):
|
||||
def tray_menu(self, tray_menu: QtWidgets.QMenu) -> None:
|
||||
"""Add service to tray menu."""
|
||||
from qtpy import QtWidgets
|
||||
|
||||
action = QtWidgets.QAction(
|
||||
|
|
@ -341,21 +391,18 @@ class ITrayService(ITrayAddon):
|
|||
|
||||
self.set_service_running_icon()
|
||||
|
||||
def set_service_running_icon(self):
|
||||
def set_service_running_icon(self) -> None:
|
||||
"""Change icon of an QAction to green circle."""
|
||||
|
||||
if self.menu_action:
|
||||
self.menu_action.setIcon(self.get_icon_running())
|
||||
|
||||
def set_service_failed_icon(self):
|
||||
def set_service_failed_icon(self) -> None:
|
||||
"""Change icon of an QAction to red circle."""
|
||||
|
||||
if self.menu_action:
|
||||
self.menu_action.setIcon(self.get_icon_failed())
|
||||
|
||||
def set_service_idle_icon(self):
|
||||
def set_service_idle_icon(self) -> None:
|
||||
"""Change icon of an QAction to orange circle."""
|
||||
|
||||
if self.menu_action:
|
||||
self.menu_action.setIcon(self.get_icon_idle())
|
||||
|
||||
|
|
@ -365,18 +412,31 @@ class IHostAddon(AYONInterface):
|
|||
|
||||
@property
|
||||
@abstractmethod
|
||||
def host_name(self):
|
||||
def host_name(self) -> str:
|
||||
"""Name of host which addon represents."""
|
||||
raise NotImplementedError
|
||||
|
||||
pass
|
||||
|
||||
def get_workfile_extensions(self):
|
||||
def get_workfile_extensions(self) -> list[str]:
|
||||
"""Define workfile extensions for host.
|
||||
|
||||
Not all hosts support workfiles thus this is optional implementation.
|
||||
|
||||
Returns:
|
||||
List[str]: Extensions used for workfiles with dot.
|
||||
"""
|
||||
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class ITraits(AYONInterface):
|
||||
"""Interface for traits."""
|
||||
|
||||
@abstractmethod
|
||||
def get_addon_traits(self) -> list[Type[TraitBase]]:
|
||||
"""Get trait classes for the addon.
|
||||
|
||||
Returns:
|
||||
list[Type[TraitBase]]: Traits for the addon.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -271,13 +271,41 @@ rep = Representation.from_dict(name="image", rep_dict)
|
|||
|
||||
```
|
||||
|
||||
## Future
|
||||
|
||||
Apart of some new additions to traits if needed, there are few thing that needs to be done.
|
||||
## Addon specific traits
|
||||
|
||||
### Traits plugin system
|
||||
Addon can define its own traits. To do so, it needs to implement `ITraits` interface:
|
||||
|
||||
Traits are now ordinary python classes, but to extend its usability more, it would be good to
|
||||
have addon level API to expose traits defined by individual addons. This API would then be used not
|
||||
only by discovery logic but also by the AYON server that can display and work with the information
|
||||
defined by them.
|
||||
```python
|
||||
from ayon_core.pipeline.traits import TraitBase
|
||||
from ayon_core.addon import (
|
||||
AYONAddon,
|
||||
ITraits,
|
||||
)
|
||||
|
||||
class MyTraitFoo(TraitBase):
|
||||
id = "myaddon.mytrait.foo.v1"
|
||||
name = "My Trait Foo"
|
||||
description = "This is my trait foo"
|
||||
persistent = True
|
||||
|
||||
|
||||
class MyTraitBar(TraitBase):
|
||||
id = "myaddon.mytrait.bar.v1"
|
||||
name = "My Trait Bar"
|
||||
description = "This is my trait bar"
|
||||
persistent = True
|
||||
|
||||
|
||||
class MyAddon(AYONAddon, ITraits):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def get_addon_traits(self):
|
||||
return [
|
||||
MyTraitFoo,
|
||||
MyTraitBar,
|
||||
]
|
||||
|
||||
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue