From b0e41d715960bfd3abe0a1caa4c9861a8d5732b5 Mon Sep 17 00:00:00 2001 From: Ian Anderson Date: Tue, 15 Sep 2009 11:11:46 -0500 Subject: [PATCH] Adding Ducksoup module Signed-off-by: Ian Anderson --- .gitignore | 4 +++ MANIFEST.in | 1 + README.textile | 66 +++++++++++++++++++++++++++++++++++++++++++ ducksoup/__init__.py | 9 ++++++ ducksoup/interface.py | 37 ++++++++++++++++++++++++ ducksoup/plugin.py | 62 ++++++++++++++++++++++++++++++++++++++++ setup.py | 20 +++++++++++++ 7 files changed, 199 insertions(+) create mode 100644 .gitignore create mode 100644 MANIFEST.in create mode 100644 README.textile create mode 100644 ducksoup/__init__.py create mode 100644 ducksoup/interface.py create mode 100644 ducksoup/plugin.py create mode 100755 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f0a128 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +dist/ +*.egg-info/ +*.pyc diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..51db70e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.textile \ No newline at end of file diff --git a/README.textile b/README.textile new file mode 100644 index 0000000..6feb381 --- /dev/null +++ b/README.textile @@ -0,0 +1,66 @@ +h1. Ducksoup Module + +A ducktype plugin library for python. The module does a "ducktype" interface check on a "setuptools":http://peak.telecommunity.com/DevCenter/setuptools +entry point module. If the module passes, it is listed as an available plugin. + +_*{color:red;}Requires "setuptools":http://peak.telecommunity.com/DevCenter/setuptools*_ + +h2. Installing + +Run the following shell commands: + +
+git clone git://github.com/getfatday/ducksoup.git
+cd ducksoup
+./setup.py install
+
+ +h2. Usage + +Define a plugin interface and entry point for you application: + +
+# Define an interface.
+
+class ICommand(IPlugin):
+
+  name = ""
+  description = ""
+  def execute(*arg): pass
+
+# Register an entry point.
+
+class Command(plugin):
+    __entry_point__ = "ducksoup.plugin.commands"
+    __interface__ = ICommand
+
+ +Register an external module as a plugin. + +
+from setuptools import setup
+
+setup(
+  name="mycommands",
+  version="0.0.1",
+  description="""My Commands""",
+  entry_points="""
+  [ducksoup.plugin.commands]
+  mycommands.command = mycommands.command:Command
+  """
+)
+
+ +Search for available plugins: + +
+    
+for entry in Command().entries:
+    print """Name: %s
+Description: %s
+""" % (entry.name, entry.description)    
+
+ +h2. Feedback + +Shoot me an email at "getfatday@gmail.com":mailto:getfatday@gmail.com with any feedback. \ No newline at end of file diff --git a/ducksoup/__init__.py b/ducksoup/__init__.py new file mode 100644 index 0000000..22eb48e --- /dev/null +++ b/ducksoup/__init__.py @@ -0,0 +1,9 @@ +"""ducksoup + +A ducktype plugin library for python. The module does a "ducktype" interface check on a "setuptools":http://peak.telecommunity.com/DevCenter/setuptools +entry point module. If the module passes, it is listed as an available plugin. +""" + +__version__ = '0.0.1' +__dist__ = 'git://github.com/getfatday/ducksoup.git' + diff --git a/ducksoup/interface.py b/ducksoup/interface.py new file mode 100644 index 0000000..f92f56b --- /dev/null +++ b/ducksoup/interface.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +interface.py + +Created by Ian Anderson on 2009-07-08. + +Derived from: +http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/204349 +http://svn.pythonfr.org/public/pythonfr/plugin_framework/framework +""" + +import inspect + +class interface_type(type): + """ + Validates the presence of required attributes + """ + + def __new__(cls, classname, bases, classdict): + obj = type.__new__(cls, classname, bases, classdict) + interface = classdict.get('__implements__') + + if interface: + defined = set(dir(obj)) + required = set(dir(interface)) + if not required.issubset(defined): + missing = list(required - defined) + error = "Not implemented methods from %s : %r" + raise KeyError, error % (interface.__name__, missing) + + return obj + + +class interface(object): + __metaclass__ = interface_type + __implements__ = None \ No newline at end of file diff --git a/ducksoup/plugin.py b/ducksoup/plugin.py new file mode 100644 index 0000000..5598761 --- /dev/null +++ b/ducksoup/plugin.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +plugin.py + +Created by Ian Anderson on 2009-07-08. +""" + +from interface import interface +from pkg_resources import iter_entry_points + +class IPlugin(): + # Returns true if plugin is enabled + def enabled(self): pass + +class plugin_type(type): + + def __init__(cls, classname, bases, classdict): + super(plugin_type, cls).__init__(classname, bases, classdict) + setattr(cls, '__entries__', None) + setattr(cls, '__invalid__', None) + +class plugin(object): + + __metaclass__ = plugin_type + __entry_point__ = "ducksoup.plugin" + __interface__ = IPlugin + + @classmethod + def enabled(cls): + return True + + @property + def entries(self): + if self.__entries__ == None: + self.__getentries__() + return self.__entries__ + + @property + def invalid_entries(self): + if self.__invalid__ == None: + self.__getentries__() + return self.__invalid__ + + def __getentries__(self): + """Returns plugin classes avialable""" + + self.__entries__ = [] + self.__invalid__ = [] + + for e in iter_entry_points(group=self.__entry_point__): + cls = e.load() + defined = set(dir(cls)) + required = set(dir(self.__interface__)) + if not required.issubset(defined): + self.__invalid__.append(cls) + missing = list(required - defined) + error = "Plug-in '%s' missing implementations of '%s' required for '%s'" + print error % (cls.__name__, ", ".join(missing), self.__entry_point__) + else: + if cls.enabled(): + self.__entries__.append(cls) \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..859c35b --- /dev/null +++ b/setup.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +# encoding: utf-8 + +""" +Created by Ian Anderson on 2009-09-15. +""" + +from setuptools import setup +from ducksoup import __version__ as version +from ducksoup import __doc__ as long_description + +setup(name = "ducksoup", + version = version, + description = "A ducktype plugin library for python", + author = "Ian Anderson", + author_email = "getfatday@gmail.com", + url = "http://github.com/getfatday/ducksoup", + packages = ['ducksoup',], + long_description = long_description +) \ No newline at end of file