The Trac component model is very simple, it is based on Interface classes that are used to document a particular set of methods and properties that must be defined by a Component subclass when it declares it implements that interface.
Marker base class for extension point interfaces.
Base class for components.
Every component can declare what extension points it provides, as well as what extension points of other components it extends.
The static method Component.implements is never used as such, but rather via the global implements function. This globally registers that particular component subclass as an implementation of the listed interfaces.
Can be used in the class definition of Component subclasses to declare the extension points that are extended.
For example:
class IStuffProvider(Interface):
"""All interfaces start by convention with an "I" and even if it's
not a convention, in practice most interfaces are "Provider" of
something ;-)
"""
def get_stuff(color=None):
"""We usually don't specify "self" here, but try to describe
as precisely as possible how the method might be called and
what is the expected return type."""
class ComponentA(Component):
implements(IStuffProvider)
# IStuffProvider methods
def get_stuff(self, color=None):
if not color or color == 'yellow':
yield ('duck', "the regular waterproof plastic duck")
The benefit of implementing an interface is to possibility to define an ExtensionPoint property for an Interface, in a Component subclass. Such a property provides a convenient way to retrieve all registered and enabled component instances for that interface. The enabling of components is the responsibility of the ComponentManager, see is_component_enabled below.
Marker class for extension points in components.
Create the extension point.
Parameters: | interface – the Interface subclass that defines the protocol for the extension point |
---|
Return a list of components that declare to implement the extension point interface.
Continuing the example:
class StuffModule(Component):
stuff_providers = ExtensionPoint(IStuffProvider)
def get_all_stuff(self, color=None):
stuff = {}
for provider in self.stuff_provider:
for name, descr in provider.get_stuff(color) or []:
stuff[name] = descr
return stuff
Note that besides going through an extension point, Component subclass instances can alternatively be retrieved directly by using the instantiation syntax. This is not an usual instantiation though, as this will always return the same instance in the given ComponentManager “scope” passed to the constructor:
>>> a1 = ComponentA(mgr)
>>> a2 = ComponentA(mgr)
>>> a1 is a2
True
The same thing happens when retrieving components via an extension point, the retrieved instances belong to the same “scope” as the instance used to access the extension point:
>>> b = StuffModule(mgr)
>>> any(a is a1 for a in b.stuff_providers)
True
The component manager keeps a pool of active components.
Initialize the component manager.
Can be overridden by sub-classes so that special initialization for components can be provided.
Force a component to be disabled.
Parameters: | component – can be a class or an instance. |
---|
Can be overridden by sub-classes to veto the activation of a component.
If this method returns False, the component was disabled explicitly. If it returns None, the component was neither enabled nor disabled explicitly. In both cases, the component with the given class will not be available.
Return whether the given component class is enabled.
In practice, there’s only one kind of ComponentManager in the Trac application itself, the trac.env.Environment.
We have seen above that one way to retrieve a Component instance is to call the constructor on a ComponentManager instance mgr:
a1 = ComponentA(mgr)
This will eventually trigger the creation of a new ComponentA instance if there wasn’t already one created for mgr [*]. At this unique occasion, the constructor of the component subclass will be called without arguments, so if you define a constructor it must have the following signature:
def __init__(self):
self.all_colors = set()
Note that one should try to do as little as possible in a Component constructor. The most complex operation could be for example the allocation of a lock to control the concurrent access to some data members and guarantee thread-safe initialization of more costly resources on first use. Never do such costly initializations in the constructor itself.
Exception base class for errors in Trac.
If message is a genshi.builder.tag object, everything up to the first <p> will be displayed in the red box, and everything after will be displayed below the red box. If title is given, it will be displayed as the large header above the error message.
[*] | Ok, it might happen that more than one component instance get created due to a race condition. This is usually harmless, see #9418. |