Package trac :: Module core

Source Code for Module trac.core

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2003-2020 Edgewall Software 
  4  # Copyright (C) 2003-2004 Jonas Borgström <[email protected]> 
  5  # Copyright (C) 2004-2005 Christopher Lenz <[email protected]> 
  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 https://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 https://trac.edgewall.org/log/. 
 15  # 
 16  # Author: Jonas Borgström <[email protected]> 
 17  #         Christopher Lenz <[email protected]> 
 18   
 19  import sys 
 20   
 21  __all__ = ['Component', 'ExtensionPoint', 'implements', 'Interface', 
 22             'TracBaseError', 'TracError', 'TracValueError'] 
23 24 25 -def N_(string):
26 """No-op translation marker, inlined here to avoid importing from 27 `trac.util`. 28 """ 29 return string
30
31 32 -class TracBaseError(Exception):
33 """Base class for all exceptions defined in Trac.""" 34 35 title = N_("Trac Error")
36
37 38 -class TracError(TracBaseError):
39 """Standard exception for errors in Trac.""" 40
41 - def __init__(self, message, title=None, show_traceback=False):
42 """If the `message` contains a `p` or `div` element it will be 43 rendered directly. Use the `message` class on the `p` or `div` 44 element to style as a red box. Otherwise, the message should be 45 plain text or contain only inline elements and will be wrapped 46 in a `p` element and rendered in a red box. 47 48 If title is given, it will be displayed as the large header 49 above the error message. 50 """ 51 from trac.util.translation import gettext 52 super(TracError, self).__init__(message) 53 self._message = message 54 self.title = title or gettext(self.title) 55 self.show_traceback = show_traceback
56 57 message = property(lambda self: self._message, 58 lambda self, v: setattr(self, '_message', v)) 59
60 - def __unicode__(self):
61 return unicode(self.message)
62
63 64 -class TracValueError(TracError, ValueError):
65 """Raised when a function or operator receives an argument that is 66 the correct type, but inappropriate value. 67 68 :since: 1.2.1 69 """
70
71 72 -class Interface(object):
73 """Marker base class for extension point interfaces."""
74
75 76 -class ExtensionPoint(property):
77 """Marker class for extension points in components.""" 78
79 - def __init__(self, interface):
80 """Create the extension point. 81 82 :param interface: the `Interface` subclass that defines the 83 protocol for the extension point 84 """ 85 property.__init__(self, self.extensions) 86 self.interface = interface 87 self.__doc__ = ("List of components that implement `~%s.%s`" % 88 (self.interface.__module__, self.interface.__name__))
89
90 - def extensions(self, component):
91 """Return a list of components that declare to implement the 92 extension point interface. 93 """ 94 classes = ComponentMeta._registry.get(self.interface, ()) 95 components = [component.compmgr[cls] for cls in classes] 96 return [c for c in components if c]
97
98 - def __repr__(self):
99 """Return a textual representation of the extension point.""" 100 return '<ExtensionPoint %s>' % self.interface.__name__
101
102 103 -class ComponentMeta(type):
104 """Meta class for components. 105 106 Takes care of component and extension point registration. 107 """ 108 _components = [] 109 _registry = {} 110
111 - def __new__(mcs, name, bases, d):
112 """Create the component class.""" 113 114 new_class = type.__new__(mcs, name, bases, d) 115 if name == 'Component': 116 # Don't put the Component base class in the registry 117 return new_class 118 119 if d.get('abstract'): 120 # Don't put abstract component classes in the registry 121 return new_class 122 123 ComponentMeta._components.append(new_class) 124 registry = ComponentMeta._registry 125 for cls in new_class.__mro__: 126 for interface in cls.__dict__.get('_implements', ()): 127 classes = registry.setdefault(interface, []) 128 if new_class not in classes: 129 classes.append(new_class) 130 131 return new_class
132
133 - def __call__(cls, *args, **kwargs):
134 """Return an existing instance of the component if it has 135 already been activated, otherwise create a new instance. 136 """ 137 # If this component is also the component manager, just invoke that 138 if issubclass(cls, ComponentManager): 139 self = cls.__new__(cls) 140 self.compmgr = self 141 self.__init__(*args, **kwargs) 142 return self 143 144 # The normal case where the component is not also the component manager 145 assert len(args) >= 1 and isinstance(args[0], ComponentManager), \ 146 "First argument must be a ComponentManager instance" 147 compmgr = args[0] 148 self = compmgr.components.get(cls) 149 # Note that this check is racy, we intentionally don't use a 150 # lock in order to keep things simple and avoid the risk of 151 # deadlocks, as the impact of having temporarily two (or more) 152 # instances for a given `cls` is negligible. 153 if self is None: 154 self = cls.__new__(cls) 155 self.compmgr = compmgr 156 compmgr.component_activated(self) 157 self.__init__() 158 # Only register the instance once it is fully initialized (#9418) 159 compmgr.components[cls] = self 160 return self
161 162 @classmethod
163 - def deregister(cls, component):
164 """Remove a component from the registry.""" 165 try: 166 cls._components.remove(component) 167 except ValueError: 168 pass 169 for class_ in component.__mro__: 170 for interface in class_.__dict__.get('_implements', ()): 171 implementers = cls._registry.get(interface) 172 try: 173 implementers.remove(component) 174 except ValueError: 175 pass
176
177 178 -class Component(object):
179 """Base class for components. 180 181 Every component can declare what extension points it provides, as 182 well as what extension points of other components it extends. 183 """ 184 __metaclass__ = ComponentMeta 185 186 @staticmethod
187 - def implements(*interfaces):
188 """Can be used in the class definition of `Component` 189 subclasses to declare the extension points that are extended. 190 """ 191 frame = sys._getframe(1) 192 locals_ = frame.f_locals 193 194 # Some sanity checks 195 assert locals_ is not frame.f_globals and '__module__' in locals_, \ 196 'implements() can only be used in a class definition' 197 198 locals_.setdefault('_implements', []).extend(interfaces)
199
200 - def __repr__(self):
201 """Return a textual representation of the component.""" 202 return '<Component %s.%s>' % (self.__class__.__module__, 203 self.__class__.__name__)
204 205 implements = Component.implements
206 207 208 -class ComponentManager(object):
209 """The component manager keeps a pool of active components.""" 210
211 - def __init__(self):
212 """Initialize the component manager.""" 213 self.components = {} 214 self.enabled = {} 215 if isinstance(self, Component): 216 self.components[self.__class__] = self
217
218 - def __contains__(self, cls):
219 """Return whether the given class is in the list of active 220 components.""" 221 return cls in self.components
222
223 - def __getitem__(self, cls):
224 """Activate the component instance for the given class, or 225 return the existing instance if the component has already been 226 activated. 227 228 Note that `ComponentManager` components can't be activated 229 that way. 230 """ 231 if not self.is_enabled(cls): 232 return None 233 component = self.components.get(cls) 234 if not component and not issubclass(cls, ComponentManager): 235 if cls not in ComponentMeta._components: 236 raise TracError('Component "%s" not registered' % cls.__name__) 237 try: 238 component = cls(self) 239 except TypeError as e: 240 raise TracError("Unable to instantiate component %r (%s)" % 241 (cls, e)) 242 return component
243
244 - def is_enabled(self, cls):
245 """Return whether the given component class is enabled.""" 246 if cls not in self.enabled: 247 self.enabled[cls] = self.is_component_enabled(cls) 248 return self.enabled[cls]
249
250 - def disable_component(self, component):
251 """Force a component to be disabled. 252 253 :param component: can be a class or an instance. 254 """ 255 if not isinstance(component, type): 256 component = component.__class__ 257 self.enabled[component] = False 258 self.components[component] = None
259
260 - def enable_component(self, component):
261 """Force a component to be enabled. 262 263 :param component: can be a class or an instance. 264 265 :since: 1.0.13 266 """ 267 if not isinstance(component, type): 268 component = component.__class__ 269 self.enabled[component] = True
270
271 - def component_activated(self, component):
272 """Can be overridden by sub-classes so that special 273 initialization for components can be provided. 274 """
275
276 - def is_component_enabled(self, cls):
277 """Can be overridden by sub-classes to veto the activation of 278 a component. 279 280 If this method returns `False`, the component was disabled 281 explicitly. If it returns `None`, the component was neither 282 enabled nor disabled explicitly. In both cases, the component 283 with the given class will not be available. 284 """ 285 return True
286