mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #1222 from ynput/feature/AY-2218_Plugin-hooks-Loader-and-Scene-Inventory
Pre and post loader hooks
This commit is contained in:
commit
b61d36867a
4 changed files with 196 additions and 11 deletions
|
|
@ -49,6 +49,11 @@ from .plugins import (
|
|||
deregister_loader_plugin_path,
|
||||
register_loader_plugin_path,
|
||||
deregister_loader_plugin,
|
||||
|
||||
register_loader_hook_plugin,
|
||||
deregister_loader_hook_plugin,
|
||||
register_loader_hook_plugin_path,
|
||||
deregister_loader_hook_plugin_path,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -103,4 +108,10 @@ __all__ = (
|
|||
"deregister_loader_plugin_path",
|
||||
"register_loader_plugin_path",
|
||||
"deregister_loader_plugin",
|
||||
|
||||
"register_loader_hook_plugin",
|
||||
"deregister_loader_hook_plugin",
|
||||
"register_loader_hook_plugin_path",
|
||||
"deregister_loader_hook_plugin_path",
|
||||
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
from __future__ import annotations
|
||||
import os
|
||||
import logging
|
||||
from typing import Any, Type, Optional
|
||||
from abc import abstractmethod
|
||||
|
||||
from ayon_core.settings import get_project_settings
|
||||
from ayon_core.pipeline.plugin_discover import (
|
||||
|
|
@ -251,15 +254,94 @@ class ProductLoaderPlugin(LoaderPlugin):
|
|||
"""
|
||||
|
||||
|
||||
class LoaderHookPlugin:
|
||||
"""Plugin that runs before and post specific Loader in 'loaders'
|
||||
|
||||
Should be used as non-invasive method to enrich core loading process.
|
||||
Any studio might want to modify loaded data before or after
|
||||
they are loaded without need to override existing core plugins.
|
||||
|
||||
The post methods are called after the loader's methods and receive the
|
||||
return value of the loader's method as `result` argument.
|
||||
"""
|
||||
order = 0
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def is_compatible(cls, Loader: Type[LoaderPlugin]) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pre_load(
|
||||
self,
|
||||
plugin: LoaderPlugin,
|
||||
context: dict,
|
||||
name: Optional[str],
|
||||
namespace: Optional[str],
|
||||
options: Optional[dict],
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def post_load(
|
||||
self,
|
||||
plugin: LoaderPlugin,
|
||||
result: Any,
|
||||
context: dict,
|
||||
name: Optional[str],
|
||||
namespace: Optional[str],
|
||||
options: Optional[dict],
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pre_update(
|
||||
self,
|
||||
plugin: LoaderPlugin,
|
||||
container: dict, # (ayon:container-3.0)
|
||||
context: dict,
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def post_update(
|
||||
self,
|
||||
plugin: LoaderPlugin,
|
||||
result: Any,
|
||||
container: dict, # (ayon:container-3.0)
|
||||
context: dict,
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def pre_remove(
|
||||
self,
|
||||
plugin: LoaderPlugin,
|
||||
container: dict, # (ayon:container-3.0)
|
||||
):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def post_remove(
|
||||
self,
|
||||
plugin: LoaderPlugin,
|
||||
result: Any,
|
||||
container: dict, # (ayon:container-3.0)
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
def discover_loader_plugins(project_name=None):
|
||||
from ayon_core.lib import Logger
|
||||
from ayon_core.pipeline import get_current_project_name
|
||||
|
||||
log = Logger.get_logger("LoaderDiscover")
|
||||
plugins = discover(LoaderPlugin)
|
||||
if not project_name:
|
||||
project_name = get_current_project_name()
|
||||
project_settings = get_project_settings(project_name)
|
||||
plugins = discover(LoaderPlugin)
|
||||
hooks = discover(LoaderHookPlugin)
|
||||
sorted_hooks = sorted(hooks, key=lambda hook: hook.order)
|
||||
for plugin in plugins:
|
||||
try:
|
||||
plugin.apply_settings(project_settings)
|
||||
|
|
@ -268,11 +350,58 @@ def discover_loader_plugins(project_name=None):
|
|||
"Failed to apply settings to loader {}".format(
|
||||
plugin.__name__
|
||||
),
|
||||
exc_info=True
|
||||
exc_info=True,
|
||||
)
|
||||
compatible_hooks = []
|
||||
for hook_cls in sorted_hooks:
|
||||
if hook_cls.is_compatible(plugin):
|
||||
compatible_hooks.append(hook_cls)
|
||||
add_hooks_to_loader(plugin, compatible_hooks)
|
||||
return plugins
|
||||
|
||||
|
||||
def add_hooks_to_loader(
|
||||
loader_class: LoaderPlugin, compatible_hooks: list[Type[LoaderHookPlugin]]
|
||||
) -> None:
|
||||
"""Monkey patch method replacing Loader.load|update|remove methods
|
||||
|
||||
It wraps applicable loaders with pre/post hooks. Discovery is called only
|
||||
once per loaders discovery.
|
||||
"""
|
||||
loader_class._load_hooks = compatible_hooks
|
||||
|
||||
def wrap_method(method_name: str):
|
||||
original_method = getattr(loader_class, method_name)
|
||||
|
||||
def wrapped_method(self, *args, **kwargs):
|
||||
# Call pre_<method_name> on all hooks
|
||||
pre_hook_name = f"pre_{method_name}"
|
||||
|
||||
hooks: list[LoaderHookPlugin] = []
|
||||
for cls in loader_class._load_hooks:
|
||||
hook = cls() # Instantiate the hook
|
||||
hooks.append(hook)
|
||||
pre_hook = getattr(hook, pre_hook_name, None)
|
||||
if callable(pre_hook):
|
||||
pre_hook(self, *args, **kwargs)
|
||||
# Call original method
|
||||
result = original_method(self, *args, **kwargs)
|
||||
# Call post_<method_name> on all hooks
|
||||
post_hook_name = f"post_{method_name}"
|
||||
for hook in hooks:
|
||||
post_hook = getattr(hook, post_hook_name, None)
|
||||
if callable(post_hook):
|
||||
post_hook(self, result, *args, **kwargs)
|
||||
|
||||
return result
|
||||
|
||||
setattr(loader_class, method_name, wrapped_method)
|
||||
|
||||
for method in ("load", "update", "remove"):
|
||||
if hasattr(loader_class, method):
|
||||
wrap_method(method)
|
||||
|
||||
|
||||
def register_loader_plugin(plugin):
|
||||
return register_plugin(LoaderPlugin, plugin)
|
||||
|
||||
|
|
@ -287,3 +416,19 @@ def deregister_loader_plugin_path(path):
|
|||
|
||||
def register_loader_plugin_path(path):
|
||||
return register_plugin_path(LoaderPlugin, path)
|
||||
|
||||
|
||||
def register_loader_hook_plugin(plugin):
|
||||
return register_plugin(LoaderHookPlugin, plugin)
|
||||
|
||||
|
||||
def deregister_loader_hook_plugin(plugin):
|
||||
deregister_plugin(LoaderHookPlugin, plugin)
|
||||
|
||||
|
||||
def register_loader_hook_plugin_path(path):
|
||||
return register_plugin_path(LoaderHookPlugin, path)
|
||||
|
||||
|
||||
def deregister_loader_hook_plugin_path(path):
|
||||
deregister_plugin_path(LoaderHookPlugin, path)
|
||||
|
|
|
|||
|
|
@ -288,7 +288,12 @@ def get_representation_context(project_name, representation):
|
|||
|
||||
|
||||
def load_with_repre_context(
|
||||
Loader, repre_context, namespace=None, name=None, options=None, **kwargs
|
||||
Loader,
|
||||
repre_context,
|
||||
namespace=None,
|
||||
name=None,
|
||||
options=None,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
# Ensure the Loader is compatible for the representation
|
||||
|
|
@ -320,7 +325,12 @@ def load_with_repre_context(
|
|||
|
||||
|
||||
def load_with_product_context(
|
||||
Loader, product_context, namespace=None, name=None, options=None, **kwargs
|
||||
Loader,
|
||||
product_context,
|
||||
namespace=None,
|
||||
name=None,
|
||||
options=None,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
# Ensure options is a dictionary when no explicit options provided
|
||||
|
|
@ -343,7 +353,12 @@ def load_with_product_context(
|
|||
|
||||
|
||||
def load_with_product_contexts(
|
||||
Loader, product_contexts, namespace=None, name=None, options=None, **kwargs
|
||||
Loader,
|
||||
product_contexts,
|
||||
namespace=None,
|
||||
name=None,
|
||||
options=None,
|
||||
**kwargs
|
||||
):
|
||||
|
||||
# Ensure options is a dictionary when no explicit options provided
|
||||
|
|
@ -553,15 +568,20 @@ def update_container(container, version=-1):
|
|||
return Loader().update(container, context)
|
||||
|
||||
|
||||
def switch_container(container, representation, loader_plugin=None):
|
||||
def switch_container(
|
||||
container,
|
||||
representation,
|
||||
loader_plugin=None,
|
||||
):
|
||||
"""Switch a container to representation
|
||||
|
||||
Args:
|
||||
container (dict): container information
|
||||
representation (dict): representation entity
|
||||
loader_plugin (LoaderPlugin)
|
||||
|
||||
Returns:
|
||||
function call
|
||||
return from function call
|
||||
"""
|
||||
from ayon_core.pipeline import get_current_project_name
|
||||
|
||||
|
|
|
|||
|
|
@ -322,7 +322,6 @@ class LoaderActionsModel:
|
|||
available_loaders = self._filter_loaders_by_tool_name(
|
||||
project_name, discover_loader_plugins(project_name)
|
||||
)
|
||||
|
||||
repre_loaders = []
|
||||
product_loaders = []
|
||||
loaders_by_identifier = {}
|
||||
|
|
@ -340,6 +339,7 @@ class LoaderActionsModel:
|
|||
loaders_by_identifier_c.update_data(loaders_by_identifier)
|
||||
product_loaders_c.update_data(product_loaders)
|
||||
repre_loaders_c.update_data(repre_loaders)
|
||||
|
||||
return product_loaders, repre_loaders
|
||||
|
||||
def _get_loader_by_identifier(self, project_name, identifier):
|
||||
|
|
@ -719,7 +719,12 @@ class LoaderActionsModel:
|
|||
loader, repre_contexts, options
|
||||
)
|
||||
|
||||
def _load_representations_by_loader(self, loader, repre_contexts, options):
|
||||
def _load_representations_by_loader(
|
||||
self,
|
||||
loader,
|
||||
repre_contexts,
|
||||
options
|
||||
):
|
||||
"""Loops through list of repre_contexts and loads them with one loader
|
||||
|
||||
Args:
|
||||
|
|
@ -770,7 +775,12 @@ class LoaderActionsModel:
|
|||
))
|
||||
return error_info
|
||||
|
||||
def _load_products_by_loader(self, loader, version_contexts, options):
|
||||
def _load_products_by_loader(
|
||||
self,
|
||||
loader,
|
||||
version_contexts,
|
||||
options
|
||||
):
|
||||
"""Triggers load with ProductLoader type of loaders.
|
||||
|
||||
Warning:
|
||||
|
|
@ -796,7 +806,6 @@ class LoaderActionsModel:
|
|||
version_contexts,
|
||||
options=options
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
formatted_traceback = None
|
||||
if not isinstance(exc, LoadError):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue