From 9cddce0d378c07cedaa760f1ebabefc7bd2bb31a Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 27 Feb 2014 11:24:17 +0100 Subject: [PATCH] add a plugin system A sample plugin can be found in samples/ --- authentic2/plugins.py | 77 +++++++++++++++++++ authentic2/settings.py | 8 +- authentic2/urls.py | 4 +- .../a2_test_plugin/a2_test_plugin/__init__.py | 7 ++ .../a2_test_plugin/a2_test_plugin/models.py | 4 + samples/a2_test_plugin/a2_test_plugin/urls.py | 4 + .../a2_test_plugin/a2_test_plugin/views.py | 4 + samples/a2_test_plugin/setup.py | 17 ++++ 8 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 authentic2/plugins.py create mode 100644 samples/a2_test_plugin/a2_test_plugin/__init__.py create mode 100644 samples/a2_test_plugin/a2_test_plugin/models.py create mode 100644 samples/a2_test_plugin/a2_test_plugin/urls.py create mode 100644 samples/a2_test_plugin/a2_test_plugin/views.py create mode 100755 samples/a2_test_plugin/setup.py diff --git a/authentic2/plugins.py b/authentic2/plugins.py new file mode 100644 index 000000000..109a88dba --- /dev/null +++ b/authentic2/plugins.py @@ -0,0 +1,77 @@ +""" + Use setuptools entrypoints to find plugins + + Propose helper methods to load urls from plugins or modify INSTALLED_APPS +""" +import pkg_resources +import logging + + +from django.conf.urls import patterns, include, url + + +logger = logging.getLogger(__name__) + + +__ALL__ = ['get_plugins'] + +PLUGIN_CACHE = {} + +class PluginError(Exception): + pass + +def get_plugins(group_name, use_cache=True, *args, **kwargs): + '''Traverse all entry points for group_name and instantiate them using args + and kwargs. + ''' + global PLUGIN_CACHE + if group_name in PLUGIN_CACHE and use_cache: + return PLUGIN_CACHE[group_name] + plugins = [] + for entrypoint in pkg_resources.iter_entry_points(group_name): + try: + plugin_callable = entrypoint.load() + except Exception, e: + logger.exception('unable to load entrypoint %s', entrypoint) + raise PluginError('unable to load entrypoint %s' % entrypoint, e) + plugins.append(plugin_callable(*args, **kwargs)) + PLUGIN_CACHE[group_name] = plugins + return plugins + +def register_plugins_urls(group_name, urlpatterns): + '''Call get_before_urls and get_after_urls on all plugins providing them + and add those urls to the given urlpatterns. + + URLs returned by get_before_urls() are added to the head of urlpatterns + and those returned by get_after_urls() are added to the tail of + urlpatterns. + ''' + plugins = get_plugins(group_name) + before_urls = [] + after_urls = [] + for plugin in plugins: + if hasattr(plugin, 'get_before_urls'): + urls = plugin.get_before_urls() + before_urls.append(url('^', include(urls))) + if hasattr(plugin, 'get_after_urls'): + urls = plugin.get_after_urls() + after_urls.append(url('^', include(urls))) + before_patterns = patterns('', *before_urls) + after_patterns = patterns('', *after_urls) + return before_patterns + urlpatterns + after_patterns + +def register_plugins_installed_apps(group_name, installed_apps): + '''Call get_apps() on all plugins of group_name and add the returned + applications path to the installed_apps sequence. + + Applications already present are ignored. + ''' + installed_apps = list(installed_apps) + for plugin in get_plugins(group_name): + if hasattr(plugin, 'get_apps'): + apps = plugin.get_apps() + for app in apps: + if app not in installed_apps: + installed_apps.append(app) + return installed_apps + diff --git a/authentic2/settings.py b/authentic2/settings.py index f011de066..c74944cb6 100644 --- a/authentic2/settings.py +++ b/authentic2/settings.py @@ -1,8 +1,11 @@ # Django settings for authentic project. import os -from django.core.exceptions import ImproperlyConfigured import json +from django.core.exceptions import ImproperlyConfigured + +from . import plugins + gettext_noop = lambda s: s def to_boolean(name, default=True): @@ -149,6 +152,9 @@ INSTALLED_APPS = ( 'south', ) +INSTALLED_APPS = plugins.register_plugins_installed_apps('authentic2.plugin', + INSTALLED_APPS) + MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage' diff --git a/authentic2/urls.py b/authentic2/urls.py index e3cc83742..71b0ada04 100644 --- a/authentic2/urls.py +++ b/authentic2/urls.py @@ -7,7 +7,7 @@ from authentic2.idp.decorators import prevent_access_to_transient_users import authentic2.idp.views from .admin import admin -from . import app_settings +from . import app_settings, plugins admin.autodiscover() handler500 = 'authentic2.views.server_error' @@ -56,3 +56,5 @@ try: ) except: pass + +urlpatterns = plugins.register_plugins_urls('authentic2.plugin', urlpatterns) diff --git a/samples/a2_test_plugin/a2_test_plugin/__init__.py b/samples/a2_test_plugin/a2_test_plugin/__init__.py new file mode 100644 index 000000000..3b6ffc05e --- /dev/null +++ b/samples/a2_test_plugin/a2_test_plugin/__init__.py @@ -0,0 +1,7 @@ +class Plugin(object): + def get_before_urls(self): + from . import urls + return urls.urlpatterns + + def get_apps(self): + return [__name__] diff --git a/samples/a2_test_plugin/a2_test_plugin/models.py b/samples/a2_test_plugin/a2_test_plugin/models.py new file mode 100644 index 000000000..f9de96f88 --- /dev/null +++ b/samples/a2_test_plugin/a2_test_plugin/models.py @@ -0,0 +1,4 @@ +from django.db import models + +class MyModel(models.Model): + a = models.TextField() diff --git a/samples/a2_test_plugin/a2_test_plugin/urls.py b/samples/a2_test_plugin/a2_test_plugin/urls.py new file mode 100644 index 000000000..604db17af --- /dev/null +++ b/samples/a2_test_plugin/a2_test_plugin/urls.py @@ -0,0 +1,4 @@ +from django.conf.urls import patterns, url + +urlpatterns = patterns('a2_test_plugin.views', + url('^test/', 'test')) diff --git a/samples/a2_test_plugin/a2_test_plugin/views.py b/samples/a2_test_plugin/a2_test_plugin/views.py new file mode 100644 index 000000000..efde38e74 --- /dev/null +++ b/samples/a2_test_plugin/a2_test_plugin/views.py @@ -0,0 +1,4 @@ +from django.http import HttpResponse + +def test(request): + return HttpResponse('coucou') diff --git a/samples/a2_test_plugin/setup.py b/samples/a2_test_plugin/setup.py new file mode 100755 index 000000000..d5a6190c0 --- /dev/null +++ b/samples/a2_test_plugin/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/python +from setuptools import setup, find_packages +import os + +setup(name='authentic2-test-plugin', + version='1.0', + license='AGPLv3', + description='Authentic2 Test Plugin', + author="Entr'ouvert", + author_email="info@entrouvert.com", + packages=find_packages(os.path.dirname(__file__) or '.'), + entry_points={ + 'authentic2.plugin': [ + 'test-plugin = a2_test_plugin:Plugin', + ], + }, +)