-
Notifications
You must be signed in to change notification settings - Fork 338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Register plugin from entry points #1872
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
*.egg-info |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
""" | ||
baz plugin | ||
""" | ||
|
||
from rez.command import Command | ||
|
||
# This attribute is optional, default behavior will be applied if not present. | ||
command_behavior = { | ||
"hidden": False, # (bool): default False | ||
"arg_mode": None, # (str): "passthrough", "grouped", default None | ||
} | ||
|
||
|
||
def setup_parser(parser, completions=False): | ||
parser.add_argument( | ||
"-m", "--message", action="store_true", help="Print message from world." | ||
) | ||
|
||
|
||
def command(opts, parser=None, extra_arg_groups=None): | ||
from baz import core | ||
|
||
if opts.message: | ||
msg = core.get_message_from_tmp() | ||
print(msg) | ||
return | ||
|
||
print("Please use '-h' flag to see what you can do to this world !") | ||
|
||
|
||
class BazCommand(Command): | ||
@classmethod | ||
def name(cls): | ||
return "baz" | ||
|
||
|
||
def register_plugin(): | ||
return BazCommand |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
""" | ||
baz plugin | ||
""" | ||
|
||
from rez.command import Command | ||
|
||
# This attribute is optional, default behavior will be applied if not present. | ||
command_behavior = { | ||
"hidden": False, # (bool): default False | ||
"arg_mode": None, # (str): "passthrough", "grouped", default None | ||
} | ||
|
||
|
||
def setup_parser(parser, completions=False): | ||
parser.add_argument( | ||
"-m", "--message", action="store_true", help="Print message from world." | ||
) | ||
|
||
|
||
def command(opts, parser=None, extra_arg_groups=None): | ||
from baz import core | ||
|
||
if opts.message: | ||
msg = core.get_message_from_baz() | ||
print(msg) | ||
return | ||
|
||
print("Please use '-h' flag to see what you can do to this world !") | ||
|
||
|
||
class BazCommand(Command): | ||
@classmethod | ||
def name(cls): | ||
return "baz" | ||
|
||
|
||
def register_plugin(): | ||
return BazCommand |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
def get_message_from_baz(): | ||
from rez.config import config | ||
message = config.plugins.command.baz.message | ||
return message |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
baz = { | ||
"message": "welcome to this world." | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from __future__ import print_function, with_statement | ||
from setuptools import setup, find_packages | ||
|
||
|
||
setup( | ||
name="baz", | ||
version="0.1.0", | ||
package_dir={ | ||
"baz": "baz" | ||
}, | ||
packages=find_packages(where="."), | ||
entry_points={ | ||
'rez.plugins': [ | ||
'baz_cmd = baz', | ||
] | ||
} | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -109,6 +109,10 @@ def register_plugin(self, plugin_name, plugin_class, plugin_module): | |
self.plugin_modules[plugin_name] = plugin_module | ||
|
||
def load_plugins(self): | ||
self.load_plugins_from_namespace() | ||
self.load_plugins_from_entry_points() | ||
|
||
def load_plugins_from_namespace(self): | ||
import pkgutil | ||
from importlib import import_module | ||
type_module_name = 'rezplugins.' + self.type_name | ||
|
@@ -153,44 +157,15 @@ def load_plugins(self): | |
if config.debug("plugins"): | ||
print_debug("loading %s plugin at %s: %s..." | ||
% (self.type_name, path, modname)) | ||
|
||
try: | ||
# https://github.com/AcademySoftwareFoundation/rez/pull/218 | ||
# load_module will force reload the module if it's | ||
# already loaded, so check for that | ||
plugin_module = sys.modules.get(modname) | ||
if plugin_module is None: | ||
loader = importer.find_module(modname) | ||
plugin_module = loader.load_module(modname) | ||
|
||
elif os.path.dirname(plugin_module.__file__) != path: | ||
if config.debug("plugins"): | ||
# this should not happen but if it does, tell why. | ||
print_warning( | ||
"plugin module %s is not loaded from current " | ||
"load path but reused from previous imported " | ||
"path: %s" % (modname, plugin_module.__file__)) | ||
|
||
if (hasattr(plugin_module, "register_plugin") | ||
and callable(plugin_module.register_plugin)): | ||
|
||
plugin_class = plugin_module.register_plugin() | ||
if plugin_class is not None: | ||
self.register_plugin(plugin_name, | ||
plugin_class, | ||
plugin_module) | ||
else: | ||
if config.debug("plugins"): | ||
print_warning( | ||
"'register_plugin' function at %s: %s did " | ||
"not return a class." % (path, modname)) | ||
else: | ||
if config.debug("plugins"): | ||
print_warning( | ||
"no 'register_plugin' function at %s: %s" | ||
% (path, modname)) | ||
|
||
# delete from sys.modules? | ||
|
||
self.register_plugin_module(plugin_name, plugin_module, path) | ||
self.load_config_from_plugin(plugin_module) | ||
except Exception as e: | ||
nameish = modname.split('.')[-1] | ||
self.failed_plugins[nameish] = str(e) | ||
|
@@ -201,9 +176,55 @@ def load_plugins(self): | |
traceback.print_exc(file=out) | ||
print_debug(out.getvalue()) | ||
|
||
# load config | ||
data, _ = _load_config_from_filepaths([os.path.join(path, "rezconfig")]) | ||
deep_update(self.config_data, data) | ||
def load_plugins_from_entry_points(self): | ||
if sys.version_info.minor >= 8: | ||
from importlib.metadata import entry_points | ||
else: | ||
from importlib_metadata import entry_points | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will force us to vendor |
||
|
||
discovered_plugins = entry_points(group='rez.plugins') | ||
for plugin in discovered_plugins: | ||
plugin = plugin.load() | ||
plugin_name = plugin.__name__.split('.')[-1] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure to understand why we need to do this. Can't we just use |
||
plugin_path = os.path.dirname(plugin.__file__) | ||
self.register_plugin_module(plugin_name, plugin, plugin_path) | ||
self.load_config_from_plugin(plugin) | ||
Comment on lines
+187
to
+191
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should put this code under a try/except block and add logs similar to logs found in |
||
|
||
def load_config_from_plugin(self, plugin): | ||
plugin_path = os.path.dirname(plugin.__file__) | ||
data, _ = _load_config_from_filepaths([os.path.join(plugin_path, "rezconfig")]) | ||
deep_update(self.config_data, data) | ||
|
||
def register_plugin_module(self, plugin_name, plugin_module, plugin_path): | ||
module_name = plugin_module.__name__ | ||
if os.path.dirname(plugin_module.__file__) != plugin_path: | ||
if config.debug("plugins"): | ||
# this should not happen but if it does, tell why. | ||
print_warning( | ||
"plugin module %s is not loaded from current " | ||
"load path but reused from previous imported " | ||
"path: %s" % (module_name, plugin_module.__file__)) | ||
|
||
if (hasattr(plugin_module, "register_plugin") | ||
and callable(plugin_module.register_plugin)): | ||
|
||
plugin_class = plugin_module.register_plugin() | ||
if plugin_class is not None: | ||
self.register_plugin( | ||
plugin_name, | ||
plugin_class, | ||
plugin_module | ||
) | ||
else: | ||
if config.debug("plugins"): | ||
print_warning( | ||
"'register_plugin' function at %s: %s did " | ||
"not return a class." % (plugin_path, module_name)) | ||
else: | ||
if config.debug("plugins"): | ||
print_warning( | ||
"no 'register_plugin' function at %s: %s" | ||
% (plugin_path, module_name)) | ||
|
||
def get_plugin_class(self, plugin_name): | ||
"""Returns the class registered under the given plugin name.""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,10 @@ | |
""" | ||
test rezplugins manager behaviors | ||
""" | ||
from rez.tests.util import TestBase, TempdirMixin, restore_sys_path | ||
from rez.tests.util import TestBase, TempdirMixin, restore_pip, restore_sys_path | ||
from rez.plugin_managers import plugin_manager, uncache_rezplugins_module_paths | ||
from rez.package_repository import package_repository_manager | ||
import os | ||
import sys | ||
import unittest | ||
|
||
|
@@ -49,7 +50,7 @@ def setUp(self): | |
TestBase.setUp(self) | ||
self._reset_plugin_manager() | ||
|
||
def test_old_loading_style(self): | ||
def test_load_plugin_from_plugin_path(self): | ||
"""Test loading rez plugin from plugin_path""" | ||
self.update_settings(dict( | ||
plugin_path=[self.data_path("extensions", "foo")] | ||
|
@@ -59,7 +60,7 @@ def test_old_loading_style(self): | |
"package_repository", "cloud") | ||
self.assertEqual(cloud_cls.name(), "cloud") | ||
|
||
def test_new_loading_style(self): | ||
def test_load_plugin_from_python_module(self): | ||
"""Test loading rez plugin from python modules""" | ||
with restore_sys_path(): | ||
sys.path.append(self.data_path("extensions")) | ||
|
@@ -68,6 +69,13 @@ def test_new_loading_style(self): | |
"package_repository", "cloud") | ||
self.assertEqual(cloud_cls.name(), "cloud") | ||
|
||
def test_load_plugin_from_entry_points(self): | ||
"""Test loading rez plugin from setuptools entry points""" | ||
with restore_pip("baz", os.path.join(self.data_path("extensions"), "baz")): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be safer to either just install into a temporary directory, or just commit the dist-info folder in the repo to avoid having to install it. |
||
baz_cls = plugin_manager.get_plugin_class( | ||
"command", "baz") | ||
self.assertEqual(baz_cls.name(), "baz") | ||
|
||
def test_plugin_override_1(self): | ||
"""Test plugin from plugin_path can override the default""" | ||
self.update_settings(dict( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the plugin defined twice?