Merge pull request #3623 from pypeclub/feature/OP-3679_Plugin-settings-handled-by-plugins

General: Plugin settings handled by plugins
This commit is contained in:
Jakub Trllo 2022-08-05 18:26:59 +02:00 committed by GitHub
commit 3ffa995ed6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 276 additions and 58 deletions

View file

@ -1,11 +1,13 @@
# -*- coding: utf-8 -*-
"""Avalon/Pyblish plugin tools."""
import os
import inspect
import logging
import re
import json
import warnings
import functools
from openpype.client import get_asset_by_id
from openpype.settings import get_project_settings
@ -17,6 +19,51 @@ log = logging.getLogger(__name__)
DEFAULT_SUBSET_TEMPLATE = "{family}{Variant}"
class PluginToolsDeprecatedWarning(DeprecationWarning):
pass
def deprecated(new_destination):
"""Mark functions as deprecated.
It will result in a warning being emitted when the function is used.
"""
func = None
if callable(new_destination):
func = new_destination
new_destination = None
def _decorator(decorated_func):
if new_destination is None:
warning_message = (
" Please check content of deprecated function to figure out"
" possible replacement."
)
else:
warning_message = " Please replace your usage with '{}'.".format(
new_destination
)
@functools.wraps(decorated_func)
def wrapper(*args, **kwargs):
warnings.simplefilter("always", PluginToolsDeprecatedWarning)
warnings.warn(
(
"Call to deprecated function '{}'"
"\nFunction was moved or removed.{}"
).format(decorated_func.__name__, warning_message),
category=PluginToolsDeprecatedWarning,
stacklevel=4
)
return decorated_func(*args, **kwargs)
return wrapper
if func is None:
return _decorator
return _decorator(func)
class TaskNotSetError(KeyError):
def __init__(self, msg=None):
if not msg:
@ -197,6 +244,7 @@ def prepare_template_data(fill_pairs):
return fill_data
@deprecated("openpype.pipeline.publish.lib.filter_pyblish_plugins")
def filter_pyblish_plugins(plugins):
"""Filter pyblish plugins by presets.
@ -206,57 +254,14 @@ def filter_pyblish_plugins(plugins):
Args:
plugins (dict): Dictionary of plugins produced by :mod:`pyblish-base`
`discover()` method.
"""
from pyblish import api
host = api.current_host()
from openpype.pipeline.publish.lib import filter_pyblish_plugins
presets = get_project_settings(os.environ['AVALON_PROJECT']) or {}
# skip if there are no presets to process
if not presets:
return
# iterate over plugins
for plugin in plugins[:]:
try:
config_data = presets[host]["publish"][plugin.__name__]
except KeyError:
# host determined from path
file = os.path.normpath(inspect.getsourcefile(plugin))
file = os.path.normpath(file)
split_path = file.split(os.path.sep)
if len(split_path) < 4:
log.warning(
'plugin path too short to extract host {}'.format(file)
)
continue
host_from_file = split_path[-4]
plugin_kind = split_path[-2]
# TODO: change after all plugins are moved one level up
if host_from_file == "openpype":
host_from_file = "global"
try:
config_data = presets[host_from_file][plugin_kind][plugin.__name__] # noqa: E501
except KeyError:
continue
for option, value in config_data.items():
if option == "enabled" and value is False:
log.info('removing plugin {}'.format(plugin.__name__))
plugins.remove(plugin)
else:
log.info('setting {}:{} on plugin {}'.format(
option, value, plugin.__name__))
setattr(plugin, option, value)
filter_pyblish_plugins(plugins)
@deprecated
def set_plugin_attributes_from_settings(
plugins, superclass, host_name=None, project_name=None
):
@ -273,6 +278,8 @@ def set_plugin_attributes_from_settings(
project_name (str): Name of project for which settings will be loaded.
Value from environment `AVALON_PROJECT` is used if not entered.
"""
# Function is not used anymore
from openpype.pipeline import LegacyCreator, LoaderPlugin
# determine host application to use for finding presets

View file

@ -18,8 +18,8 @@ from openpype.client import (
)
from openpype.modules import load_modules, ModulesManager
from openpype.settings import get_project_settings
from openpype.lib import filter_pyblish_plugins
from .publish.lib import filter_pyblish_plugins
from .anatomy import Anatomy
from .template_data import get_template_data_with_names
from . import (

View file

@ -1,3 +1,4 @@
import os
import copy
from abc import (
@ -7,10 +8,8 @@ from abc import (
)
import six
from openpype.lib import (
get_subset_name_with_asset_doc,
set_plugin_attributes_from_settings,
)
from openpype.settings import get_system_settings, get_project_settings
from openpype.lib import get_subset_name_with_asset_doc
from openpype.pipeline.plugin_discover import (
discover,
register_plugin,
@ -438,8 +437,24 @@ def discover_creator_plugins():
def discover_legacy_creator_plugins():
from openpype.lib import Logger
log = Logger.get_logger("CreatorDiscover")
plugins = discover(LegacyCreator)
set_plugin_attributes_from_settings(plugins, LegacyCreator)
project_name = os.environ.get("AVALON_PROJECT")
system_settings = get_system_settings()
project_settings = get_project_settings(project_name)
for plugin in plugins:
try:
plugin.apply_settings(project_settings, system_settings)
except Exception:
log.warning(
"Failed to apply settings to loader {}".format(
plugin.__name__
),
exc_info=True
)
return plugins

View file

@ -5,6 +5,7 @@ Renamed classes and functions
- 'create' -> 'legacy_create'
"""
import os
import logging
import collections
@ -37,6 +38,48 @@ class LegacyCreator(object):
self.data.update(data or {})
@classmethod
def apply_settings(cls, project_settings, system_settings):
"""Apply OpenPype settings to a plugin class."""
host_name = os.environ.get("AVALON_APP")
plugin_type = "create"
plugin_type_settings = (
project_settings
.get(host_name, {})
.get(plugin_type, {})
)
global_type_settings = (
project_settings
.get("global", {})
.get(plugin_type, {})
)
if not global_type_settings and not plugin_type_settings:
return
plugin_name = cls.__name__
plugin_settings = None
# Look for plugin settings in host specific settings
if plugin_name in plugin_type_settings:
plugin_settings = plugin_type_settings[plugin_name]
# Look for plugin settings in global settings
elif plugin_name in global_type_settings:
plugin_settings = global_type_settings[plugin_name]
if not plugin_settings:
return
print(">>> We have preset for {}".format(plugin_name))
for option, value in plugin_settings.items():
if option == "enabled" and value is False:
setattr(cls, "active", False)
print(" - is disabled by preset")
else:
setattr(cls, option, value)
print(" - setting `{}`: `{}`".format(option, value))
def process(self):
pass

View file

@ -1,6 +1,8 @@
import os
import logging
from openpype.lib import set_plugin_attributes_from_settings
from openpype.settings import get_system_settings, get_project_settings
from openpype.pipeline import legacy_io
from openpype.pipeline.plugin_discover import (
discover,
register_plugin,
@ -37,6 +39,46 @@ class LoaderPlugin(list):
def __init__(self, context):
self.fname = self.filepath_from_context(context)
@classmethod
def apply_settings(cls, project_settings, system_settings):
host_name = os.environ.get("AVALON_APP")
plugin_type = "load"
plugin_type_settings = (
project_settings
.get(host_name, {})
.get(plugin_type, {})
)
global_type_settings = (
project_settings
.get("global", {})
.get(plugin_type, {})
)
if not global_type_settings and not plugin_type_settings:
return
plugin_name = cls.__name__
plugin_settings = None
# Look for plugin settings in host specific settings
if plugin_name in plugin_type_settings:
plugin_settings = plugin_type_settings[plugin_name]
# Look for plugin settings in global settings
elif plugin_name in global_type_settings:
plugin_settings = global_type_settings[plugin_name]
if not plugin_settings:
return
print(">>> We have preset for {}".format(plugin_name))
for option, value in plugin_settings.items():
if option == "enabled" and value is False:
setattr(cls, "active", False)
print(" - is disabled by preset")
else:
setattr(cls, option, value)
print(" - setting `{}`: `{}`".format(option, value))
@classmethod
def get_representations(cls):
return cls.representations
@ -110,9 +152,25 @@ class SubsetLoaderPlugin(LoaderPlugin):
pass
def discover_loader_plugins():
def discover_loader_plugins(project_name=None):
from openpype.lib import Logger
log = Logger.get_logger("LoaderDiscover")
plugins = discover(LoaderPlugin)
set_plugin_attributes_from_settings(plugins, LoaderPlugin)
if not project_name:
project_name = legacy_io.active_project()
system_settings = get_system_settings()
project_settings = get_project_settings(project_name)
for plugin in plugins:
try:
plugin.apply_settings(project_settings, system_settings)
except Exception:
log.warning(
"Failed to apply settings to loader {}".format(
plugin.__name__
),
exc_info=True
)
return plugins

View file

@ -6,6 +6,10 @@ import xml.etree.ElementTree
import six
import pyblish.plugin
import pyblish.api
from openpype.lib import Logger
from openpype.settings import get_project_settings, get_system_settings
class DiscoverResult:
@ -180,3 +184,92 @@ def publish_plugins_discover(paths=None):
result.plugins = plugins
return result
def filter_pyblish_plugins(plugins):
"""Pyblish plugin filter which applies OpenPype settings.
Apply OpenPype settings on discovered plugins. On plugin with implemented
class method 'def apply_settings(cls, project_settings, system_settings)'
is called the method. Default behavior looks for plugin name and current
host name to look for
Args:
plugins (List[pyblish.plugin.Plugin]): Discovered plugins on which
are applied settings.
"""
log = Logger.get_logger("filter_pyblish_plugins")
# TODO: Don't use host from 'pyblish.api' but from defined host by us.
# - kept becau on farm is probably used host 'shell' which propably
# affect how settings are applied there
host = pyblish.api.current_host()
project_name = os.environ.get("AVALON_PROJECT")
project_setting = get_project_settings(project_name)
system_settings = get_system_settings()
# iterate over plugins
for plugin in plugins[:]:
if hasattr(plugin, "apply_settings"):
try:
# Use classmethod 'apply_settings'
# - can be used to target settings from custom settings place
# - skip default behavior when successful
plugin.apply_settings(project_setting, system_settings)
continue
except Exception:
log.warning(
(
"Failed to apply settings on plugin {}"
).format(plugin.__name__),
exc_info=True
)
try:
config_data = (
project_setting
[host]
["publish"]
[plugin.__name__]
)
except KeyError:
# host determined from path
file = os.path.normpath(inspect.getsourcefile(plugin))
file = os.path.normpath(file)
split_path = file.split(os.path.sep)
if len(split_path) < 4:
log.warning(
'plugin path too short to extract host {}'.format(file)
)
continue
host_from_file = split_path[-4]
plugin_kind = split_path[-2]
# TODO: change after all plugins are moved one level up
if host_from_file == "openpype":
host_from_file = "global"
try:
config_data = (
project_setting
[host_from_file]
[plugin_kind]
[plugin.__name__]
)
except KeyError:
continue
for option, value in config_data.items():
if option == "enabled" and value is False:
log.info('removing plugin {}'.format(plugin.__name__))
plugins.remove(plugin)
else:
log.info('setting {}:{} on plugin {}'.format(
option, value, plugin.__name__))
setattr(plugin, option, value)

View file

@ -434,7 +434,8 @@ class SubsetWidget(QtWidgets.QWidget):
# Get all representation->loader combinations available for the
# index under the cursor, so we can list the user the options.
available_loaders = discover_loader_plugins()
project_name = self.dbcon.active_project()
available_loaders = discover_loader_plugins(project_name)
if self.tool_name:
available_loaders = lib.remove_tool_name_from_loaders(
available_loaders, self.tool_name
@ -1330,7 +1331,8 @@ class RepresentationWidget(QtWidgets.QWidget):
selected_side = self._get_selected_side(point_index, rows)
# Get all representation->loader combinations available for the
# index under the cursor, so we can list the user the options.
available_loaders = discover_loader_plugins()
project_name = self.dbcon.active_project()
available_loaders = discover_loader_plugins(project_name)
filtered_loaders = []
for loader in available_loaders: