Allow adding external directories to the plugin package

This commit is contained in:
Tobias Gruetzmacher 2020-10-01 21:54:30 +02:00
parent 3256f9fdc2
commit 0bdf3dd94b
4 changed files with 68 additions and 17 deletions

View file

@ -5,11 +5,12 @@
Functions to load plugin modules. Functions to load plugin modules.
Example usage: Example usage:
modules = loader.get_plugin_modules() for module in loader.get_plugin_modules():
plugins = loader.get_plugins(modules, PluginClass) plugins.extend(loader.get_module_plugins(module, PluginClass))
""" """
import importlib import importlib
import pkgutil import pkgutil
import sys
from .plugins import (__name__ as plugin_package, __path__ as plugin_path) from .plugins import (__name__ as plugin_package, __path__ as plugin_path)
from .output import out from .output import out
@ -44,16 +45,21 @@ def _get_all_modules_pyinstaller():
return toc return toc
def get_plugins(modules, classobj): def get_plugin_modules_from_dir(path, prefix='user_'):
"""Find all class objects in all modules. """Load and import a directory of python files as if they were part of the
@param modules: the modules to search "plugins" package. (Mostly "stolen" from
@ptype modules: iterator of modules https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly)
@return: found classes
@rytpe: iterator of class objects
""" """
for module in modules: modules = []
for plugin in get_module_plugins(module, classobj): for f in path.glob('*.py'):
yield plugin 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): def get_module_plugins(module, classobj):

View file

@ -542,7 +542,7 @@ class Cache:
slow. slow.
""" """
def __init__(self): def __init__(self):
self.data = None self.data = []
def find(self, comic, multiple_allowed=False): def find(self, comic, multiple_allowed=False):
"""Get a list comic scraper objects. """Get a list comic scraper objects.
@ -574,12 +574,41 @@ class Cache:
def load(self): def load(self):
out.debug("Loading comic modules...") out.debug("Loading comic modules...")
modules = loader.get_plugin_modules() modules = 0
plugins = list(loader.get_plugins(modules, Scraper)) classes = 0
self.data = list([m for x in plugins for m in x.getmodules()]) for module in loader.get_plugin_modules():
modules += 1
classes += self.addmodule(module)
self.validate() self.validate()
out.debug("... %d modules loaded from %d classes." % ( out.debug("... %d scrapers loaded from %d classes in %d modules." % (
len(self.data), len(plugins))) len(self.data), classes, modules))
def adddir(self, path):
"""Add an additional directory with python modules to the scraper list.
These are handled as if the were part of the plugins package.
"""
if not self.data:
self.load()
modules = 0
classes = 0
out.debug("Loading user scrapers from '{}'...".format(path))
for module in loader.get_plugin_modules_from_dir(path):
modules += 1
classes += self.addmodule(module)
self.validate()
if classes > 0:
out.debug("Added %d user classes from %d modules." % (
classes, modules))
def addmodule(self, module):
"""Adds all valid plugin classes from the specified module to the cache.
@return: number of classes added
"""
classes = 0
for plugin in loader.get_module_plugins(module, Scraper):
classes += 1
self.data.extend(plugin.getmodules())
return classes
def get(self, include_removed=False): def get(self, include_removed=False):
"""Find all comic scraper classes in the plugins directory. """Find all comic scraper classes in the plugins directory.

View file

@ -0,0 +1,7 @@
# SPDX-License-Identifier: MIT
# Copyright (C) 2020 Tobias Gruetzmacher
from ..scraper import _ParserScraper
class ADummyTestScraper(_ParserScraper):
url = 'https://dummy.example/'

View file

@ -1,7 +1,10 @@
# SPDX-License-Identifier: MIT # SPDX-License-Identifier: MIT
# Copyright (C) 2013-2014 Bastian Kleineidam # Copyright (C) 2013-2014 Bastian Kleineidam
# Copyright (C) 2015-2020 Tobias Gruetzmacher # Copyright (C) 2015-2020 Tobias Gruetzmacher
from pathlib import Path
import pytest import pytest
from dosagelib.scraper import scrapers from dosagelib.scraper import scrapers
@ -24,3 +27,9 @@ class TestScraper(object):
def test_find_scrapers_error(self): def test_find_scrapers_error(self):
with pytest.raises(ValueError, match='empty comic name'): with pytest.raises(ValueError, match='empty comic name'):
scrapers.find('') scrapers.find('')
def test_user_dir(self):
oldlen = len(scrapers.get())
scrapers.adddir(Path(__file__).parent / 'mocks' / 'plugins')
assert len(scrapers.get()) == oldlen + 1
assert len(scrapers.find('ADummyTestScraper')) == 1