Skip to content
Permalink
Browse files

Namespace plugin implementation

* Creates pelican.plugins
* Moves plugin related code under pelican.plugins
  * pelican.plugins.signals is now the location for signals, pelican.signals is kept
    for backwards compatibility
  * pelican.plugins._utils contains necessary bits for plugin discovery and loading.
    Logic from Pelican class is moved here. Pelican class now just asks for plugins
    and registers them
* Contains tests for old and new plugin loading
  • Loading branch information
avaris committed Oct 27, 2019
1 parent 772005f commit a2053c34c39a035a03d1fcce7a66e7ad1af885d0
@@ -9,6 +9,11 @@
import time
import traceback
from collections.abc import Iterable
# Combines all paths to `pelican` package accessible from `sys.path`
# Makes it possible to install `pelican` and namespace plugins into different
# locations in the file system (e.g. pip with `-e` or `--user`)
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

# pelican.log has to be the first pelican module to be loaded
# because logging.setLoggerClass has to be called before logging.getLogger
@@ -17,6 +22,7 @@
from pelican.generators import (ArticlesGenerator, PagesGenerator,
SourceFileGenerator, StaticGenerator,
TemplatePagesGenerator)
from pelican.plugins._utils import load_plugins
from pelican.readers import Readers
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
from pelican.settings import read_settings
@@ -62,27 +68,14 @@ def init_path(self):
sys.path.insert(0, '')

def init_plugins(self):
self.plugins = []
logger.debug('Temporarily adding PLUGIN_PATHS to system path')
_sys_path = sys.path[:]
for pluginpath in self.settings['PLUGIN_PATHS']:
sys.path.insert(0, pluginpath)
for plugin in self.settings['PLUGINS']:
# if it's a string, then import it
if isinstance(plugin, str):
logger.debug("Loading plugin `%s`", plugin)
try:
plugin = __import__(plugin, globals(), locals(), 'module')
except ImportError as e:
logger.error(
"Cannot load plugin `%s`\n%s", plugin, e)
continue

logger.debug("Registering plugin `%s`", plugin.__name__)
plugin.register()
self.plugins.append(plugin)
logger.debug('Restoring system path')
sys.path = _sys_path
self.plugins = load_plugins(self.settings)
for plugin in self.plugins:
logger.debug('Registering plugin `%s`', plugin.__name__)
try:
plugin.register()
except Exception as e:
logger.error('Cannot register plugin `%s`\n%s',
plugin.__name__, e)

def run(self):
"""Run the generators and return"""
@@ -0,0 +1,77 @@
import importlib
import logging
import pkgutil
import sys

import six


logger = logging.getLogger(__name__)


def iter_namespace(ns_pkg):
# Specifying the second argument (prefix) to iter_modules makes the
# returned name an absolute name instead of a relative one. This allows
# import_module to work without having to do additional modification to
# the name.
return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".")


def get_namespace_plugins(ns_pkg=None):
if ns_pkg is None:
import pelican.plugins as ns_pkg

return {
name: importlib.import_module(name)
for finder, name, ispkg
in iter_namespace(ns_pkg)
if ispkg
}


def list_plugins(ns_pkg=None):
from pelican.log import init as init_logging
init_logging(logging.INFO)
ns_plugins = get_namespace_plugins(ns_pkg)
if ns_plugins:
logger.info('Plugins found:\n' + '\n'.join(ns_plugins))
else:
logger.info('No plugins are installed')


def load_plugins(settings):
logger.debug('Finding namespace plugins')
namespace_plugins = get_namespace_plugins()
if namespace_plugins:
logger.debug('Namespace plugins found:\n' +
'\n'.join(namespace_plugins))
plugins = []
if settings.get('PLUGINS') is not None:
_sys_path = sys.path[:]

for path in settings.get('PLUGIN_PATHS', []):
sys.path.insert(0, path)

for plugin in settings['PLUGINS']:
if isinstance(plugin, six.string_types):
logger.debug('Loading plugin `%s`', plugin)
# try to find in namespace plugins
if plugin in namespace_plugins:
plugin = namespace_plugins[plugin]
elif 'pelican.plugins.{}'.format(plugin) in namespace_plugins:
plugin = namespace_plugins['pelican.plugins.{}'.format(
plugin)]
# try to import it
else:
try:
plugin = __import__(plugin, globals(), locals(),
str('module'))
except ImportError as e:
logger.error('Cannot load plugin `%s`\n%s', plugin, e)
continue
plugins.append(plugin)
sys.path = _sys_path
else:
plugins = list(namespace_plugins.values())

return plugins
@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from __future__ import print_function, unicode_literals

from blinker import signal

# Run-level signals:

initialized = signal('pelican_initialized')
get_generators = signal('get_generators')
all_generators_finalized = signal('all_generators_finalized')
get_writer = signal('get_writer')
finalized = signal('pelican_finalized')

# Reader-level signals

readers_init = signal('readers_init')

# Generator-level signals

generator_init = signal('generator_init')

article_generator_init = signal('article_generator_init')
article_generator_pretaxonomy = signal('article_generator_pretaxonomy')
article_generator_finalized = signal('article_generator_finalized')
article_generator_write_article = signal('article_generator_write_article')
article_writer_finalized = signal('article_writer_finalized')

page_generator_init = signal('page_generator_init')
page_generator_finalized = signal('page_generator_finalized')
page_generator_write_page = signal('page_generator_write_page')
page_writer_finalized = signal('page_writer_finalized')

static_generator_init = signal('static_generator_init')
static_generator_finalized = signal('static_generator_finalized')

# Page-level signals

article_generator_preread = signal('article_generator_preread')
article_generator_context = signal('article_generator_context')

page_generator_preread = signal('page_generator_preread')
page_generator_context = signal('page_generator_context')

static_generator_preread = signal('static_generator_preread')
static_generator_context = signal('static_generator_context')

content_object_init = signal('content_object_init')

# Writers signals
content_written = signal('content_written')
feed_generated = signal('feed_generated')
feed_written = signal('feed_written')
@@ -138,7 +138,7 @@ def load_source(name, path):
'TYPOGRIFY_IGNORE_TAGS': [],
'SUMMARY_MAX_LENGTH': 50,
'PLUGIN_PATHS': [],
'PLUGINS': [],
'PLUGINS': None,
'PYGMENTS_RST_OPTIONS': {},
'TEMPLATE_PAGES': {},
'TEMPLATE_EXTENSIONS': ['.html'],
@@ -1,51 +1,4 @@
# -*- coding: utf-8 -*-
# This file is for backwards compatibility

from blinker import signal

# Run-level signals:

initialized = signal('pelican_initialized')
get_generators = signal('get_generators')
all_generators_finalized = signal('all_generators_finalized')
get_writer = signal('get_writer')
finalized = signal('pelican_finalized')

# Reader-level signals

readers_init = signal('readers_init')

# Generator-level signals

generator_init = signal('generator_init')

article_generator_init = signal('article_generator_init')
article_generator_pretaxonomy = signal('article_generator_pretaxonomy')
article_generator_finalized = signal('article_generator_finalized')
article_generator_write_article = signal('article_generator_write_article')
article_writer_finalized = signal('article_writer_finalized')

page_generator_init = signal('page_generator_init')
page_generator_finalized = signal('page_generator_finalized')
page_generator_write_page = signal('page_generator_write_page')
page_writer_finalized = signal('page_writer_finalized')

static_generator_init = signal('static_generator_init')
static_generator_finalized = signal('static_generator_finalized')

# Page-level signals

article_generator_preread = signal('article_generator_preread')
article_generator_context = signal('article_generator_context')

page_generator_preread = signal('page_generator_preread')
page_generator_context = signal('page_generator_context')

static_generator_preread = signal('static_generator_preread')
static_generator_context = signal('static_generator_context')

content_object_init = signal('content_object_init')

# Writers signals
content_written = signal('content_written')
feed_generated = signal('feed_generated')
feed_written = signal('feed_written')
from pelican.plugins.signals import * # noqa
@@ -0,0 +1,5 @@
NAME = 'namespace plugin'


def register():
pass
@@ -0,0 +1,5 @@
NAME = 'normal plugin'


def register():
pass

0 comments on commit a2053c3

Please sign in to comment.
You can’t perform that action at this time.