# -*- coding: utf-8 -*- # # Copyright © Spyder Project Contributors # Licensed under the terms of the MIT License # (see spyder/__init__.py for details) """ Spyder third-party plugins configuration management. """ # Standard library imports import importlib import logging import os import os.path as osp import sys import traceback # Local imports from spyder.config.base import get_conf_path from spyder.py3compat import to_text_string # Constants logger = logging.getLogger(__name__) USER_PLUGIN_DIR = "plugins" PLUGIN_PREFIX = "spyder_" IO_PREFIX = PLUGIN_PREFIX + "io_" def get_spyderplugins_mods(io=False): """Import modules from plugins package and return the list""" # Create user directory user_plugin_path = osp.join(get_conf_path(), USER_PLUGIN_DIR) if not osp.isdir(user_plugin_path): os.makedirs(user_plugin_path) modlist, modnames = [], [] # The user plugins directory is given the priority when looking for modules for plugin_path in [user_plugin_path] + sys.path: _get_spyderplugins(plugin_path, io, modnames, modlist) return modlist def _get_spyderplugins(plugin_path, is_io, modnames, modlist): """Scan the directory `plugin_path` for plugin packages and loads them.""" if not osp.isdir(plugin_path): return for name in os.listdir(plugin_path): # This is needed in order to register the spyder_io_hdf5 plugin. # See spyder-ide/spyder#4487. # Is this a Spyder plugin? if not name.startswith(PLUGIN_PREFIX): continue # Ensure right type of plugin if is_io and not name.startswith(IO_PREFIX): continue # Skip names that end in certain suffixes forbidden_suffixes = ['dist-info', 'egg.info', 'egg-info', 'egg-link', 'kernels', 'boilerplate'] if any([name.endswith(s) for s in forbidden_suffixes]): continue # Import the plugin _import_plugin(name, plugin_path, modnames, modlist) def _import_plugin(module_name, plugin_path, modnames, modlist): """Import the plugin `module_name` from `plugin_path`, add it to `modlist` and adds its name to `modnames`. """ if module_name in modnames: return try: # First add a mock module with the LOCALEPATH attribute so that the # helper method can find the locale on import mock = _ModuleMock() mock.LOCALEPATH = osp.join(plugin_path, module_name, 'locale') sys.modules[module_name] = mock if osp.isdir(osp.join(plugin_path, module_name)): module = _import_module_from_path(module_name, plugin_path) else: module = None # Then restore the actual loaded module instead of the mock if module and getattr(module, 'PLUGIN_CLASS', False): sys.modules[module_name] = module modlist.append(module) modnames.append(module_name) except Exception as e: sys.stderr.write("ERROR: 3rd party plugin import failed for " "`{0}`\n".format(module_name)) traceback.print_exc(file=sys.stderr) def _import_module_from_path(module_name, plugin_path): """Imports `module_name` from `plugin_path`. Return None if no module is found. """ module = None try: spec = importlib.machinery.PathFinder.find_spec( module_name, [plugin_path]) if spec: module = spec.loader.load_module(module_name) except Exception as err: debug_message = ("plugin: '{module_name}' load failed with `{err}`" "").format(module_name=module_name, err=to_text_string(err)) logger.debug(debug_message) return module class _ModuleMock(): """This mock module is added to sys.modules on plugin load to add the location of the LOCALEDATA so that the module loads succesfully. Once loaded the module is replaced by the actual loaded module object. """ pass