# Authors: # Jason Gerard DeRose # # 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 errors def to_cli(name): """ Takes a Python identifier and transforms it into form suitable for the Command Line Interface. """ assert isinstance(name, str) return name.replace('__', '.').replace('_', '-') def from_cli(cli_name): """ Takes a string from the Command Line Interface and transforms it into a Python identifier. """ assert isinstance(cli_name, basestring) return cli_name.replace('-', '_').replace('.', '__') class Plugin(object): """ Base class for all plugins. """ def __get_name(self): """ Returns the class name of this instance. """ return self.__class__.__name__ name = property(__get_name) def __repr__(self): """ Returns a valid Python expression that could create this plugin instance given the appropriate environment. """ return '%s.%s()' % ( self.__class__.__module__, self.__class__.__name__ ) class Proxy(object): """ Used to only export certain attributes into the dynamic API. Subclasses must list names of attributes to be proxied in the __slots__ class attribute. """ __slots__ = ( '__obj', 'name', ) def __init__(self, obj, proxy_name=None): """ Proxy attributes on `obj`. """ if proxy_name is None: proxy_name = obj.__class__.__name__ assert isinstance(proxy_name, str) object.__setattr__(self, '_Proxy__obj', obj) object.__setattr__(self, 'name', proxy_name) for name in self.__slots__: object.__setattr__(self, name, getattr(obj, name)) def __setattr__(self, name, value): """ Proxy instances are read-only. This raises an AttributeError anytime an attempt is made to set an attribute. """ raise AttributeError('cannot set %s.%s' % (self.__class__.__name__, name) ) def __delattr__(self, name): """ Proxy instances are read-only. This raises an AttributeError anytime an attempt is made to delete an attribute. """ raise AttributeError('cannot del %s.%s' % (self.__class__.__name__, name) ) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.__obj) def __str__(self): return to_cli(self.name) 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 errors.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 errors.DuplicateError(cls) # Check override: if cls.__name__ in sub_d: # Must use override=True to override: if not override: raise errors.OverrideError(base, cls) else: # There was nothing already registered to override: if override: raise errors.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