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...")
     app.logger.debug("Loading plugins...")
 
 
     plugin_manager = PluginManager(app)
     plugin_manager = PluginManager(app)
-    plugin_manager.load_plugins()
+
+    # Just a temporary solution to enable the plugins.
+    plugin_manager.enable_plugins()
 
 
     app.logger.debug(
     app.logger.debug(
         "({}) {} Plugins loaded."
         "({}) {} Plugins loaded."

+ 1 - 1
flaskbb/forum/views.py

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

+ 81 - 9
flaskbb/plugins/__init__.py

@@ -26,37 +26,40 @@ class Plugin(object):
 
 
     @property
     @property
     def name(self):
     def name(self):
+        """If not overridden, it will use the classname as the plugin name."""
         return self.__class__.__name__
         return self.__class__.__name__
 
 
     @property
     @property
     def description(self):
     def description(self):
+        """Returns a small description of the plugin."""
         return ""
         return ""
 
 
     @property
     @property
     def version(self):
     def version(self):
+        """Returns the version of the plugin"""
         return "0.0.0"
         return "0.0.0"
 
 
     def enable(self):
     def enable(self):
         """Enable the plugin."""
         """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."""
         """Disable the plugin."""
-        #pass
-        # Just temporary
-        self.uninstall()
+        raise NotImplementedError("{} has not implemented the "
+                                  "disable method".format(self.name))
 
 
     def install(self):
     def install(self):
         """The plugin should specify here what needs to be installed.
         """The plugin should specify here what needs to be installed.
         For example, create the database and register the hooks."""
         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):
     def uninstall(self):
         """Uninstalls the plugin and deletes the things that
         """Uninstalls the plugin and deletes the things that
         the plugin has installed."""
         the plugin has installed."""
-        raise NotImplemented()
+        raise NotImplementedError("{} has not implemented the "
+                                  "uninstall method".format(self.name))
 
 
     # Some helpers
     # Some helpers
     def create_table(self, model, db):
     def create_table(self, model, db):
@@ -101,3 +104,72 @@ class Plugin(object):
                 self.drop_table(model, db)
                 self.drop_table(model, db)
         else:
         else:
             raise PluginError("No models found in 'models'.")
             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 flask import flash
 from flaskbb.plugins import Plugin, hooks
 from flaskbb.plugins import Plugin, hooks
 
 
-
 #: The name of your plugin class
 #: The name of your plugin class
 __plugin__ = "ExamplePlugin"
 __plugin__ = "ExamplePlugin"
 
 
@@ -19,9 +18,14 @@ class ExamplePlugin(Plugin):
     def version(self):
     def version(self):
         return "1.0.0"
         return "1.0.0"
 
 
+    def enable(self):
+        hooks.add("beforeIndex", hello_world)
+
+    def disable(self):
+        hooks.remove("beforeIndex", hello_world)
+
     def install(self):
     def install(self):
-        # register hooks and blueprints/routes here
-        hooks.registered.beforeIndex.append(hello_world)
+        pass
 
 
     def uninstall(self):
     def uninstall(self):
         pass
         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
     flaskbb.plugins.manager
     ~~~~~~~~~~~~~~~~~~~~~~~
     ~~~~~~~~~~~~~~~~~~~~~~~
 
 
-    The Plugin loader.
+    The Plugin manager.
 
 
     :copyright: (c) 2014 by the FlaskBB Team.
     :copyright: (c) 2014 by the FlaskBB Team.
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
@@ -14,13 +14,43 @@ from werkzeug.utils import import_string
 
 
 class PluginManager(object):
 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.app = app
         self.plugin_folder = os.path.join(self.app.root_path, "plugins")
         self.plugin_folder = os.path.join(self.app.root_path, "plugins")
         self.base_plugin_package = ".".join([self.app.name, "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):
     def __getitem__(self, name):
         for plugin in self.plugins:
         for plugin in self.plugins:
@@ -28,32 +58,30 @@ class PluginManager(object):
                 return plugin
                 return plugin
         raise KeyError("Plugin %s is not known" % name)
         raise KeyError("Plugin %s is not known" % name)
 
 
-    def register_blueprints(self, blueprint):
-        self.app.register_blueprints(blueprint)
-
     def load_plugins(self):
     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():
         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):
     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)
             plugin_class = import_string(plugin)
 
 
             if plugin_class is not None:
             if plugin_class is not None:
-                self.plugins.append(plugin)
                 yield plugin_class
                 yield plugin_class
 
 
     def find_plugins(self):
     def find_plugins(self):
+        """Find all possible plugins in the plugin folder."""
         for item in os.listdir(self.plugin_folder):
         for item in os.listdir(self.plugin_folder):
-
             if os.path.isdir(os.path.join(self.plugin_folder, item)) and \
             if os.path.isdir(os.path.join(self.plugin_folder, item)) and \
                     os.path.exists(
                     os.path.exists(
                         os.path.join(self.plugin_folder, item, "__init__.py")):
                         os.path.join(self.plugin_folder, item, "__init__.py")):
@@ -64,31 +92,51 @@ class PluginManager(object):
                 tmp = __import__(
                 tmp = __import__(
                     plugin, globals(), locals(), ["__plugin__"], -1
                     plugin, globals(), locals(), ["__plugin__"], -1
                 )
                 )
+
                 try:
                 try:
                     plugin = "{}.{}".format(plugin, tmp.__plugin__)
                     plugin = "{}.{}".format(plugin, tmp.__plugin__)
-                    self.found_plugins.append(plugin)
+                    self._found_plugins.append(plugin)
                 except AttributeError:
                 except AttributeError:
                     pass
                     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:
         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:
         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]
     models = [PortalModel]
 
 
     @property
     @property
+    def name(self):
+        return "Portal Plugin"
+
+    @property
     def description(self):
     def description(self):
         return "A simple portal plugin"
         return "A simple portal plugin"
 
 
@@ -21,6 +25,12 @@ class PortalPlugin(Plugin):
     def version(self):
     def version(self):
         return "0.0.1"
         return "0.0.1"
 
 
+    def enable(self):
+        pass
+
+    def disable(self):
+        pass
+
     def install(self):
     def install(self):
         self.create_all_tables(db)
         self.create_all_tables(db)
         current_app.register_blueprint(portal)
         current_app.register_blueprint(portal)