summaryrefslogtreecommitdiffstats
path: root/ipalib/plugable.py
diff options
context:
space:
mode:
authorJason Gerard DeRose <jderose@redhat.com>2008-07-28 04:34:25 +0000
committerJason Gerard DeRose <jderose@redhat.com>2008-07-28 04:34:25 +0000
commitbc1675dc3853748064dbf1485bf58bce0e344add (patch)
tree1403fd2ae76c21b49f3ee16f91d72c79aac2d76e /ipalib/plugable.py
parent8b64314359950801f1b3220f655261bcee2ead85 (diff)
downloadfreeipa-bc1675dc3853748064dbf1485bf58bce0e344add.tar.gz
freeipa-bc1675dc3853748064dbf1485bf58bce0e344add.tar.xz
freeipa-bc1675dc3853748064dbf1485bf58bce0e344add.zip
30: Added plugable module with more generic implementation of Registrar; added corresponding unit tests
Diffstat (limited to 'ipalib/plugable.py')
-rw-r--r--ipalib/plugable.py95
1 files changed, 95 insertions, 0 deletions
diff --git a/ipalib/plugable.py b/ipalib/plugable.py
new file mode 100644
index 00000000..ba2241b9
--- /dev/null
+++ b/ipalib/plugable.py
@@ -0,0 +1,95 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2008 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 only
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Utility classes for registering plugins, base classe for writing plugins.
+"""
+
+import inspect
+import exceptions
+
+
+
+class Registrar(object):
+ def __init__(self, *allowed):
+ """
+ `*allowed` is a list of the base classes plugins can be subclassed
+ from.
+ """
+ self.__allowed = frozenset(allowed)
+ self.__d = {}
+ self.__registered = set()
+ assert len(self.__allowed) == len(allowed)
+ for base in self.__allowed:
+ assert inspect.isclass(base)
+ assert base.__name__ not in self.__d
+ self.__d[base.__name__] = {}
+
+ def __findbase(self, cls):
+ """
+ If `cls` is a subclass of a base in self.__allowed, returns that
+ base; otherwise raises SubclassError.
+ """
+ assert inspect.isclass(cls)
+ for base in self.__allowed:
+ if issubclass(cls, base):
+ return base
+ raise exceptions.SubclassError(cls, self.__allowed)
+
+ def __call__(self, cls, override=False):
+ """
+ Register the plugin `cls`.
+ """
+ if not inspect.isclass(cls):
+ raise TypeError('plugin must be a class: %r' % cls)
+
+ # Find the base class or raise SubclassError:
+ base = self.__findbase(cls)
+ sub_d = self.__d[base.__name__]
+
+ # Raise DuplicateError if this exact class was already registered:
+ if cls in self.__registered:
+ raise exceptions.DuplicateError(cls)
+
+ # Check override:
+ if cls.__name__ in sub_d:
+ # Must use override=True to override:
+ if not override:
+ raise exceptions.OverrideError(base, cls)
+ else:
+ # There was nothing already registered to override:
+ if override:
+ raise exceptions.MissingOverrideError(base, cls)
+
+ # The plugin is okay, add to __registered and sub_d:
+ self.__registered.add(cls)
+ sub_d[cls.__name__] = cls
+
+ def __getitem__(self, name):
+ """
+ Returns a copy of the namespace dict of the base class named `name`.
+ """
+ return dict(self.__d[name])
+
+ def __iter__(self):
+ """
+ Iterates through the names of the allowed base classes.
+ """
+ for key in self.__d:
+ yield key