webcomics/dosage

View on GitHub
dosagelib/loader.py

Summary

Maintainability
A
1 hr
Test Coverage
B
85%
# SPDX-License-Identifier: MIT
# Copyright (C) 2012-2014 Bastian Kleineidam
# Copyright (C) 2016-2020 Tobias Gruetzmacher
"""
Functions to load plugin modules.

Example usage:
    for module in loader.get_plugin_modules():
        plugins.extend(loader.get_module_plugins(module, PluginClass))
"""
import importlib
import pkgutil
import sys

from .plugins import (__name__ as plugin_package, __path__ as plugin_path)
from .output import out


def get_plugin_modules():
    """Find (and import) all valid modules in the "plugins" package.
    @return: all loaded valid modules
    @rtype: iterator of module
    """
    prefix = plugin_package + "."
    modules = {m[1] for m in pkgutil.iter_modules(plugin_path, prefix)}

    for elm in _get_all_modules_pyinstaller():
        if elm.startswith(prefix):
            modules.add(elm)

    for name in modules:
        try:
            yield importlib.import_module(name)
        except ImportError as msg:
            out.error("could not load module %s: %s" % (name, msg))


def _get_all_modules_pyinstaller():
    # Special handling for PyInstaller
    toc = set()
    importers = pkgutil.iter_importers(__package__)
    for i in importers:
        if hasattr(i, 'toc'):
            toc |= i.toc
    return toc


def get_plugin_modules_from_dir(path, prefix='user_'):
    """Load and import a directory of python files as if they were part of the
    "plugins" package. (Mostly "stolen" from
    https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly)
    """
    modules = []
    for f in path.glob('*.py'):
        name = plugin_package + "." + prefix + f.stem
        # FIXME: Drop str() when this is Python 3.6+
        spec = importlib.util.spec_from_file_location(name, str(f))
        module = importlib.util.module_from_spec(spec)
        sys.modules[name] = module
        spec.loader.exec_module(module)
        modules.append(module)
    return modules


def get_module_plugins(module, classobj):
    """Return all subclasses of a class in the module.
    If the module defines __all__, only those entries will be searched,
    otherwise all objects not starting with '_' will be searched.
    """
    try:
        names = module.__all__
    except AttributeError:
        names = [x for x in vars(module) if not x.startswith('_')]
    for name in names:
        try:
            obj = getattr(module, name)
        except AttributeError:
            continue
        try:
            if issubclass(obj, classobj):
                yield obj
        except TypeError:
            continue