Browse Source

It works! :)

sh4nks 11 years ago
parent
commit
396714ccd9

+ 3 - 1
flaskbb/app.py

@@ -64,7 +64,9 @@ def create_app(config=None):
     app.logger.debug("Loading plugins...")
 
     plugin_manager = PluginManager(app)
-    plugin_manager.load_plugins()
+
+    # Just a temporary solution to enable the plugins.
+    plugin_manager.enable_plugins()
 
     app.logger.debug(
         "({}) {} Plugins loaded."

+ 1 - 1
flaskbb/forum/views.py

@@ -43,7 +43,7 @@ def index():
     newest_user = User.query.order_by(User.id.desc()).first()
 
     current_app.logger.debug("Runnnig beforeIndex hook...")
-    hooks.runHook(hooks.registered.beforeIndex)
+    hooks.call("beforeIndex")
 
     # Check if we use redis or not
     if not current_app.config["REDIS_ENABLED"]:

+ 81 - 9
flaskbb/plugins/__init__.py

@@ -26,37 +26,40 @@ class Plugin(object):
 
     @property
     def name(self):
+        """If not overridden, it will use the classname as the plugin name."""
         return self.__class__.__name__
 
     @property
     def description(self):
+        """Returns a small description of the plugin."""
         return ""
 
     @property
     def version(self):
+        """Returns the version of the plugin"""
         return "0.0.0"
 
     def enable(self):
         """Enable the plugin."""
-        #raise NotImplemented()
-        # Just temporary
-        self.install()
+        raise NotImplementedError("{} has not implemented the "
+                                  "enable method".format(self.name))
 
-    def disable(self):  # pragma: no cover
+    def disable(self):
         """Disable the plugin."""
-        #pass
-        # Just temporary
-        self.uninstall()
+        raise NotImplementedError("{} has not implemented the "
+                                  "disable method".format(self.name))
 
     def install(self):
         """The plugin should specify here what needs to be installed.
         For example, create the database and register the hooks."""
-        raise NotImplemented()
+        raise NotImplementedError("{} has not implemented the "
+                                  "install method".format(self.name))
 
     def uninstall(self):
         """Uninstalls the plugin and deletes the things that
         the plugin has installed."""
-        raise NotImplemented()
+        raise NotImplementedError("{} has not implemented the "
+                                  "uninstall method".format(self.name))
 
     # Some helpers
     def create_table(self, model, db):
@@ -101,3 +104,72 @@ class Plugin(object):
                 self.drop_table(model, db)
         else:
             raise PluginError("No models found in 'models'.")
+
+
+class HookManager(object):
+    """Manages all available hooks."""
+
+    def __init__(self):
+        self.hooks = {}
+
+    def new(self, name):
+        """Creates a new hook.
+
+        :param name: The name of the hook.
+        """
+        if name not in self.hooks:
+            self.hooks[name] = Hook()
+
+    def add(self, name, callback):
+        """Adds a callback to the hook. If the hook doesn't exist, it will
+        create a new one and add than the callback will be added.
+
+        :param name: The name of the hook.
+
+        :param callback: The callback which should be added to the hook.
+        """
+        if name not in self.hooks:
+            self.new(name)
+
+        return self.hooks[name].add(callback)
+
+    def remove(self, name, callback):
+        """Removes a callback from the hook.
+
+        :param name: The name of the hook.
+
+        :param callback: The callback which should be removed
+        """
+        self.hooks[name].remove(callback)
+
+    def call(self, name, *args, **kwargs):
+        """Calls all callbacks from a named hook with the given arguments.
+
+        :param name: The name of the hook.
+        """
+        return self.hooks[name].call(*args, **kwargs)
+
+
+class Hook(object):
+    """Represents a hook."""
+
+    def __init__(self):
+        self.callbacks = []
+
+    def add(self, callback):
+        """Adds a callback to a hook"""
+        if callback not in self.callbacks:
+            self.callbacks.append(callback)
+        return callback
+
+    def remove(self, callback):
+        """Removes a callback from a hook"""
+        if callback in self.callbacks:
+            self.callbacks.remove(callback)
+
+    def call(self, *args, **kwargs):
+        """Runs all callbacks for the hook."""
+        for callback in self.callbacks:
+            callback(*args, **kwargs)
+
+hooks = HookManager()

+ 7 - 3
flaskbb/plugins/example/__init__.py

@@ -1,7 +1,6 @@
 from flask import flash
 from flaskbb.plugins import Plugin, hooks
 
-
 #: The name of your plugin class
 __plugin__ = "ExamplePlugin"
 
@@ -19,9 +18,14 @@ class ExamplePlugin(Plugin):
     def version(self):
         return "1.0.0"
 
+    def enable(self):
+        hooks.add("beforeIndex", hello_world)
+
+    def disable(self):
+        hooks.remove("beforeIndex", hello_world)
+
     def install(self):
-        # register hooks and blueprints/routes here
-        hooks.registered.beforeIndex.append(hello_world)
+        pass
 
     def uninstall(self):
         pass

+ 0 - 21
flaskbb/plugins/hooks.py

@@ -1,21 +0,0 @@
-#import hooks
-# inspired fromhttp://stackoverflow.com/questions/932069/building-a-minimal-plugin-architecture-in-python
-#hooks.registered.beforeIndex.append(hello_world)
-#hooks.runHook(hooks.registered.beforeIndex)
-
-
-class HookError(Exception):
-    pass
-
-
-class registered(object):
-    beforeIndex = []
-    afterIndex = []
-
-
-def runHook(hook):
-    for h in hook:
-        try:
-            h()
-        except Exception as e:
-            raise HookError("An error occured %s", e)

+ 81 - 33
flaskbb/plugins/manager.py

@@ -3,7 +3,7 @@
     flaskbb.plugins.manager
     ~~~~~~~~~~~~~~~~~~~~~~~
 
-    The Plugin loader.
+    The Plugin manager.
 
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
@@ -14,13 +14,43 @@ from werkzeug.utils import import_string
 
 class PluginManager(object):
 
-    def __init__(self, app):
+    def __init__(self, app=None, plugin_folder="plugins",
+                 base_plugin_package="plugins"):
+        """Initializes the PluginManager. It is also possible to initialize the
+        PluginManager via a factory. For example::
+
+            plugin_manager = PluginManager()
+            plugin_manager.init_app(app)
+
+        :param app: The flask application. It is needed to do plugin
+                    specific things like registering additional views or
+                    doing things where the application context is needed.
+
+        :param plugin_folder: The plugin folder where the plugins resides.
+
+        :param base_plugin_package: The plugins package name. It is usually the
+                                    same like the plugin_folder.
+        """
+        if app is not None:
+            self.init_app(app, plugin_folder, base_plugin_package)
+
+        # All loaded plugins
+        self._plugins = []
+
+        # All found plugins
+        self._found_plugins = []
+
+    def init_app(self, app, plugin_folder, base_plugin_package):
         self.app = app
         self.plugin_folder = os.path.join(self.app.root_path, "plugins")
         self.base_plugin_package = ".".join([self.app.name, "plugins"])
 
-        self.found_plugins = []
-        self.plugins = []
+    @property
+    def plugins(self):
+        """Returns all loaded plugins. You still need to enable them."""
+        if not len(self._plugins):
+            self._plugins = self.load_plugins()
+        return self._plugins
 
     def __getitem__(self, name):
         for plugin in self.plugins:
@@ -28,32 +58,30 @@ class PluginManager(object):
                 return plugin
         raise KeyError("Plugin %s is not known" % name)
 
-    def register_blueprints(self, blueprint):
-        self.app.register_blueprints(blueprint)
-
     def load_plugins(self):
-        """Loads and install all found plugins.
-        TODO: Only load/install activated plugins
+        """Loads all plugins. They are still disabled.
+        Returns a list with all loaded plugins. They should now be accessible
+        via self.plugins.
         """
-        self.find_plugins()
-
+        plugins = []
         for plugin in self.iter_plugins():
-            with self.app.app_context():
-                plugin().install()
+            plugins.append(plugin)
 
-        #self.enable_plugins(self.plugins)
+        return plugins
 
     def iter_plugins(self):
-        for plugin in self.found_plugins:
+        """Iterates over all possible plugins found in ``self.find_plugins()``,
+        imports them and if the import succeeded it will yield the plugin class.
+        """
+        for plugin in self.find_plugins():
             plugin_class = import_string(plugin)
 
             if plugin_class is not None:
-                self.plugins.append(plugin)
                 yield plugin_class
 
     def find_plugins(self):
+        """Find all possible plugins in the plugin folder."""
         for item in os.listdir(self.plugin_folder):
-
             if os.path.isdir(os.path.join(self.plugin_folder, item)) and \
                     os.path.exists(
                         os.path.join(self.plugin_folder, item, "__init__.py")):
@@ -64,31 +92,51 @@ class PluginManager(object):
                 tmp = __import__(
                     plugin, globals(), locals(), ["__plugin__"], -1
                 )
+
                 try:
                     plugin = "{}.{}".format(plugin, tmp.__plugin__)
-                    self.found_plugins.append(plugin)
+                    self._found_plugins.append(plugin)
                 except AttributeError:
                     pass
 
-    def enable_plugins(self, plugins=None):
-        """Enable all or selected plugins."""
+        return self._found_plugins
 
+    def install_plugins(self, plugins=None):
+        """Install all or selected plugins.
+
+        :param plugins: An iterable with plugins. If no plugins are passed
+                        it will try to install all plugins.
+        """
         for plugin in plugins or self.plugins:
-            plugin.enable()
+            with self.app.app_context():
+                plugin().install()
 
-    def disable_plugins(self, plugins=None):
-        """Disable all or selected plugins."""
+    def uninstall_plugins(self, plugins=None):
+        """Uninstall the plugin.
 
+        :param plugins: An iterable with plugins. If no plugins are passed
+                        it will try to uninstall all plugins.
+        """
         for plugin in plugins or self.plugins:
-            plugin.disable()
+            with self.app.app_context():
+                plugin().uninstall()
 
+    def enable_plugins(self, plugins=None):
+        """Enable all or selected plugins.
 
-"""
-for ipython:
-from flask import current_app
-from flaskbb.plugins import Plugin
-from flaskbb.plugins.manager import PluginManager
-manager = PluginLoader(current_app)
-manager.find_plugins()
-manager.plugins
-"""
+        :param plugins: An iterable with plugins. If no plugins are passed
+                        it will try to enable all plugins.
+        """
+        for plugin in plugins or self.plugins:
+            with self.app.app_context():
+                plugin().enable()
+
+    def disable_plugins(self, plugins=None):
+        """Disable all or selected plugins.
+
+        :param plugins: An iterable with plugins. If no plugins are passed
+                        it will try to disable all plugins.
+        """
+        for plugin in plugins or self.plugins:
+            with self.app.app_context():
+                plugin().disable()

+ 10 - 0
flaskbb/plugins/portal/__init__.py

@@ -14,6 +14,10 @@ class PortalPlugin(Plugin):
     models = [PortalModel]
 
     @property
+    def name(self):
+        return "Portal Plugin"
+
+    @property
     def description(self):
         return "A simple portal plugin"
 
@@ -21,6 +25,12 @@ class PortalPlugin(Plugin):
     def version(self):
         return "0.0.1"
 
+    def enable(self):
+        pass
+
+    def disable(self):
+        pass
+
     def install(self):
         self.create_all_tables(db)
         current_app.register_blueprint(portal)