diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 9eb7724a60..06de486f2e 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # flake8: noqa E402 -"""Pype module API.""" +"""OpenPype lib functions.""" # add vendor to sys path based on Python version import sys import os @@ -94,7 +94,8 @@ from .python_module_tools import ( modules_from_path, recursive_bases_from_class, classes_from_module, - import_module_from_dirpath + import_module_from_dirpath, + is_func_signature_supported, ) from .profiles_filtering import ( @@ -243,6 +244,7 @@ __all__ = [ "recursive_bases_from_class", "classes_from_module", "import_module_from_dirpath", + "is_func_signature_supported", "get_transcode_temp_directory", "should_convert_for_ffmpeg", diff --git a/openpype/lib/events.py b/openpype/lib/events.py index bed00fe659..dca58fcf93 100644 --- a/openpype/lib/events.py +++ b/openpype/lib/events.py @@ -6,10 +6,9 @@ import inspect import logging import weakref from uuid import uuid4 -try: - from weakref import WeakMethod -except Exception: - from openpype.lib.python_2_comp import WeakMethod + +from .python_2_comp import WeakMethod +from .python_module_tools import is_func_signature_supported class MissingEventSystem(Exception): @@ -80,40 +79,8 @@ class EventCallback(object): # Get expected arguments from function spec # - positional arguments are always preferred - expect_args = False - expect_kwargs = False - fake_event = "fake" - if hasattr(inspect, "signature"): - # Python 3 using 'Signature' object where we try to bind arg - # or kwarg. Using signature is recommended approach based on - # documentation. - sig = inspect.signature(func) - try: - sig.bind(fake_event) - expect_args = True - except TypeError: - pass - - try: - sig.bind(event=fake_event) - expect_kwargs = True - except TypeError: - pass - - else: - # In Python 2 'signature' is not available so 'getcallargs' is used - # - 'getcallargs' is marked as deprecated since Python 3.0 - try: - inspect.getcallargs(func, fake_event) - expect_args = True - except TypeError: - pass - - try: - inspect.getcallargs(func, event=fake_event) - expect_kwargs = True - except TypeError: - pass + expect_args = is_func_signature_supported(func, "fake") + expect_kwargs = is_func_signature_supported(func, event="fake") self._func_ref = func_ref self._func_name = func_name diff --git a/openpype/lib/execute.py b/openpype/lib/execute.py index ef456395e7..6f52efdfcc 100644 --- a/openpype/lib/execute.py +++ b/openpype/lib/execute.py @@ -190,7 +190,7 @@ def run_openpype_process(*args, **kwargs): Example: ``` - run_openpype_process("run", "") + run_detached_process("run", "") ``` Args: diff --git a/openpype/lib/python_2_comp.py b/openpype/lib/python_2_comp.py index d7137dbe9c..091c51a6f6 100644 --- a/openpype/lib/python_2_comp.py +++ b/openpype/lib/python_2_comp.py @@ -1,41 +1,44 @@ import weakref -class _weak_callable: - def __init__(self, obj, func): - self.im_self = obj - self.im_func = func +WeakMethod = getattr(weakref, "WeakMethod", None) - def __call__(self, *args, **kws): - if self.im_self is None: - return self.im_func(*args, **kws) - else: - return self.im_func(self.im_self, *args, **kws) +if WeakMethod is None: + class _WeakCallable: + def __init__(self, obj, func): + self.im_self = obj + self.im_func = func + + def __call__(self, *args, **kws): + if self.im_self is None: + return self.im_func(*args, **kws) + else: + return self.im_func(self.im_self, *args, **kws) -class WeakMethod: - """ Wraps a function or, more importantly, a bound method in - a way that allows a bound method's object to be GCed, while - providing the same interface as a normal weak reference. """ + class WeakMethod: + """ Wraps a function or, more importantly, a bound method in + a way that allows a bound method's object to be GCed, while + providing the same interface as a normal weak reference. """ - def __init__(self, fn): - try: - self._obj = weakref.ref(fn.im_self) - self._meth = fn.im_func - except AttributeError: - # It's not a bound method - self._obj = None - self._meth = fn + def __init__(self, fn): + try: + self._obj = weakref.ref(fn.im_self) + self._meth = fn.im_func + except AttributeError: + # It's not a bound method + self._obj = None + self._meth = fn - def __call__(self): - if self._dead(): - return None - return _weak_callable(self._getobj(), self._meth) + def __call__(self): + if self._dead(): + return None + return _WeakCallable(self._getobj(), self._meth) - def _dead(self): - return self._obj is not None and self._obj() is None + def _dead(self): + return self._obj is not None and self._obj() is None - def _getobj(self): - if self._obj is None: - return None - return self._obj() + def _getobj(self): + if self._obj is None: + return None + return self._obj() diff --git a/openpype/lib/python_module_tools.py b/openpype/lib/python_module_tools.py index 9e8e94842c..a10263f991 100644 --- a/openpype/lib/python_module_tools.py +++ b/openpype/lib/python_module_tools.py @@ -230,3 +230,70 @@ def import_module_from_dirpath(dirpath, folder_name, dst_module_name=None): dirpath, folder_name, dst_module_name ) return module + + +def is_func_signature_supported(func, *args, **kwargs): + """Check if a function signature supports passed args and kwargs. + + This check does not actually call the function, just look if function can + be called with the arguments. + + 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'. + + Example: + >>> def my_function(my_number): + ... return my_number + 1 + ... + >>> is_func_signature_supported(my_function, 1) + True + >>> is_func_signature_supported(my_function, 1, 2) + False + >>> is_func_signature_supported(my_function, my_number=1) + True + >>> is_func_signature_supported(my_function, number=1) + False + >>> is_func_signature_supported(my_function, "string") + True + >>> def my_other_function(*args, **kwargs): + ... my_function(*args, **kwargs) + ... + >>> is_func_signature_supported( + ... my_other_function, + ... "string", + ... 1, + ... other=None + ... ) + True + + Args: + func (function): A function where the signature should be tested. + *args (tuple[Any]): Positional arguments for function signature. + **kwargs (dict[str, Any]): Keyword arguments for function signature. + + Returns: + bool: Function can pass in arguments. + """ + + if hasattr(inspect, "signature"): + # Python 3 using 'Signature' object where we try to bind arg + # or kwarg. Using signature is recommended approach based on + # documentation. + sig = inspect.signature(func) + try: + sig.bind(*args, **kwargs) + return True + except TypeError: + pass + + else: + # In Python 2 'signature' is not available so 'getcallargs' is used + # - 'getcallargs' is marked as deprecated since Python 3.0 + try: + inspect.getcallargs(func, *args, **kwargs) + return True + except TypeError: + pass + return False diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py index 40186238aa..63a856e326 100644 --- a/openpype/pipeline/publish/lib.py +++ b/openpype/pipeline/publish/lib.py @@ -1,12 +1,10 @@ import os import sys -import types import inspect import copy import tempfile import xml.etree.ElementTree -import six import pyblish.util import pyblish.plugin import pyblish.api @@ -42,7 +40,9 @@ def get_template_name_profiles( Args: project_name (str): Name of project where to look for templates. - project_settings(Dic[str, Any]): Prepared project settings. + project_settings (Dict[str, Any]): Prepared project settings. + logger (Optional[logging.Logger]): Logger object to be used instead + of default logger. Returns: List[Dict[str, Any]]: Publish template profiles. @@ -103,7 +103,9 @@ def get_hero_template_name_profiles( Args: project_name (str): Name of project where to look for templates. - project_settings(Dic[str, Any]): Prepared project settings. + project_settings (Dict[str, Any]): Prepared project settings. + logger (Optional[logging.Logger]): Logger object to be used instead + of default logger. Returns: List[Dict[str, Any]]: Publish template profiles. @@ -172,9 +174,10 @@ def get_publish_template_name( project_name (str): Name of project where to look for settings. host_name (str): Name of host integration. family (str): Family for which should be found template. - task_name (str): Task name on which is intance working. - task_type (str): Task type on which is intance working. - project_setting (Dict[str, Any]): Prepared project settings. + task_name (str): Task name on which is instance working. + task_type (str): Task type on which is instance working. + project_settings (Dict[str, Any]): Prepared project settings. + hero (bool): Template is for hero version publishing. logger (logging.Logger): Custom logger used for 'filter_profiles' function. @@ -264,19 +267,18 @@ def load_help_content_from_plugin(plugin): def publish_plugins_discover(paths=None): """Find and return available pyblish plug-ins - Overridden function from `pyblish` module to be able collect crashed files - and reason of their crash. + Overridden function from `pyblish` module to be able to collect + crashed files and reason of their crash. Arguments: paths (list, optional): Paths to discover plug-ins from. If no paths are provided, all paths are searched. - """ # The only difference with `pyblish.api.discover` result = DiscoverResult(pyblish.api.Plugin) - plugins = dict() + plugins = {} plugin_names = [] allow_duplicates = pyblish.plugin.ALLOW_DUPLICATES @@ -302,7 +304,7 @@ def publish_plugins_discover(paths=None): mod_name, mod_ext = os.path.splitext(fname) - if not mod_ext == ".py": + if mod_ext != ".py": continue try: @@ -533,10 +535,10 @@ def find_close_plugin(close_plugin_name, log): def remote_publish(log, close_plugin_name=None, raise_error=False): """Loops through all plugins, logs to console. Used for tests. - Args: - log (openpype.lib.Logger) - close_plugin_name (str): name of plugin with responsibility to - close host app + Args: + log (Logger) + close_plugin_name (str): name of plugin with responsibility to + close host app """ # Error exit as soon as any error occurs. error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}"