add type hints

This commit is contained in:
Jakub Trllo 2025-09-26 15:03:57 +02:00
parent 7368ddfdfb
commit 1caedb841f

View file

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Base class for AYON addons.""" """Base class for AYON addons."""
from __future__ import annotations
import copy import copy
import os import os
import sys import sys
@ -11,10 +13,11 @@ import collections
import warnings import warnings
from uuid import uuid4 from uuid import uuid4
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Optional from types import ModuleType
import typing
from typing import Optional, Any, Union
import ayon_api import ayon_api
from semver import VersionInfo
from ayon_core import AYON_CORE_ROOT from ayon_core import AYON_CORE_ROOT
from ayon_core.lib import ( from ayon_core.lib import (
@ -30,6 +33,11 @@ from .interfaces import (
IHostAddon, IHostAddon,
) )
if typing.TYPE_CHECKING:
import click
from ayon_core.host import HostBase
# Files that will be always ignored on addons import # Files that will be always ignored on addons import
IGNORED_FILENAMES = { IGNORED_FILENAMES = {
"__pycache__", "__pycache__",
@ -101,7 +109,7 @@ class _LoadCache:
addon_modules = [] addon_modules = []
def load_addons(force=False): def load_addons(force: bool = False) -> None:
"""Load AYON addons as python modules. """Load AYON addons as python modules.
Modules does not load only classes (like in Interfaces) because there must Modules does not load only classes (like in Interfaces) because there must
@ -128,7 +136,7 @@ def load_addons(force=False):
time.sleep(0.1) time.sleep(0.1)
def _get_ayon_bundle_data(): def _get_ayon_bundle_data() -> Optional[dict[str, Any]]:
studio_bundle_name = os.environ.get("AYON_STUDIO_BUNDLE_NAME") studio_bundle_name = os.environ.get("AYON_STUDIO_BUNDLE_NAME")
project_bundle_name = os.getenv("AYON_BUNDLE_NAME") project_bundle_name = os.getenv("AYON_BUNDLE_NAME")
bundles = ayon_api.get_bundles()["bundles"] bundles = ayon_api.get_bundles()["bundles"]
@ -158,18 +166,21 @@ def _get_ayon_bundle_data():
return project_bundle return project_bundle
def _get_ayon_addons_information(bundle_info): def _get_ayon_addons_information(
bundle_info: dict[str, Any]
) -> list[dict[str, Any]]:
"""Receive information about addons to use from server. """Receive information about addons to use from server.
Todos: Todos:
Actually ask server for the information. Actually ask server for the information.
Allow project name as optional argument to be able to query information Allow project name as optional argument to be able to query information
about used addons for specific project. about used addons for specific project.
Wrap versions into an object.
Returns: Returns:
List[Dict[str, Any]]: List of addon information to use. list[dict[str, Any]]: List of addon information to use.
"""
"""
output = [] output = []
bundle_addons = bundle_info["addons"] bundle_addons = bundle_info["addons"]
addons = ayon_api.get_addons_info()["addons"] addons = ayon_api.get_addons_info()["addons"]
@ -188,7 +199,7 @@ def _get_ayon_addons_information(bundle_info):
return output return output
def _load_ayon_addons(log): def _load_ayon_addons(log: logging.Logger) -> list[ModuleType]:
"""Load AYON addons based on information from server. """Load AYON addons based on information from server.
This function should not trigger downloading of any addons but only use This function should not trigger downloading of any addons but only use
@ -198,6 +209,9 @@ def _load_ayon_addons(log):
Args: Args:
log (logging.Logger): Logger object. log (logging.Logger): Logger object.
Returns:
list[ModuleType]: Loaded addon modules.
""" """
all_addon_modules = [] all_addon_modules = []
bundle_info = _get_ayon_bundle_data() bundle_info = _get_ayon_bundle_data()
@ -325,20 +339,21 @@ class AYONAddon(ABC):
Attributes: Attributes:
enabled (bool): Is addon enabled. enabled (bool): Is addon enabled.
name (str): Addon name.
Args: Args:
manager (AddonsManager): Manager object who discovered addon. manager (AddonsManager): Manager object who discovered addon.
settings (dict[str, Any]): AYON settings. settings (dict[str, Any]): AYON settings.
""" """
enabled = True enabled: bool = True
_id = None _id = None
# Temporary variable for 'version' property # Temporary variable for 'version' property
_missing_version_warned = False _missing_version_warned = False
def __init__(self, manager, settings): def __init__(
self, manager: AddonsManager, settings: dict[str, Any]
) -> None:
self.manager = manager self.manager = manager
self.log = Logger.get_logger(self.name) self.log = Logger.get_logger(self.name)
@ -346,7 +361,7 @@ class AYONAddon(ABC):
self.initialize(settings) self.initialize(settings)
@property @property
def id(self): def id(self) -> str:
"""Random id of addon object. """Random id of addon object.
Returns: Returns:
@ -359,7 +374,7 @@ class AYONAddon(ABC):
@property @property
@abstractmethod @abstractmethod
def name(self): def name(self) -> str:
"""Addon name. """Addon name.
Returns: Returns:
@ -369,7 +384,7 @@ class AYONAddon(ABC):
pass pass
@property @property
def version(self): def version(self) -> str:
"""Addon version. """Addon version.
Todo: Todo:
@ -388,7 +403,7 @@ class AYONAddon(ABC):
) )
return "0.0.0" return "0.0.0"
def initialize(self, settings): def initialize(self, settings: dict[str, Any]) -> None:
"""Initialization of addon attributes. """Initialization of addon attributes.
It is not recommended to override __init__ that's why specific method It is not recommended to override __init__ that's why specific method
@ -400,7 +415,7 @@ class AYONAddon(ABC):
""" """
pass pass
def connect_with_addons(self, enabled_addons): def connect_with_addons(self, enabled_addons: list[AYONAddon]) -> None:
"""Connect with other enabled addons. """Connect with other enabled addons.
Args: Args:
@ -411,7 +426,7 @@ class AYONAddon(ABC):
def ensure_is_process_ready( def ensure_is_process_ready(
self, process_context: ProcessContext self, process_context: ProcessContext
): ) -> None:
"""Make sure addon is prepared for a process. """Make sure addon is prepared for a process.
This method is called when some action makes sure that addon has set This method is called when some action makes sure that addon has set
@ -432,7 +447,7 @@ class AYONAddon(ABC):
""" """
pass pass
def get_global_environments(self): def get_global_environments(self) -> dict[str, str]:
"""Get global environments values of addon. """Get global environments values of addon.
Environment variables that can be get only from system settings. Environment variables that can be get only from system settings.
@ -443,20 +458,12 @@ class AYONAddon(ABC):
""" """
return {} return {}
def modify_application_launch_arguments(self, application, env): def on_host_install(
"""Give option to modify launch environments before application launch. self,
host: HostBase,
Implementation is optional. To change environments modify passed host_name: str,
dictionary of environments. project_name: str,
) -> None:
Args:
application (Application): Application that is launched.
env (dict[str, str]): Current environment variables.
"""
pass
def on_host_install(self, host, host_name, project_name):
"""Host was installed which gives option to handle in-host logic. """Host was installed which gives option to handle in-host logic.
It is a good option to register in-host event callbacks which are It is a good option to register in-host event callbacks which are
@ -467,7 +474,7 @@ class AYONAddon(ABC):
to receive from 'host' object. to receive from 'host' object.
Args: Args:
host (Union[ModuleType, HostBase]): Access to installed/registered host (HostBase): Access to installed/registered
host object. host object.
host_name (str): Name of host. host_name (str): Name of host.
project_name (str): Project name which is main part of host project_name (str): Project name which is main part of host
@ -476,7 +483,7 @@ class AYONAddon(ABC):
""" """
pass pass
def cli(self, addon_click_group): def cli(self, addon_click_group: click.Group) -> None:
"""Add commands to click group. """Add commands to click group.
The best practise is to create click group for whole addon which is The best practise is to create click group for whole addon which is
@ -507,15 +514,21 @@ class AYONAddon(ABC):
class _AddonReportInfo: class _AddonReportInfo:
def __init__( def __init__(
self, class_name, name, version, report_value_by_label self,
): class_name: str,
name: str,
version: str,
report_value_by_label: dict[str, Optional[str]],
) -> None:
self.class_name = class_name self.class_name = class_name
self.name = name self.name = name
self.version = version self.version = version
self.report_value_by_label = report_value_by_label self.report_value_by_label = report_value_by_label
@classmethod @classmethod
def from_addon(cls, addon, report): def from_addon(
cls, addon: AYONAddon, report: dict[str, dict[str, int]]
) -> "_AddonReportInfo":
class_name = addon.__class__.__name__ class_name = addon.__class__.__name__
report_value_by_label = { report_value_by_label = {
label: reported.get(class_name) label: reported.get(class_name)
@ -542,29 +555,35 @@ class AddonsManager:
_report_total_key = "Total" _report_total_key = "Total"
_log = None _log = None
def __init__(self, settings=None, initialize=True): def __init__(
self,
settings: Optional[dict[str, Any]] = None,
initialize: bool = True,
) -> None:
self._settings = settings self._settings = settings
self._addons = [] self._addons: list[AYONAddon] = []
self._addons_by_id = {} self._addons_by_id: dict[str, AYONAddon] = {}
self._addons_by_name = {} self._addons_by_name: dict[str, AYONAddon] = {}
# For report of time consumption # For report of time consumption
self._report = {} self._report: dict[str, dict[str, int]] = {}
if initialize: if initialize:
self.initialize_addons() self.initialize_addons()
self.connect_addons() self.connect_addons()
def __getitem__(self, addon_name): def __getitem__(self, addon_name: str) -> AYONAddon:
return self._addons_by_name[addon_name] return self._addons_by_name[addon_name]
@property @property
def log(self): def log(self) -> logging.Logger:
if self._log is None: if self._log is None:
self._log = logging.getLogger(self.__class__.__name__) self._log = Logger.get_logger(self.__class__.__name__)
return self._log return self._log
def get(self, addon_name, default=None): def get(
self, addon_name: str, default: Optional[Any] = None
) -> Union[AYONAddon, Any]:
"""Access addon by name. """Access addon by name.
Args: Args:
@ -578,18 +597,20 @@ class AddonsManager:
return self._addons_by_name.get(addon_name, default) return self._addons_by_name.get(addon_name, default)
@property @property
def addons(self): def addons(self) -> list[AYONAddon]:
return list(self._addons) return list(self._addons)
@property @property
def addons_by_id(self): def addons_by_id(self) -> dict[str, AYONAddon]:
return dict(self._addons_by_id) return dict(self._addons_by_id)
@property @property
def addons_by_name(self): def addons_by_name(self) -> dict[str, AYONAddon]:
return dict(self._addons_by_name) return dict(self._addons_by_name)
def get_enabled_addon(self, addon_name, default=None): def get_enabled_addon(
self, addon_name: str, default: Optional[Any] = None
) -> Union[AYONAddon, Any]:
"""Fast access to enabled addon. """Fast access to enabled addon.
If addon is available but is not enabled default value is returned. If addon is available but is not enabled default value is returned.
@ -600,7 +621,7 @@ class AddonsManager:
not enabled. not enabled.
Returns: Returns:
Union[AYONAddon, None]: Enabled addon found by name or None. Union[AYONAddon, Any]: Enabled addon found by name or None.
""" """
addon = self.get(addon_name) addon = self.get(addon_name)
@ -608,7 +629,7 @@ class AddonsManager:
return addon return addon
return default return default
def get_enabled_addons(self): def get_enabled_addons(self) -> list[AYONAddon]:
"""Enabled addons initialized by the manager. """Enabled addons initialized by the manager.
Returns: Returns:
@ -621,7 +642,7 @@ class AddonsManager:
if addon.enabled if addon.enabled
] ]
def initialize_addons(self): def initialize_addons(self) -> None:
"""Import and initialize addons.""" """Import and initialize addons."""
# Make sure modules are loaded # Make sure modules are loaded
load_addons() load_addons()
@ -702,7 +723,7 @@ class AddonsManager:
report[self._report_total_key] = time.time() - time_start report[self._report_total_key] = time.time() - time_start
self._report["Initialization"] = report self._report["Initialization"] = report
def connect_addons(self): def connect_addons(self) -> None:
"""Trigger connection with other enabled addons. """Trigger connection with other enabled addons.
Addons should handle their interfaces in `connect_with_addons`. Addons should handle their interfaces in `connect_with_addons`.
@ -730,7 +751,7 @@ class AddonsManager:
report[self._report_total_key] = time.time() - time_start report[self._report_total_key] = time.time() - time_start
self._report["Connect modules"] = report self._report["Connect modules"] = report
def collect_global_environments(self): def collect_global_environments(self) -> dict[str, str]:
"""Helper to collect global environment variabled from modules. """Helper to collect global environment variabled from modules.
Returns: Returns:
@ -753,7 +774,7 @@ class AddonsManager:
module_envs[key] = value module_envs[key] = value
return module_envs return module_envs
def collect_plugin_paths(self): def collect_plugin_paths(self) -> dict[str, list[str]]:
"""Helper to collect all plugins from modules inherited IPluginPaths. """Helper to collect all plugins from modules inherited IPluginPaths.
Unknown keys are logged out. Unknown keys are logged out.
@ -828,7 +849,7 @@ class AddonsManager:
) )
return output return output
def _collect_plugin_paths(self, method_name, *args, **kwargs): def _collect_plugin_paths(self, method_name: str, *args, **kwargs):
output = [] output = []
for addon in self.get_enabled_addons(): for addon in self.get_enabled_addons():
# Skip addon that do not inherit from `IPluginPaths` # Skip addon that do not inherit from `IPluginPaths`
@ -859,7 +880,7 @@ class AddonsManager:
output.extend(paths) output.extend(paths)
return output return output
def collect_launcher_action_paths(self): def collect_launcher_action_paths(self) -> list[str]:
"""Helper to collect launcher action paths from addons. """Helper to collect launcher action paths from addons.
Returns: Returns:
@ -874,16 +895,16 @@ class AddonsManager:
output.insert(0, actions_dir) output.insert(0, actions_dir)
return output return output
def collect_create_plugin_paths(self, host_name): def collect_create_plugin_paths(self, host_name: str) -> list[str]:
"""Helper to collect creator plugin paths from addons. """Helper to collect creator plugin paths from addons.
Args: Args:
host_name (str): For which host are creators meant. host_name (str): For which host are creators meant.
Returns: Returns:
list: List of creator plugin paths. list[str]: List of creator plugin paths.
"""
"""
return self._collect_plugin_paths( return self._collect_plugin_paths(
"get_create_plugin_paths", "get_create_plugin_paths",
host_name host_name
@ -891,37 +912,37 @@ class AddonsManager:
collect_creator_plugin_paths = collect_create_plugin_paths collect_creator_plugin_paths = collect_create_plugin_paths
def collect_load_plugin_paths(self, host_name): def collect_load_plugin_paths(self, host_name: str) -> list[str]:
"""Helper to collect load plugin paths from addons. """Helper to collect load plugin paths from addons.
Args: Args:
host_name (str): For which host are load plugins meant. host_name (str): For which host are load plugins meant.
Returns: Returns:
list: List of load plugin paths. list[str]: List of load plugin paths.
"""
"""
return self._collect_plugin_paths( return self._collect_plugin_paths(
"get_load_plugin_paths", "get_load_plugin_paths",
host_name host_name
) )
def collect_publish_plugin_paths(self, host_name): def collect_publish_plugin_paths(self, host_name: str) -> list[str]:
"""Helper to collect load plugin paths from addons. """Helper to collect load plugin paths from addons.
Args: Args:
host_name (str): For which host are load plugins meant. host_name (str): For which host are load plugins meant.
Returns: Returns:
list: List of pyblish plugin paths. list[str]: List of pyblish plugin paths.
"""
"""
return self._collect_plugin_paths( return self._collect_plugin_paths(
"get_publish_plugin_paths", "get_publish_plugin_paths",
host_name host_name
) )
def collect_inventory_action_paths(self, host_name): def collect_inventory_action_paths(self, host_name: str) -> list[str]:
"""Helper to collect load plugin paths from addons. """Helper to collect load plugin paths from addons.
Args: Args:
@ -929,21 +950,21 @@ class AddonsManager:
Returns: Returns:
list: List of pyblish plugin paths. list: List of pyblish plugin paths.
"""
"""
return self._collect_plugin_paths( return self._collect_plugin_paths(
"get_inventory_action_paths", "get_inventory_action_paths",
host_name host_name
) )
def get_host_addon(self, host_name): def get_host_addon(self, host_name: str) -> Optional[AYONAddon]:
"""Find host addon by host name. """Find host addon by host name.
Args: Args:
host_name (str): Host name for which is found host addon. host_name (str): Host name for which is found host addon.
Returns: Returns:
Union[AYONAddon, None]: Found host addon by name or `None`. Optional[AYONAddon]: Found host addon by name or `None`.
""" """
for addon in self.get_enabled_addons(): for addon in self.get_enabled_addons():
@ -954,21 +975,21 @@ class AddonsManager:
return addon return addon
return None return None
def get_host_names(self): def get_host_names(self) -> set[str]:
"""List of available host names based on host addons. """List of available host names based on host addons.
Returns: Returns:
Iterable[str]: All available host names based on enabled addons set[str]: All available host names based on enabled addons
inheriting 'IHostAddon'. inheriting 'IHostAddon'.
"""
"""
return { return {
addon.host_name addon.host_name
for addon in self.get_enabled_addons() for addon in self.get_enabled_addons()
if isinstance(addon, IHostAddon) if isinstance(addon, IHostAddon)
} }
def print_report(self): def print_report(self) -> None:
"""Print out report of time spent on addons initialization parts. """Print out report of time spent on addons initialization parts.
Reporting is not automated must be implemented for each initialization Reporting is not automated must be implemented for each initialization