Wednesday, April 15, 2009

Trac component registration and management

Trac is a highly flexible project management system written in Python and based around a component architecture. In fact, a large portion of the base Trac system is indeed a set of components. Example components from this set would include the Trac ticketing system or the Trac wiki formatting engine. Using a component based architecture is a smart design decision in the majority software solutions for more reasons than one. Perhaps the most compelling reason to implement a component based architecture is the replaceability that components provide. Components both require and provide interfaces which means that these components can easily be swapped for a different component that provides the same interfaces as the original component. At the very core of Trac are a small set of classes that define how components in Trac work. Like any well designed software core, it is small and unlikely to change drastically in the future. The other benefit of the core being small is the fact that this core is depended upon by all Trac components in any given Trac installation. This core is not only required for interface purposes, but also for component registration and management. This way Trac always knows during its' lifetime what components are available to it. The core set of classes for dealing with components in Trac are ComponentMeta, ComponentManager, and Component.

The most important class here for Trac component developers is the Component class. This class is the external interface Trac provides to the outside world. The Component class is intended to be generalized or extended by each component within the Trac plugin. The ComponentMeta class is used to register defined components within a Trac environment by performing meta operations. That is, by transforming the original Component class as necessary. The ComponentManager class acts as a storage pool for all components in a Trac environment. Any time the Trac system needs access to any given component, it is retrieved through this class. This provides a centralized place for all components to live. Although the Component class is all the developers need concern themselves with, since the behavior of the other two classes is encapsulated, it is nonetheless useful to have a general idea of why they exist.

The Component class states that ComponentMeta class is its' meta class. Given this declaration in Python, when Component gets instantiated, the result returned from ComponentMeta.__new__() is what the instance will ultimately be. This is a useful feature of the language because it allows the behavior of the original class to be modified based on the context. The ComponentMeta.__new__() method has all the contextual data provided to it as parameters, including the original class, the name, base classes, and constructor parameters. The ComponentMeta class not only registers the various interfaces provided by the component in question, but will also redefine the Component constructor while still preserving the functionality of the original constructor. It does this by defining a nested maybe_init() function inside the ComponentMeta.__new__() method. The nested maybe_init() function will become the new Component constructor. The reason redefining the original constructor is so that a ComponentManager instance may now be passed to the Component constructor. This ComponentManager instance will then store the component. What really makes this useful is that if the original constructor existed within the Component in question, it is still invoked by the new maybe_init() constructor.

The ComponentManager is where Trac components are stored once loaded. As mentioned above, the ComponentMeta class dynamically injects functionality into the Component instantiation process that will store itself in a ComponentManager instance. Components stored in the ComponentManager instance be be retrieved by simple attribute access, using the component name as the attribute. This is implemented by the ComponentManager using the __get__() method. If a component is requested by this method that is not currently enabled, the ComponentManager will enable it before returning it. Otherwise it will simply return it. Developers also have an opportunity with Trac to subclass the ComponentManager and override the empty methods it invokes when enabling components. This could potentially be useful if enabling a component is a meaningful event.