Merge pull request #1201 from ynput/bugfix/fix-publish-plugin-loading2

Fix publish plugin discovery
This commit is contained in:
Ondřej Samohel 2025-03-21 17:24:24 +01:00 committed by GitHub
commit 3d9fa01058
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 21 deletions

View file

@ -1,6 +1,8 @@
"""Tools for working with python modules and classes."""
import os
import sys
import types
from typing import Optional
import importlib
import inspect
import logging
@ -8,13 +10,22 @@ import logging
log = logging.getLogger(__name__)
def import_filepath(filepath, module_name=None):
def import_filepath(
filepath: str,
module_name: Optional[str] = None,
sys_module_name: Optional[str] = None) -> types.ModuleType:
"""Import python file as python module.
Args:
filepath (str): Path to python file.
module_name (str): Name of loaded module. Only for Python 3. By default
is filled with filename of filepath.
sys_module_name (str): Name of module in `sys.modules` where to store
loaded module. By default is None so module is not added to
`sys.modules`.
Todo (antirotor): We should add the module to the sys.modules always but
we need to be careful about it and test it properly.
"""
if module_name is None:
@ -28,6 +39,9 @@ def import_filepath(filepath, module_name=None):
module_loader = importlib.machinery.SourceFileLoader(
module_name, filepath
)
# only add to sys.modules if requested
if sys_module_name:
sys.modules[sys_module_name] = module
module_loader.exec_module(module)
return module
@ -126,7 +140,8 @@ def classes_from_module(superclass, module):
return classes
def import_module_from_dirpath(dirpath, folder_name, dst_module_name=None):
def import_module_from_dirpath(
dirpath, folder_name, dst_module_name=None):
"""Import passed directory as a python module.
Imported module can be assigned as a child attribute of already loaded
@ -193,7 +208,7 @@ def is_func_signature_supported(func, *args, **kwargs):
Notes:
This does NOT check if the function would work with passed arguments
only if they can be passed in. If function have *args, **kwargs
in paramaters, this will always return 'True'.
in parameters, this will always return 'True'.
Example:
>>> def my_function(my_number):

View file

@ -1,3 +1,5 @@
"""Library functions for publishing."""
from __future__ import annotations
import os
import sys
import inspect
@ -12,8 +14,8 @@ import pyblish.plugin
import pyblish.api
from ayon_core.lib import (
Logger,
import_filepath,
Logger,
filter_profiles,
)
from ayon_core.settings import get_project_settings
@ -163,7 +165,7 @@ class HelpContent:
def load_help_content_from_filepath(filepath):
"""Load help content from xml file.
Xml file may containt errors and warnings.
Xml file may contain errors and warnings.
"""
errors = {}
warnings = {}
@ -208,8 +210,9 @@ def load_help_content_from_plugin(plugin):
return load_help_content_from_filepath(filepath)
def publish_plugins_discover(paths=None):
"""Find and return available pyblish plug-ins
def publish_plugins_discover(
paths: Optional[list[str]] = None) -> DiscoverResult:
"""Find and return available pyblish plug-ins.
Overridden function from `pyblish` module to be able to collect
crashed files and reason of their crash.
@ -252,17 +255,14 @@ def publish_plugins_discover(paths=None):
continue
try:
module = import_filepath(abspath, mod_name)
module = import_filepath(
abspath, mod_name, sys_module_name=mod_name)
# Store reference to original module, to avoid
# garbage collection from collecting it's global
# imports, such as `import os`.
sys.modules[abspath] = module
except Exception as err:
except Exception as err: # noqa: BLE001
# we need broad exception to catch all possible errors.
result.crashed_file_paths[abspath] = sys.exc_info()
log.debug("Skipped: \"%s\" (%s)", mod_name, err)
log.debug('Skipped: "%s" (%s)', mod_name, err)
continue
for plugin in pyblish.plugin.plugins_from_module(module):
@ -280,9 +280,8 @@ def publish_plugins_discover(paths=None):
continue
plugin_names.append(plugin.__name__)
plugin.__module__ = module.__file__
key = "{0}.{1}".format(plugin.__module__, plugin.__name__)
plugin.__file__ = module.__file__
key = f"{module.__file__}.{plugin.__name__}"
plugins[key] = plugin
# Include plug-ins from registration.
@ -361,7 +360,7 @@ def get_plugin_settings(plugin, project_settings, log, category=None):
# Settings category determined from path
# - usually path is './<category>/plugins/publish/<plugin file>'
# - category can be host name of addon name ('maya', 'deadline', ...)
filepath = os.path.normpath(inspect.getsourcefile(plugin))
filepath = os.path.normpath(inspect.getfile(plugin))
split_path = filepath.rsplit(os.path.sep, 5)
if len(split_path) < 4:
@ -427,7 +426,7 @@ def filter_pyblish_plugins(plugins):
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
# - kept because on farm is probably used host 'shell' which probably
# affect how settings are applied there
host_name = pyblish.api.current_host()
project_name = os.environ.get("AYON_PROJECT_NAME")
@ -529,7 +528,7 @@ def filter_instances_for_context_plugin(plugin, context):
Args:
plugin (pyblish.api.Plugin): Plugin with filters.
context (pyblish.api.Context): Pyblish context with insances.
context (pyblish.api.Context): Pyblish context with instances.
Returns:
Iterator[pyblish.lib.Instance]: Iteration of valid instances.