Skip to content

Instantly share code, notes, and snippets.

@martijnbastiaan
Created October 14, 2013 21:54
Show Gist options
  • Save martijnbastiaan/6982854 to your computer and use it in GitHub Desktop.
Save martijnbastiaan/6982854 to your computer and use it in GitHub Desktop.
diff --git a/amcat/contrib/plugins/README.TXT b/amcat/contrib/plugins/README.TXT
index e69de29..c3c53ed 100644
--- a/amcat/contrib/plugins/README.TXT
+++ b/amcat/contrib/plugins/README.TXT
@@ -0,0 +1,29 @@
+Plugins are modular pieces of code which inherit from special classes, indicating
+their type. As of now, only "NLP" and "Article Upload" plugins are supported, but
+more types might be added in the future.
+
+== Adding plugins ==
+You can add modules to this directory and run:
+
+ ./manage.py sync_plugins
+
+to add them to the database. The module will be searched for classes inheriting
+from one of the classes (see Types). One module can hold more than one plugin.
+
+You can also add plugins manually by creating a Plugin entry in the database. It
+does not need to be (sym)linked in this directory:
+
+ >>> from amcat.models import Plugin, PluginType
+ >>> Plugin.objects.create(
+ label="Test", class_name="my_module.UploadScript",
+ plugin_type=PluginType.objects.get(label="Article Uploading")
+ )
+ >>>
+
+When an ImportError occurs while running sync_plugins, you'll be asked whether
+the plugin can be removed. Make sure your modules are accessible when running
+manage.py.
+
+== Types ==
+Article upload: amcat.scripts.article_upload.upload.UploadScript
+NLP preprocessing: amcat.nlp.analysisscript.AnalysisScript
\ No newline at end of file
diff --git a/amcat/management/commands/sync_plugins.py b/amcat/management/commands/sync_plugins.py
index e69de29..46dd546 100644
--- a/amcat/management/commands/sync_plugins.py
+++ b/amcat/management/commands/sync_plugins.py
@@ -0,0 +1,90 @@
+###########################################################################
+# (C) Vrije Universiteit, Amsterdam (the Netherlands) #
+# #
+# This file is part of AmCAT - The Amsterdam Content Analysis Toolkit #
+# #
+# AmCAT is free software: you can redistribute it and/or modify it under #
+# the terms of the GNU Affero General Public License as published by the #
+# Free Software Foundation, either version 3 of the License, or (at your #
+# option) any later version. #
+# #
+# AmCAT is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public #
+# License for more details. #
+# #
+# You should have received a copy of the GNU Affero General Public #
+# License along with AmCAT. If not, see <http://www.gnu.org/licenses/>. #
+###########################################################################
+from inspect import isclass
+from itertools import imap, chain
+from django.core.management.base import BaseCommand, CommandError
+from amcat.models import Plugin, PluginType
+from amcat.contrib import plugins
+
+import importlib
+import os
+
+import logging; log = logging.getLogger(__name__)
+
+PLUGIN_TYPES = tuple(p.get_class() for p in PluginType.objects.all())
+PLUGIN_MODULE = plugins.__name__
+
+def is_plugin(cls):
+ """Determines whether given class represents a plugin.
+
+ @type return: bool"""
+ return isclass(cls) and issubclass(cls, PLUGIN_TYPES)
+
+def _get_plugins(directory):
+ mod = importlib.import_module(".".join((PLUGIN_MODULE, os.path.basename(directory))))
+ attrs = [getattr(mod, a) for a in dir(mod) if not a.startswith('__')]
+ return filter(is_plugin, attrs)
+
+def get_plugins(dirs):
+ """Return all classes which represent a plugin. Search modules `directories`."""
+ return chain.from_iterable(imap(_get_plugins, dirs))
+
+def get_plugin_type(cls):
+ """Returns PluginType instance based on given class."""
+ for pt in PLUGIN_TYPES:
+ if issubclass(cls, pt):
+ return PluginType.objects.get(class_name=".".join((pt.__module__, pt.__name__)))
+
+class Command(BaseCommand):
+ help = 'Syncs plugins available in amcat.contrib.plugins, and plugins currently in database.'
+
+ def handle(self, *args, **options):
+ # Remove plugins which yield an error upon importing
+ for plugin in Plugin.objects.all():
+ try:
+ plugin.get_class()
+ except ImportError:
+ while True:
+ ans = raw_input('Error on importing {plugin.class_name}. Remove? [y/N]'.format(**locals()))
+ ans = ans.strip().lower()
+
+ if ans in ("", "n"):
+ break
+ elif ans == "y":
+ plugin.delete()
+ break
+
+ # Look for plugins in plugin directory
+ plugin_files = os.listdir(os.path.dirname(plugins.__file__))
+ plugin_paths = (os.path.join(os.path.dirname(plugins.__file__), p) for p in plugin_files)
+ detected_plugins = get_plugins(filter(os.path.isdir, plugin_paths))
+ new_plugins = (p for p in detected_plugins if not Plugin.objects.filter(class_name=p.__name__).exists())
+
+ for p in new_plugins:
+ log.info("Found new plugin: {p}".format(**locals()))
+
+ plugin = Plugin.objects.create(
+ label=p.__module__[len(PLUGIN_MODULE)+1:].split(".")[0],
+ class_name=".".join((p.__module__, p.__name__)),
+ plugin_type=get_plugin_type(p)
+ )
+
+ log.info("Created new plugin: {plugin.class_name}".format(**locals()))
+
+
diff --git a/amcat/models/plugin.py b/amcat/models/plugin.py
index bff5818..506a49e 100644
--- a/amcat/models/plugin.py
+++ b/amcat/models/plugin.py
@@ -23,7 +23,6 @@ from __future__ import unicode_literals, print_function, absolute_import
from django.db import models
from amcat.tools.model import AmcatModel
from amcat.tools import classtools
-from amcat.tools.djangotoolkit import JsonField
class PluginType(AmcatModel):
"""
@@ -35,7 +34,7 @@ class PluginType(AmcatModel):
class_name = models.CharField(max_length=100, unique=True)
def get_classes(self):
- return (p.get_class() for p in self.plugins.all())
+ return (p.get_class() for p in self.plugins.only("class_name").all())
def get_class(self):
"""Return the base class defined by this plugin type"""
diff --git a/setup.py b/setup.py
index 3bd0b48..dffeae6 100644
--- a/setup.py
+++ b/setup.py
@@ -1,6 +1,9 @@
from distutils.core import setup
+from os import path
-requirements = filter(str.strip, open("pip_requirements.txt").readlines())
+here = path.abspath(path.join(path.dirname(path.abspath(__file__))))
+
+requirements = filter(str.strip, open(path.join(here, "pip_requirements.txt")).readlines())
requirements = [x for x in requirements if x and not x.startswith("#")]
package = dict(
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment