source: pyutilib.component.core/trunk/pyutilib/plugin/core/core.py @ 1626

Revision 1626, 31.7 KB checked in by wehart, 4 years ago (diff)

Renaming pyutilib.plugin.* to pyutilib.component.*

This namechange was prompted by some documentation efforts that John S. and
I have started with this package. We've decided to call pyutilib.component.core
the PyUtilib? Component Architecture. A plugin is simply a component definition.

Looking at other component/plugin packages, they use 'component' rather than
'plugin', so ...

Line 
1#  _________________________________________________________________________
2#
3#  PyUtilib: A Python utility library.
4#  Copyright (c) 2008 Sandia Corporation.
5#  This software is distributed under the BSD License.
6#  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7#  the U.S. Government retains certain rights in this software.
8#  _________________________________________________________________________
9
10# This software is adapted from the Trac software (specifically, the trac.core
11# module.  The Trac copyright statement is included below.
12
13"""
14The PyUtilib Component Architecture (PCA) consists of the following core classes:
15
16* Interface - Subclasses of this class declare component interfaces that are registered in the framework
17
18* ExtensionPoint - A class used to declare extension points, which can access services with a particular interface
19
20* Plugin - Subclasses of this class declare plugins, which can be used to provide services within the PCA.
21
22* SingletonPlugin - Subclasses of this class declare singleton plugins, for which a single instance can be declared.
23
24* PluginEnvironment - A class that maintains the registries for interfaces, extension points and components.
25
26* PluginGlobals - A class that maintains global data concerning the set of environments that are currently being used.
27
28* PluginError - The exception class that is raised when errors arise in this framework.
29
30Note: The outline of this framework is adapted from Trac (see the trac.core module).  This framework generalizes the Trac by supporting multi-environment management of components, as well as non-singleton plugins.  For those familiar with Trac, the following classes roughly correspond with each other:
31
32  Trac                  PyUtilib
33  ----------------------------------------
34  Interface             Interface
35  ExtensionPoint        ExtensionPoint
36  Component             SingletonPlugin
37  ComponentManager      PluginEnvironment
38"""
39
40import re
41import logging
42import sys
43
44__all__ = ['Plugin', 'SingletonPlugin', 'PluginGlobals',
45           'ExtensionPoint', 'implements', 'Interface',
46           'PluginError', 'PluginEnvironment', 'IPluginLoader',
47           'IPluginLoadPath', 'PluginFactory',
48           'IIgnorePluginWhenLoading' ]
49
50
51#
52# Define the default logging behavior for a given namespace, which is to
53# ignore the log messages.
54#
55def logger_factory(namespace):
56    log = logging.getLogger(namespace)
57    class NullHandler(logging.Handler):
58        def emit(self, record):         #pragma:nocover
59            """Do not generate logging record"""
60    log.addHandler(NullHandler())
61    return log
62
63
64class PluginError(Exception):
65    """Exception base class for plugin errors."""
66
67    def __init__(self, value):
68        """Constructor, whose argument is the error message"""
69        self.value = value
70
71    def __str__(self):
72        """Return a string value for this message"""
73        return str(self.value)
74
75
76"""
77Global data for plugins.  The main role of this class is to manage the stack of PluginEnvironment instances.
78
79Note: a single ID counter is used for tagging both environment and plugins registrations.  This enables the user to track the relative order of construction of these objects.
80"""
81class PluginGlobals(object):
82
83    def __init__(self):                         #pragma:nocover
84        """Disable construction."""
85        raise PluginError, "The PluginGlobals class should not be created."
86
87    """The registry of interfaces, by name"""
88    interface_registry = {}
89
90    """The registry of plugins, by name"""
91    plugin_registry = {}
92
93    """The registry of environments, by name"""
94    env_registry = {}
95
96    """The stack of environments that is being used."""
97    env_stack = []
98
99    """A unique id used to name plugin objects"""
100    id_counter = 0
101
102    @staticmethod
103    def clear(bootstrap=False):
104        """
105        Clears the environment stack and defines a new default environment.
106        This setup is non-standard because we need to bootstrap the
107        configuration of the 'pyutilib.plugin' environment.
108   
109        NOTE: I _think_ that the plugin_registry should also be cleared,
110        but in practice that may not make sense since it's not easy to
111        reload modules in Python.
112        """
113        PluginGlobals.clearing=True
114        if len(PluginGlobals.env_stack) > 0:
115            PluginGlobals.env_stack[0].log.info("Clearing the PluginGlobals data")
116        PluginGlobals.env_registry = {}
117        PluginGlobals.env_stack=[]
118        PluginGlobals.id_counter=0
119        env = PluginEnvironment(name="pca", bootstrap=True)
120        PluginGlobals.env_registry[env.name] = env
121        PluginGlobals.push_env( PluginEnvironment(name="<default>", bootstrap=bootstrap) )
122        PluginGlobals.clearing=False
123
124    @staticmethod
125    def next_id():
126        """Generate the next id for plugin objects"""
127        PluginGlobals.id_counter += 1
128        return PluginGlobals.id_counter
129
130    @staticmethod
131    def default_env():
132        """
133        Return the default environment, which is constructed when the
134        plugins framework is loaded.
135        """
136        return PluginGlobals.env_stack[0]             #pragma:nocover
137
138    @staticmethod
139    def env(arg=None):
140        """Return the current environment."""
141        if arg is None:
142            return PluginGlobals.env_stack[-1]
143        else:
144            if not arg in PluginGlobals.env_registry:
145                raise PluginError, "Unknown environment %r" % arg
146            return PluginGlobals.env_registry[arg]
147
148    @staticmethod
149    def push_env(arg, validate=False):
150        """Push the given environment on the stack."""
151        if isinstance(arg,basestring):
152            if not arg in PluginGlobals.env_registry:
153                if validate:
154                    raise PluginError, "Unknown environment %r" % arg
155                else:
156                    env = PluginEnvironment(arg)
157            env = PluginGlobals.env_registry[arg]
158        else:
159            env = arg
160        PluginGlobals.env_stack.append(env)
161        env.log.debug("Pushing environment %r on the PluginGlobals stack" % env.name)
162       
163    @staticmethod
164    def pop_env():
165        """Pop the current environment from the stack."""
166        if len(PluginGlobals.env_stack) == 1:
167            env = PluginGlobals.env_stack[0]
168        else:
169            env = PluginGlobals.env_stack.pop()
170            env.log.debug("Popping environment %r from the PluginGlobals stack" % env.name)
171        return env
172       
173    @staticmethod
174    def services(name=None):
175        """
176        A convenience function that returns the services in the
177        current environment.
178        """
179        return PluginGlobals.env(name).services
180
181    @staticmethod
182    def singleton_services(name=None):
183        """
184        A convenience function that returns the singleton
185        services in the current environment.
186        """
187        return PluginGlobals.env(name).singleton_services
188
189    @staticmethod
190    def load_services(**kwds):
191        """Load services from IPluginLoader extension points"""
192        PluginGlobals.env().load_services(**kwds)
193       
194    @staticmethod
195    def pprint(**kwds):
196        """A pretty-print function"""
197        s = ""
198        s += "--------------------------------------------------------------\n"
199        s += " Registered Environments\n"
200        s += "--------------------------------------------------------------\n"
201        keys = PluginGlobals.env_registry.keys()
202        keys.sort()
203        for key in keys:
204            s += " "+key+"\n"
205        s += "\n"
206        s += "--------------------------------------------------------------\n"
207        s += " Environment Stack\n"
208        s += "--------------------------------------------------------------\n"
209        i=1
210        for env in PluginGlobals.env_stack:
211            s += " Level="+str(i)+"  name="
212            s += env.name
213            s += "\n"
214            i += 1
215        s += "\n"
216        s += "--------------------------------------------------------------\n"
217        s += " Interfaces Declared\n"
218        s += "--------------------------------------------------------------\n"
219        keys = PluginGlobals.interface_registry.keys()
220        keys.sort()
221        for key in keys:
222            s += " "+key+"\n"
223        s += "\n"
224        s += "--------------------------------------------------------------\n"
225        s += " Interfaces Declared by Namespace\n"
226        s += "--------------------------------------------------------------\n"
227        keys = PluginGlobals.interface_registry.keys()
228        keys.sort()
229        tmp = {}
230        for key in keys:
231            tmp.setdefault(PluginGlobals.interface_registry[key].__interface_namespace__,[]).append(key)
232        keys = tmp.keys()
233        keys.sort()
234        for key in keys:
235            s += " "+str(key)+"\n"
236            for item in tmp[key]:
237                s += "     "+item+"\n"
238            s += "\n"
239        #
240        # Coverage is disabled here because different platforms give different
241        # results.
242        #
243        if "plugins" not in kwds or kwds["plugins"] is True:    #pragma:nocover
244            s += "--------------------------------------------------------------\n"
245            s += " Registered Plugins by Interface\n"
246            s += "--------------------------------------------------------------\n"
247            tmp = {}
248            for key in PluginGlobals.interface_registry:
249                tmp[PluginGlobals.interface_registry[key]] = []
250            for key in PluginGlobals.plugin_registry:
251                for item in PluginGlobals.plugin_registry[key].__interfaces__:
252                    tmp[item].append(key)
253            keys = PluginGlobals.interface_registry.keys()
254            keys.sort()
255            for key in keys:
256                if key == "":                   #pragma:nocover
257                    s += " `"+str(key)+"`\n"
258                else:
259                    s += " "+str(key)+"\n"
260                ttmp = tmp[PluginGlobals.interface_registry[key]]
261                ttmp.sort()
262                if len(ttmp) == 0:
263                    s += "     None\n"
264                else:
265                    for item in ttmp:
266                        s += "     "+item+"\n"
267                s += "\n"
268            s += "--------------------------------------------------------------\n"
269            s += " Registered Plugins by Python Module\n"
270            s += "--------------------------------------------------------------\n"
271            tmp = {}
272            for key in PluginGlobals.plugin_registry:
273                tmp.setdefault(PluginGlobals.plugin_registry[key].__module__,[]).append(key)
274            keys = tmp.keys()
275            keys.sort()
276            for key in keys:
277                if key == "":                   #pragma:nocover
278                    s += " `"+str(key)+"`\n"
279                else:
280                    s += " "+str(key)+"\n"
281                ttmp = tmp[key]
282                ttmp.sort()
283                for item in ttmp:
284                    s += "     "+item+"\n"
285                s += "\n"
286        s += "--------------------------------------------------------------\n"
287        s += " Services for Registered Environments\n"
288        s += "--------------------------------------------------------------\n"
289        keys = PluginGlobals.env_registry.keys()
290        keys.sort()
291        if 'show_ids' in kwds:
292            show_ids = kwds['show_ids']
293        else:
294            show_ids = True
295        for key in keys:
296            s += PluginGlobals.env(key).pprint(show_ids=show_ids)
297            s += "\n"
298        s += "--------------------------------------------------------------\n"
299        print s
300
301
302class InterfaceMeta(type):
303    """Meta class that registered the declaration of an interface"""
304
305    def __new__(cls, name, bases, d):
306        """Register this interface"""
307        if name == "Interface":
308            d['__interface_namespace__'] = 'pca'
309        else:
310            d['__interface_namespace__'] = PluginGlobals.env().name
311        new_class = type.__new__(cls, name, bases, d)
312        if name != "Interface":
313            if name in PluginGlobals.interface_registry.keys():
314                raise PluginError, "Interface %s has already been defined" % name
315            PluginGlobals.interface_registry[name] = new_class
316        return new_class
317
318
319class Interface(object):
320    """
321    Marker base class for extension point interfaces.  This class
322    is not intended to be instantiated.  Instead, the declaration
323    of subclasses of Interface are recorded, and these
324    classes are used to define extension points.
325    """
326
327    __metaclass__ = InterfaceMeta
328
329
330class ExtensionPoint(object):
331    """Marker class for extension points in services."""
332
333    def __init__(self, *args):
334        """Create the extension point.
335       
336        @param interface: the `Interface` subclass that defines the protocol
337            for the extension point
338        @param env: the `PluginEnvironment` instance that this extension point
339            references
340        """
341        #
342        # Construct the interface, passing in this extension
343        #
344        nargs=len(args)
345        if nargs == 0:
346            raise PluginError, "Must specify interface class used in the ExtensionPoint"
347        self.interface = args[0]
348        self.env = [ PluginGlobals.env(self.interface.__interface_namespace__) ]
349        if nargs > 1:
350            for arg in args[1:]:
351                if isinstance(arg,basestring):
352                    self.env.append( PluginGlobals.env(arg) )
353                else:
354                    self.env.append(arg)
355        self.__doc__ = 'List of services that implement `%s`' % self.interface.__name__
356
357    def __iter__(self):
358        """
359        Return an iterator to a set of services that match the interface of this
360        extension point.
361        """
362        return self.extensions().__iter__()
363
364    def __call__(self, key=None, all=False):
365        """
366        Return a set of services that match the interface of this
367        extension point.
368        """
369        if type(key) in (int, long):
370            #
371            # Q: should this be a warning?  A user _might_ be trying
372            # to use an integer as a key.  But in practice that's not
373            # likely.
374            #
375            raise PluginError, "Access of the n-th extension point is disallowed.  This is not well-defined, since ExtensionPoints are stored as unordered sets."
376        return self.extensions(all=all, key=key)
377
378    def service(self, key=None, all=False):
379        """
380        Return the unique service that matches the interface of this
381        extension point.  An exception occurs if no service matches the
382        specified key, or if multiple services match.
383        """
384        ans = ExtensionPoint.__call__(self, key=key, all=all)
385        if len(ans)== 1:
386            #
387            # There is a single service, so return it.
388            #
389            return ans.pop()
390        elif len(ans) == 0:
391            return None
392        else:
393            raise PluginError, "The ExtensionPoint does not have a unique service!  %d services are defined for interface %s.  (key=%s)" % (len(ans), self.interface. __name__, str(key))
394
395    def __len__(self):
396        """
397        Return the number of services that match the interface of this
398        extension point.
399        """
400        return len(self.extensions())
401
402    def extensions(self, all=False, key=None):
403        """
404        Return a set of services that match the interface of this
405        extension point.  This tacitly filters out disabled extension points.
406        """
407        ans = set()
408        for env in self.env:
409            ans.update(env.active_services(self.interface, all=all, key=key))
410        return ans
411
412    def __repr__(self):
413        """Return a textual representation of the extension point."""
414        env_str = ""
415        for env in self.env:
416            env_str += " env=%s" % env.name
417        return '<ExtensionPoint %s%s>' % (self.interface.__name__,env_str)
418
419
420"""
421The environment for the components in the PCA.
422
423This class has the following attributes that a user may use:
424
425* name - A string that identifies this environment.  By default a unique integer id is used to define the name "env.#"
426# namespace - A name the defines the relationship of this environment to other environments
427* registry - A map from interfaces to registered services that match each interface
428* services - The set of all services (Plugin instances) that have been registered in this environment
429* singleton_services - Singleton services, which can only be registered once in each environment
430* enabled - A cache that denotes whether a service has been enabled.
431
432The namespace of Environment instances is dynamically generated by extending the namespace of the current environment.  However, the environment namespace can be explicitly declared in the constructor.
433"""
434class PluginEnvironment(object):
435
436    def __init__(self, name=None, bootstrap=False):
437        if name is None:
438            self.name = "env"+str(PluginGlobals.next_id())
439        else:
440            self.name = name
441        if self.name in PluginGlobals.env_registry:
442            raise PluginError, "The Environment %r already exists!" % self.name
443        PluginGlobals.env_registry[self.name] = self
444        self.singleton_services={}
445        self.services=set()
446        if not bootstrap:
447            self.loaders = ExtensionPoint(IPluginLoader)
448            self.loader_paths = ExtensionPoint(IPluginLoadPath)
449        self.log = logger_factory(self.name)
450        self.log.debug("Creating PluginEnvironment %r" % self.name)
451        self.level = []
452        self.clear_cache()
453
454    def __del__(self):
455        #
456        # If the PluginGlobals.clear() method is being called, then
457        # don't try to remove data from the environment registry.  It
458        # has already been deleted!
459        #
460        if not PluginGlobals.clearing:
461            if self.name in PluginGlobals.env_registry:
462                del PluginGlobals.env_registry[self.name]
463
464    def __contains__(self, cls):
465        """
466        Return whether the given service is in the set of services.
467        """
468        return cls in self.services
469
470    def active_services(self, cls, all=False, key=None):
471        """
472        Return the services that have been activated for a specific interface class.
473        """
474        if isinstance(cls,Plugin):
475            id = cls.__class__
476        else:
477            id = cls
478        try:
479            return self._cache[id,all,key]
480        except KeyError:
481            if not issubclass(id,Interface):
482                raise PluginError, "PluginEnvironment[x] expects "+str(id)+" to be an Interface class"
483            strkey = str(key)
484            tmp = filter(lambda x: id in x.__interfaces__ and (all or x.enabled()) and (key is None or x.name == strkey), self.services)
485            self._cache[id,all,key]=tmp
486            return tmp
487
488    def activate(self, service):
489        """
490        This adds the service to this environment.
491        """
492        self.log.info("Adding service %s to environment %s" % (service.name, self.name))
493        self.services.add(service)
494        self.clear_cache()
495
496    def deactivate(self, service):
497        """
498        This removes the service from this environment.
499        """
500        self.log.info("Removing service %s from environment %s" % (service.name, self.name))
501        if service in self.services:
502            self.services.remove(service)
503        self.clear_cache()
504
505    def __repr__(self):
506        self.pprint()
507
508    def pprint(self, show_ids=True):
509        """
510        Provides a detailed summary of this environment
511        """
512        s = ""
513        s += " Services for Environment %r\n" % self.name
514        flag=True
515        tmp = {}
516        for service in self.services:
517            tmp[str(service)] = service
518        keys = tmp.keys()
519        keys.sort()
520        for key in keys:
521            flag=False
522            s += "   "+key
523            if show_ids:
524                s += "  ("
525                if not tmp[key].enabled():
526                    s += "-"                    #pragma:nocover
527                s += str(tmp[key].id)
528                if tmp[key].__class__ in self.singleton_services:
529                    s += "*"
530                s += ")\n"
531            else:
532                s += "\n"
533        if flag:
534            s += "   None\n"
535        return s
536
537    def load_services(self, path=None, auto_disable=False, name_re=True):
538        """Load services from IPluginLoader extension points"""
539        #
540        # Construct the search path
541        #
542        search_path = []
543        if not path is None:
544            if isinstance(path,basestring):
545                search_path.append(path)
546            elif type(path) is list:
547                search_path += path
548            else:
549                raise PluginError, "Unknown type of path argument: "+str(type(path))
550        for item in self.loader_paths:
551            search_path += item.get_load_path()
552        self.log.info("Loading services to environment %s from search path %s" % (self.name, search_path))
553        #
554        # Compile the enable expression
555        #
556        if type(auto_disable) is bool:
557            if auto_disable:
558                disable_p = re.compile("")
559            else:
560                disable_p = re.compile("^$")
561        else:
562            disable_p = re.compile(auto_disable)
563        #
564        # Compile the name expression
565        #
566        if type(name_re) is bool:
567            if name_re:
568                name_p = re.compile("")
569            else:                           #pragma:nocover
570                raise PluginError, "It doesn't make sense to specify name_re=False"
571        else:
572            name_p = re.compile(name_re)
573
574        for loader in self.loaders:
575            loader.load(self, search_path, disable_p, name_p)
576        self.clear_cache()
577
578    def clear_cache(self):
579        """ Clear the cache of active services """
580        self._cache = {}
581
582
583#
584# Reset the plugins environment when this module is first loaded.
585#
586PluginGlobals.clear(bootstrap=True)
587PluginGlobals.push_env("pca")
588
589
590class IPluginLoader(Interface):
591    """An interface for loading plugins."""
592
593    def load(self, env, path, disable_re, name_re):
594        """Load plugins found on the specified path.  If disable_re is
595        not none, then it is interpreted as a regular expression.  If this
596        expression matches the path of a plugin, then that plugin is
597        disabled.  Otherwise, the plugin is enabled by default.
598        """
599
600
601class IPluginLoadPath(Interface):
602
603    def get_load_path(self):
604        """Returns a list of paths that are searched for plugins"""
605
606
607class IIgnorePluginWhenLoading(Interface):
608    """Interface used by Plugin loaders to identify Plugins that should
609    be ignored"""
610
611    def ignore(self, name):
612        """Returns true if a loader should ignore a plugin during loading"""
613
614
615PluginGlobals.env("<default>").loaders = ExtensionPoint(IPluginLoader)
616PluginGlobals.env("<default>").loader_paths = ExtensionPoint(IPluginLoadPath)
617PluginGlobals.env("pca").loaders = ExtensionPoint(IPluginLoader)
618PluginGlobals.env("pca").loader_paths = ExtensionPoint(IPluginLoadPath)
619
620
621class PluginMeta(type):
622    """Meta class for the Plugin class.  This meta class
623    takes care of service and extension point registration.  This class
624    also instantiates singleton plugins.
625    """
626
627    def __new__(cls, name, bases, d):
628        """Find all interfaces that need to be registered."""
629        #
630        # Avoid cycling in the Python logic by hard-coding the behavior
631        # for the Plugin and SingletonPlugin classes.
632        #
633        if name == "Plugin":
634            d['__singleton__'] = False
635            return type.__new__(cls, name, bases, d)
636        if name == "SingletonPlugin":
637            d['__singleton__'] = True
638            return type.__new__(cls, name, bases, d)
639        if name == "ManagedSingletonPlugin":
640            #
641            # This is a derived class of SingletonPlugin for which
642            # we do not need to build an instance
643            #
644            d['__singleton__'] = True
645            return type.__new__(cls, name, bases, d)
646        #
647        # Capture the environment namespace that this plugin is declared in
648        #
649        d['__plugin_namespace__'] = PluginGlobals.env().name
650        #
651        # Find all interfaces that this plugin will support
652        #
653        __interfaces__ = {}
654        for interface in d.get('_implements', {}):
655            __interfaces__.setdefault(interface,[]).extend( d['_implements'][interface] )
656        for base in [base for base in bases if hasattr(base, '__interfaces__')]:
657            for interface in base.__interfaces__:
658                __interfaces__.setdefault(interface,[]).extend( base.__interfaces__[interface] )
659        d['__interfaces__'] = __interfaces__
660        #
661        # Create a boolean, which indicates whether this is
662        # a singleton class.
663        #
664        if True in [issubclass(x, SingletonPlugin) for x in bases]:
665            d['__singleton__'] = True
666        else:
667            d['__singleton__'] = False
668        #
669        # Add interfaces to the list of base classes if they are
670        # declared inherited.
671        #
672        flag=False
673        bases = list(bases)
674        for interface in d.get('_inherited_interfaces', set()):
675            if not interface in bases:
676                bases.append(interface)
677                flag=True
678        if flag:
679            cls=MergedPluginMeta
680        #
681        # Create new class
682        #
683        new_class = type.__new__(cls, name, tuple(bases), d)
684        if d['__singleton__']:
685            #
686            # Here, we create an instance of a singleton class, which
687            # registers itself in PluginGlobals.singleton_services
688            #
689            PluginGlobals.singleton_services()[new_class] = True
690            __instance__ = new_class()
691            PluginGlobals.singleton_services()[new_class] = __instance__
692        else:
693            __instance__ = None
694        setattr(new_class,'__instance__',__instance__)
695        setattr(new_class,'__env__',PluginGlobals.env().name)
696        #
697        # Register this plugin
698        #
699        PluginGlobals.plugin_registry[name] = new_class
700        return new_class
701
702
703class MergedPluginMeta(PluginMeta,InterfaceMeta):
704
705    def __new__(cls, name, bases, d):
706        return PluginMeta.__new__(cls, name, bases, d)
707
708
709class Plugin(object):
710    """Base class for plugins.  A `service' is an instance of a Plugin.
711
712    Every Plugin class can declare what extension points it provides, as well as
713    what extension points of other Plugin's it extends.
714    """
715
716    __metaclass__ = PluginMeta
717
718    def __init__(self, **kwargs):
719        if "name" in kwargs:
720            self.name=kwargs["name"]
721
722    def __new__(cls, *args, **kwargs):
723        """Plugin constructor"""
724        #
725        # If this service is a singleton, then allocate and configure
726        # it differently.
727        #
728        env = getattr(cls,'__env__',None)
729        #print "Y",cls, cls in PluginGlobals.singleton_services(env),env
730        if cls in PluginGlobals.singleton_services(env):       #pragma:nocover
731            self = PluginGlobals.singleton_services(env)[cls]
732            #print "Z",cls,self
733            if self == True:
734                self = super(Plugin, cls).__new__(cls)
735                self.id=PluginGlobals.next_id()
736                self.name = self.__class__.__name__
737                self.activate()
738            self._enable = True
739            cls.__instance__ = self
740            return self
741        self = super(Plugin, cls).__new__(cls)
742        #
743        # Set unique instance id value
744        #
745        self.id=PluginGlobals.next_id()
746        self.name = "Plugin."+str(self.id)
747        self._enable = True
748        cls.__instance__ = None
749        #
750        # Activate the Plugin
751        #
752        self.activate()
753        return self
754
755    def implements(interface, namespace=None, inherit=False):
756        """
757        Can be used in the class definition of `Plugin` subclasses to
758        declare the extension points that are implemented by this
759        interface class.
760
761        If the `inherits` option is True, then this `Plugin` class
762        inherits from the `interface` class.
763        """
764        frame = sys._getframe(1)
765        locals_ = frame.f_locals
766        #
767        # Some sanity checks
768        #
769        assert namespace is None or isinstance(namespace,basestring), \
770               'second implements() argument must be a string'
771        assert locals_ is not frame.f_globals and '__module__' in locals_, \
772               'implements() can only be used in a class definition'
773        #
774        if namespace is None:
775            namespace = interface.__interface_namespace__
776        if inherit:
777            locals_.setdefault('_inherited_interfaces', set()).add(interface)
778        locals_.setdefault('_implements', {}).setdefault(interface,[]).append(namespace)
779    implements = staticmethod(implements)
780
781    def __repr__(self):
782        """Return a textual representation of the plugin."""
783        if self.__class__.__name__ == self.name:
784            return '<Plugin %s>' % (self.__class__.__name__)
785        else:
786            return '<Plugin %s %r>' % (self.__class__.__name__, self.name)
787
788    def activate(self):
789        """
790        Add this service to the global environment, and environments that manage the service's
791        interfaces.
792        """
793        for interface in self.__interfaces__:
794            for ns in self.__interfaces__[interface]:
795                PluginGlobals.env(ns).activate(self)
796        PluginGlobals.env(self.__plugin_namespace__).activate(self)
797       
798    def deactivate(self):
799        """
800        Remove this service from the global environment, and environments that manage the service's
801        interfaces.
802        """
803        for interface in self.__interfaces__:
804            for ns in self.__interfaces__[interface]:
805                PluginGlobals.env(ns).deactivate(self)
806        PluginGlobals.env(self.__plugin_namespace__).deactivate(self)
807       
808    def disable(self):
809        """Disable this plugin."""
810        self._enable = False
811        #
812        # Clear the cache for environments that use this plugin
813        #
814        for interface in self.__interfaces__:
815            for ns in self.__interfaces__[interface]:
816                PluginGlobals.env(ns).clear_cache()
817        PluginGlobals.env(self.__plugin_namespace__).clear_cache()
818
819    def enable(self):
820        """Enable this plugin."""
821        self._enable = True
822        #
823        # Clear the cache for environments that use this plugin
824        #
825        for interface in self.__interfaces__:
826            for ns in self.__interfaces__[interface]:
827                PluginGlobals.env(ns).clear_cache()
828        PluginGlobals.env(self.__plugin_namespace__).clear_cache()
829
830    def enabled(self):
831        """Can be overriden to control whether a plugin is enabled."""
832        return self._enable
833
834implements = Plugin.implements
835
836
837class SingletonPlugin(Plugin):
838    """The base class for singleton plugins.  The PluginMeta class
839    instantiates a SingletonPlugin class when it is declared.  Note that
840    only one instance of a SingletonPlugin class is created in
841    any environment. 
842    """
843    pass
844
845
846def PluginFactory(classname, **kwds):
847    """Construct a Plugin instance, and optionally assign it a name"""
848    try:
849        cls = PluginGlobals.plugin_registry[classname]
850    except KeyError:
851        raise PluginError, "Unknown class %r" % str(classname)
852    obj = cls(**kwds)
853    if 'name' in kwds:
854        obj.name = kwds['name']
855    PluginGlobals.env().log.debug("Creating plugin %s with name %s" % (classname, obj.name))
856    return obj
857     
858
859#
860# Copyright (C) 2003-2008 Edgewall Software
861# Copyright (C) 2003-2004 Jonas Borgstram <jonas@edgewall.com>
862# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
863# All rights reserved.
864#
865# This software is licensed as described in the file COPYING, which
866# you should have received as part of this distribution. The terms
867# are also available at http://trac.edgewall.org/wiki/TracLicense.
868#
869# This software consists of voluntary contributions made by many
870# individuals. For the exact contribution history, see the revision
871# history and logs, available at http://trac.edgewall.org/log/.
872#
873# Author: Jonas Borgstram <jonas@edgewall.com>
874#         Christopher Lenz <cmlenz@gmx.de>
875
Note: See TracBrowser for help on using the repository browser.