Edgewall Software

root/trunk/trac/core.py

Revision 8334, 8.4 KB (checked in by osimons, 6 days ago)

0.12dev: Merged [8333] from 0.11-stable (#8160 - Setting BaseException.message is deprecated in Python 2.6).

  • Property svn:eol-style set to native
Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2003-2009 Edgewall Software
4# Copyright (C) 2003-2004 Jonas Borgström <jonas@edgewall.com>
5# Copyright (C) 2004-2005 Christopher Lenz <cmlenz@gmx.de>
6# All rights reserved.
7#
8# This software is licensed as described in the file COPYING, which
9# you should have received as part of this distribution. The terms
10# are also available at http://trac.edgewall.org/wiki/TracLicense.
11#
12# This software consists of voluntary contributions made by many
13# individuals. For the exact contribution history, see the revision
14# history and logs, available at http://trac.edgewall.org/log/.
15#
16# Author: Jonas Borgström <jonas@edgewall.com>
17#         Christopher Lenz <cmlenz@gmx.de>
18
19__all__ = ['Component', 'ExtensionPoint', 'implements', 'Interface',
20           'TracError']
21
22
23class TracError(Exception):
24    """Exception base class for errors in Trac."""
25
26    title = 'Trac Error'
27   
28    def __init__(self, message, title=None, show_traceback=False):
29        """If message is a genshi.builder.tag object, everything up to the
30        first <p> will be displayed in the red box, and everything after will
31        be displayed below the red box.
32        If title is given, it will be displayed as the large header above the
33        error message.
34        """
35        Exception.__init__(self, message)
36        self._message = message
37        if title:
38            self.title = title
39        self.show_traceback = show_traceback
40
41    message = property(lambda self: self._message, 
42                       lambda self, v: setattr(self, '_message', v))
43
44    def __unicode__(self):
45        return unicode(self.message)
46
47class Interface(object):
48    """Marker base class for extension point interfaces."""
49
50
51class ExtensionPoint(property):
52    """Marker class for extension points in components."""
53
54    def __init__(self, interface):
55        """Create the extension point.
56       
57        @param interface: the `Interface` subclass that defines the protocol
58            for the extension point
59        """
60        property.__init__(self, self.extensions)
61        self.interface = interface
62        self.__doc__ = 'List of components that implement `%s`' % \
63                       self.interface.__name__
64
65    def extensions(self, component):
66        """Return a list of components that declare to implement the extension
67        point interface.
68        """
69        extensions = ComponentMeta._registry.get(self.interface, [])
70        return filter(None, [component.compmgr[cls] for cls in extensions])
71
72    def __repr__(self):
73        """Return a textual representation of the extension point."""
74        return '<ExtensionPoint %s>' % self.interface.__name__
75
76
77class ComponentMeta(type):
78    """Meta class for components.
79   
80    Takes care of component and extension point registration.
81    """
82    _components = []
83    _registry = {}
84
85    def __new__(cls, name, bases, d):
86        """Create the component class."""
87
88        new_class = type.__new__(cls, name, bases, d)
89        if name == 'Component':
90            # Don't put the Component base class in the registry
91            return new_class
92
93        # Only override __init__ for Components not inheriting ComponentManager
94        if True not in [issubclass(x, ComponentManager) for x in bases]:
95            # Allow components to have a no-argument initializer so that
96            # they don't need to worry about accepting the component manager
97            # as argument and invoking the super-class initializer
98            init = d.get('__init__')
99            if not init:
100                # Because we're replacing the initializer, we need to make sure
101                # that any inherited initializers are also called.
102                for init in [b.__init__._original for b in new_class.mro()
103                             if issubclass(b, Component)
104                             and '__init__' in b.__dict__]:
105                    break
106            def maybe_init(self, compmgr, init=init, cls=new_class):
107                if cls not in compmgr.components:
108                    compmgr.components[cls] = self
109                    if init:
110                        try:
111                            init(self)
112                        except:
113                            del compmgr.components[cls]
114                            raise
115            maybe_init._original = init
116            new_class.__init__ = maybe_init
117
118        if d.get('abstract'):
119            # Don't put abstract component classes in the registry
120            return new_class
121
122        ComponentMeta._components.append(new_class)
123        registry = ComponentMeta._registry
124        for interface in d.get('_implements', []):
125            registry.setdefault(interface, []).append(new_class)
126        for base in [base for base in bases if hasattr(base, '_implements')]:
127            for interface in base._implements:
128                registry.setdefault(interface, []).append(new_class)
129
130        return new_class
131
132
133class Component(object):
134    """Base class for components.
135
136    Every component can declare what extension points it provides, as well as
137    what extension points of other components it extends.
138    """
139    __metaclass__ = ComponentMeta
140
141    def __new__(cls, *args, **kwargs):
142        """Return an existing instance of the component if it has already been
143        activated, otherwise create a new instance.
144        """
145        # If this component is also the component manager, just invoke that
146        if issubclass(cls, ComponentManager):
147            self = super(Component, cls).__new__(cls)
148            self.compmgr = self
149            return self
150
151        # The normal case where the component is not also the component manager
152        compmgr = args[0]
153        self = compmgr.components.get(cls)
154        if self is None:
155            self = super(Component, cls).__new__(cls)
156            self.compmgr = compmgr
157            compmgr.component_activated(self)
158        return self
159
160    @staticmethod
161    def implements(*interfaces):
162        """Can be used in the class definiton of `Component` subclasses to
163        declare the extension points that are extended.
164        """
165        import sys
166
167        frame = sys._getframe(1)
168        locals_ = frame.f_locals
169
170        # Some sanity checks
171        assert locals_ is not frame.f_globals and '__module__' in locals_, \
172               'implements() can only be used in a class definition'
173
174        locals_.setdefault('_implements', []).extend(interfaces)
175
176
177implements = Component.implements
178
179
180class ComponentManager(object):
181    """The component manager keeps a pool of active components."""
182
183    def __init__(self):
184        """Initialize the component manager."""
185        self.components = {}
186        self.enabled = {}
187        if isinstance(self, Component):
188            self.components[self.__class__] = self
189
190    def __contains__(self, cls):
191        """Return wether the given class is in the list of active components."""
192        return cls in self.components
193
194    def __getitem__(self, cls):
195        """Activate the component instance for the given class, or return the
196        existing the instance if the component has already been activated.
197        """
198        if cls not in self.enabled:
199            self.enabled[cls] = self.is_component_enabled(cls)
200        if not self.enabled[cls]:
201            return None
202        component = self.components.get(cls)
203        if not component:
204            if cls not in ComponentMeta._components:
205                raise TracError('Component "%s" not registered' % cls.__name__)
206            try:
207                component = cls(self)
208            except TypeError, e:
209                raise TracError('Unable to instantiate component %r (%s)' %
210                                (cls, e))
211        return component
212
213    def disable_component(self, component):
214        """Force a component to be disabled.
215       
216        The argument `component` can be a class or an instance.
217        """
218        if not isinstance(component, type):
219            component = component.__class__
220        self.enabled[component] = False
221        self.components[component] = None
222
223    def component_activated(self, component):
224        """Can be overridden by sub-classes so that special initialization for
225        components can be provided.
226        """
227
228    def is_component_enabled(self, cls):
229        """Can be overridden by sub-classes to veto the activation of a
230        component.
231
232        If this method returns False, the component with the given class will
233        not be available.
234        """
235        return True
Note: See TracBrowser for help on using the browser.