Created
October 14, 2013 21:54
-
-
Save martijnbastiaan/6982854 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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