diff --git a/sopel/plugins/handlers.py b/sopel/plugins/handlers.py index 76902aa0..05f0279d 100644 --- a/sopel/plugins/handlers.py +++ b/sopel/plugins/handlers.py @@ -46,20 +46,15 @@ from __future__ import absolute_import, division, print_function, unicode_litera import imp import importlib +import importlib.util import inspect import itertools import os +import sys from sopel import loader from . import exceptions -try: - reload = importlib.reload -except AttributeError: - # py2: no reload function - # TODO: imp is deprecated, to be removed when py2 support is dropped - reload = imp.reload - class AbstractPluginHandler(object): """Base class for plugin handlers. @@ -301,7 +296,7 @@ class PyModulePlugin(AbstractPluginHandler): This method assumes the plugin is already loaded. """ - self._module = reload(self._module) + self._module = importlib.reload(self._module) def is_loaded(self): return self._module is not None @@ -402,45 +397,31 @@ class PyFilePlugin(PyModulePlugin): if good_file: name = os.path.basename(filename)[:-3] - module_type = imp.PY_SOURCE + spec = importlib.util.spec_from_file_location( + name, + filename, + ) elif good_dir: name = os.path.basename(filename) - module_type = imp.PKG_DIRECTORY + spec = importlib.util.spec_from_file_location( + name, + os.path.join(filename, '__init__.py'), + submodule_search_locations=filename, + ) else: raise exceptions.PluginError('Invalid Sopel plugin: %s' % filename) self.filename = filename self.path = filename - self.module_type = module_type + self.module_spec = spec super(PyFilePlugin, self).__init__(name) def _load(self): - # The current implementation uses `imp.load_module` to perform the - # load action, which also reloads the module. However, `imp` is - # deprecated in Python 3, so that might need to be changed when the - # support for Python 2 is dropped. - # - # However, the solution for Python 3 is non-trivial, since the - # `importlib` built-in module does not have a similar function, - # therefore requires to dive into its public internals - # (``importlib.machinery`` and ``importlib.util``). - # - # All of that is doable, but represents a lot of work. As long as - # Python 2 is supported, we can keep it for now. - # - # TODO: switch to ``importlib`` when Python2 support is dropped. - if self.module_type == imp.PY_SOURCE: - with open(self.path) as mod: - description = ('.py', 'U', self.module_type) - mod = imp.load_module(self.name, mod, self.path, description) - elif self.module_type == imp.PKG_DIRECTORY: - description = ('', '', self.module_type) - mod = imp.load_module(self.name, None, self.path, description) - else: - raise TypeError('Unsupported module type') - - return mod + module = importlib.util.module_from_spec(self.module_spec) + sys.modules[self.name] = module + self.module_spec.loader.exec_module(module) + return module def get_meta_description(self): """Retrieve a meta description for the plugin.