From 1f748ad0a259b03e27578968f67f1d3f451a409e Mon Sep 17 00:00:00 2001 From: antirotor Date: Tue, 25 Jun 2019 22:36:41 +0200 Subject: [PATCH 1/4] feat(pyblish): adding ability to filter and modify plugins based on presets --- pype/__init__.py | 3 +++ pype/lib.py | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/pype/__init__.py b/pype/__init__.py index 5a65e01776..7f189bb814 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -3,6 +3,7 @@ import os from pyblish import api as pyblish from avalon import api as avalon from Qt import QtWidgets +from .lib import filter_pyblish_plugins import logging log = logging.getLogger(__name__) @@ -23,6 +24,7 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "global", "load") def install(): log.info("Registering global plug-ins..") pyblish.register_plugin_path(PUBLISH_PATH) + pyblish.register_discovery_filter(filter_pyblish_plugins) avalon.register_plugin_path(avalon.Loader, LOAD_PATH) # pyblish-qml settings. @@ -42,5 +44,6 @@ def install(): def uninstall(): log.info("Deregistering global plug-ins..") pyblish.deregister_plugin_path(PUBLISH_PATH) + pyblish.deregister_discovery_filter(filter_pyblish_plugins) avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) log.info("Global plug-ins unregistred") diff --git a/pype/lib.py b/pype/lib.py index 648a26a8a3..8120a1456a 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -478,3 +478,30 @@ def get_presets_path(): path_items = [templates, 'presets'] filepath = os.path.sep.join(path_items) return filepath + + +def filter_pyblish_plugins(plugins): + """ + This servers as plugin filter / modifier for pyblish. It will load plugin + definitions from presets and filter those needed to be excluded. + + :param plugins: Dictionary of plugins produced by :mod:`pyblish-base` + `discover()` method. + :type plugins: Dict + """ + from pypeapp import config + + # load plugins + config_data = config.get_presets()['plugins']['config'] + + # iterate over plugins + for plugin in plugins[:]: + if config_data.get(plugin.__name__): + for option, value in config_data[plugin.__name__].items(): + if hasattr(plugin, option): + log.info('setting {}:{} on plugin {}'.format( + option, value, plugin.__name__)) + setattr(plugin, option, value) + if option == "enabled": + log.info('removing plugin {}'.format(plugin.__name__)) + plugins.remove(plugin) From c1de475c58c5a965621d171822e54f35cbe976d0 Mon Sep 17 00:00:00 2001 From: antirotor Date: Fri, 28 Jun 2019 19:01:10 +0200 Subject: [PATCH 2/4] changed plugin filter to load presets from directory structure, added tests --- pype/__init__.py | 24 +++++----- pype/lib.py | 26 ++++++---- pype/tests/__init__.py | 0 pype/tests/lib.py | 80 +++++++++++++++++++++++++++++++ pype/tests/test_pyblish_filter.py | 41 ++++++++++++++++ 5 files changed, 150 insertions(+), 21 deletions(-) create mode 100644 pype/tests/__init__.py create mode 100644 pype/tests/lib.py create mode 100644 pype/tests/test_pyblish_filter.py diff --git a/pype/__init__.py b/pype/__init__.py index 7f189bb814..43ca61e29a 100644 --- a/pype/__init__.py +++ b/pype/__init__.py @@ -2,7 +2,7 @@ import os from pyblish import api as pyblish from avalon import api as avalon -from Qt import QtWidgets +# from Qt import QtWidgets from .lib import filter_pyblish_plugins import logging @@ -28,17 +28,17 @@ def install(): avalon.register_plugin_path(avalon.Loader, LOAD_PATH) # pyblish-qml settings. - try: - __import__("pyblish_qml") - except ImportError as e: - log.error("Could not load pyblish-qml: %s " % e) - else: - from pyblish_qml import settings - app = QtWidgets.QApplication.instance() - screen_resolution = app.desktop().screenGeometry() - width, height = screen_resolution.width(), screen_resolution.height() - settings.WindowSize = (width / 3, height * 0.75) - settings.WindowPosition = (0, 0) + # try: + # __import__("pyblish_qml") + # except ImportError as e: + # log.error("Could not load pyblish-qml: %s " % e) + # else: + # from pyblish_qml import settings + # app = QtWidgets.QApplication.instance() + # screen_resolution = app.desktop().screenGeometry() + # width, height = screen_resolution.width(), screen_resolution.height() + # settings.WindowSize = (width / 3, height * 0.75) + # settings.WindowPosition = (0, 0) def uninstall(): diff --git a/pype/lib.py b/pype/lib.py index 8120a1456a..78658b498b 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -490,18 +490,26 @@ def filter_pyblish_plugins(plugins): :type plugins: Dict """ from pypeapp import config + from pyblish import api + + host = api.current_host() # load plugins config_data = config.get_presets()['plugins']['config'] # iterate over plugins for plugin in plugins[:]: - if config_data.get(plugin.__name__): - for option, value in config_data[plugin.__name__].items(): - if hasattr(plugin, option): - log.info('setting {}:{} on plugin {}'.format( - option, value, plugin.__name__)) - setattr(plugin, option, value) - if option == "enabled": - log.info('removing plugin {}'.format(plugin.__name__)) - plugins.remove(plugin) + try: + config_data = config.get_presets()['plugins'][host][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) diff --git a/pype/tests/__init__.py b/pype/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/tests/lib.py b/pype/tests/lib.py new file mode 100644 index 0000000000..85b9032836 --- /dev/null +++ b/pype/tests/lib.py @@ -0,0 +1,80 @@ +import os +import sys +import shutil +import tempfile +import contextlib + +import pyblish +import pyblish.cli +import pyblish.plugin +from pyblish.vendor import six + + +# Setup +HOST = 'python' +FAMILY = 'test.family' + +REGISTERED = pyblish.plugin.registered_paths() +PACKAGEPATH = pyblish.lib.main_package_path() +ENVIRONMENT = os.environ.get("PYBLISHPLUGINPATH", "") +PLUGINPATH = os.path.join(PACKAGEPATH, '..', 'tests', 'plugins') + + +def setup(): + pyblish.plugin.deregister_all_paths() + + +def setup_empty(): + """Disable all plug-ins""" + setup() + pyblish.plugin.deregister_all_plugins() + pyblish.plugin.deregister_all_paths() + pyblish.plugin.deregister_all_hosts() + pyblish.plugin.deregister_all_callbacks() + pyblish.plugin.deregister_all_targets() + pyblish.api.deregister_all_discovery_filters() + + +def teardown(): + """Restore previously REGISTERED paths""" + + pyblish.plugin.deregister_all_paths() + for path in REGISTERED: + pyblish.plugin.register_plugin_path(path) + + os.environ["PYBLISHPLUGINPATH"] = ENVIRONMENT + pyblish.api.deregister_all_plugins() + pyblish.api.deregister_all_hosts() + pyblish.api.deregister_all_discovery_filters() + pyblish.api.deregister_test() + pyblish.api.__init__() + + +@contextlib.contextmanager +def captured_stdout(): + """Temporarily reassign stdout to a local variable""" + try: + sys.stdout = six.StringIO() + yield sys.stdout + finally: + sys.stdout = sys.__stdout__ + + +@contextlib.contextmanager +def captured_stderr(): + """Temporarily reassign stderr to a local variable""" + try: + sys.stderr = six.StringIO() + yield sys.stderr + finally: + sys.stderr = sys.__stderr__ + + +@contextlib.contextmanager +def tempdir(): + """Provide path to temporary directory""" + try: + tempdir = tempfile.mkdtemp() + yield tempdir + finally: + shutil.rmtree(tempdir) diff --git a/pype/tests/test_pyblish_filter.py b/pype/tests/test_pyblish_filter.py new file mode 100644 index 0000000000..27fed50c40 --- /dev/null +++ b/pype/tests/test_pyblish_filter.py @@ -0,0 +1,41 @@ +from . import lib +import pyblish.api +import pyblish.util +import pyblish.plugin +from pype.lib import filter_pyblish_plugins +import os + + +def test_pyblish_plugin_filter(printer, monkeypatch): + """ + Test if pyblish filter can filter and modify plugins on-the-fly. + """ + + lib.setup_empty() + monkeypatch.setitem(os.environ, 'PYBLISHPLUGINPATH', '') + plugins = pyblish.api.registered_plugins() + printer("Test if we have no registered plugins") + assert len(plugins) == 0 + paths = pyblish.api.registered_paths() + printer("Test if we have no registered plugin paths") + print(paths) + + class MyTestPlugin(pyblish.api.InstancePlugin): + my_test_property = 1 + label = "Collect Renderable Camera(s)" + hosts = ["test"] + families = ["default"] + + pyblish.api.register_host("test") + pyblish.api.register_plugin(MyTestPlugin) + pyblish.api.register_discovery_filter(filter_pyblish_plugins) + plugins = pyblish.api.discover() + + printer("Test if only one plugin was discovered") + assert len(plugins) == 1 + printer("Test if properties are modified correctly") + assert plugins[0].label == "loaded from preset" + assert plugins[0].families == ["changed", "by", "preset"] + assert plugins[0].optional is True + + lib.teardown() From ed5751457918e911f9dcefcd30e94fb3957cffde Mon Sep 17 00:00:00 2001 From: antirotor Date: Fri, 28 Jun 2019 21:29:58 +0200 Subject: [PATCH 3/4] added more tests, added support for coverage --- .gitignore | 15 +++++++++++++++ pype/.coveragerc | 0 pype/tests/test_pyblish_filter.py | 21 ++++++++++++++++++++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 pype/.coveragerc diff --git a/.gitignore b/.gitignore index baf7b918e2..801760201e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,18 @@ __pycache__/ # Editor backup files # ####################### *~ + +# Unit test / coverage reports +############################## +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +/coverage +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ diff --git a/pype/.coveragerc b/pype/.coveragerc new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pype/tests/test_pyblish_filter.py b/pype/tests/test_pyblish_filter.py index 27fed50c40..8d747e63df 100644 --- a/pype/tests/test_pyblish_filter.py +++ b/pype/tests/test_pyblish_filter.py @@ -6,7 +6,7 @@ from pype.lib import filter_pyblish_plugins import os -def test_pyblish_plugin_filter(printer, monkeypatch): +def test_pyblish_plugin_filter_modifier(printer, monkeypatch): """ Test if pyblish filter can filter and modify plugins on-the-fly. """ @@ -39,3 +39,22 @@ def test_pyblish_plugin_filter(printer, monkeypatch): assert plugins[0].optional is True lib.teardown() + + +def test_pyblish_plugin_filter_removal(monkeypatch): + """ Test that plugin can be removed by filter """ + lib.setup_empty() + monkeypatch.setitem(os.environ, 'PYBLISHPLUGINPATH', '') + plugins = pyblish.api.registered_plugins() + + class MyTestRemovedPlugin(pyblish.api.InstancePlugin): + my_test_property = 1 + label = "Collect Renderable Camera(s)" + hosts = ["test"] + families = ["default"] + + pyblish.api.register_host("test") + pyblish.api.register_plugin(MyTestRemovedPlugin) + pyblish.api.register_discovery_filter(filter_pyblish_plugins) + plugins = pyblish.api.discover() + assert len(plugins) == 0 From 9865608c08147288559f24258e0963062e10c5dd Mon Sep 17 00:00:00 2001 From: antirotor Date: Mon, 8 Jul 2019 21:41:02 +0200 Subject: [PATCH 4/4] plugins overrides are now looked in presets/plugins/[host]/publish.json --- pype/lib.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pype/lib.py b/pype/lib.py index 78658b498b..e163cc14fc 100644 --- a/pype/lib.py +++ b/pype/lib.py @@ -494,13 +494,10 @@ def filter_pyblish_plugins(plugins): host = api.current_host() - # load plugins - config_data = config.get_presets()['plugins']['config'] - # iterate over plugins for plugin in plugins[:]: try: - config_data = config.get_presets()['plugins'][host][plugin.__name__] # noqa: E501 + config_data = config.get_presets()['plugins'][host]["publish"][plugin.__name__] # noqa: E501 except KeyError: continue