Changes between Version 32 and Version 33 of TracDev/ComponentArchitecture
- Timestamp:
- Dec 15, 2015, 7:40:18 PM (8 years ago)
Legend:
- Unmodified
- Added
- Removed
- Modified
-
TracDev/ComponentArchitecture
v32 v33 1 [[PageOutline(2-5,Contents,pullout)]] 2 1 3 = Trac Component Architecture 2 4 3 As the heart of Trac, [source:/trunk/trac/core.py#latest trac.core] implements a minimal '''component kernel''' that allows components to easily extend each others' functionality. It provides a meta-plugin-API: every component can easily offer its own plugin API by declaring '''extension points'''.5 As the heart of Trac, the [source:/trunk/trac/core.py#latest trac.core] implements a minimal '''component kernel''' that allows components to easily extend each others' functionality. It provides a meta-plugin-API: every component can easily offer its own plugin API by declaring '''extension points'''. 4 6 5 7 == What is a Component? … … 17 19 == Public classes 18 20 19 `trac.core.ComponentManager` [[BR]]20 21 22 `trac.core.Component` [[BR]]23 24 25 `trac.core.ExtensionPoint` [[BR]]26 27 28 `trac.core.Interface` [[BR]]29 21 `trac.core.ComponentManager`: 22 Manages component life cycle, instantiating registered components on demand. 23 24 `trac.core.Component`: 25 Abstract base class for components. 26 27 `trac.core.ExtensionPoint`: 28 Declares an extension point on a component that other components can plug in to. 29 30 `trac.core.Interface`: 31 Every extension point specifies the contract that extenders must conform to via an `Interface` subclass. 30 32 31 33 [[Image(comparch.png)]] … … 35 37 The simplest possible component is an empty class derived from `trac.core.Component`: 36 38 37 {{{ 38 #!python 39 {{{#!python 39 40 from trac.core import * 40 41 … … 45 46 In the context of a component manager, this component can already be used: 46 47 47 {{{ 48 #!python 48 {{{#!python 49 49 comp_mgr = ComponentManager() 50 50 my_comp = MyComponent(comp_mgr) … … 53 53 Remember that components follow the singleton pattern, but component managers do not. There is only one active instance of any component per component manager. The component constructor checks with the component manager whether there is already an active instance before allocating a new instance. If the component was already instantiated, the existing instance is returned: 54 54 55 {{{ 56 #!python 55 {{{#!python 57 56 my_comp1 = MyComponent(comp_mgr) 58 57 my_comp2 = MyComponent(comp_mgr) … … 62 61 If a component needs to initialize data members, it can override the `__init__` method. But because the component manager needs to be able to instantiate the component on demand, `__init__` must '''not''' require any extra parameters, including the reference to the component manager passed into the constructor: 63 62 64 {{{ 65 #!python 63 {{{#!python 66 64 from trac.core import * 67 65 … … 74 72 }}} 75 73 76 Direct `Component` sub-classes also do not need to worry about invoking the base class' (i.e. `Component`'s)`__init__` method, as it is empty.74 Direct `Component` sub-classes also do not need to worry about invoking the base class', ie `Component`'s, `__init__` method, as it is empty. 77 75 78 76 '''Note''': You can't pass data to the constructor of a component. … … 80 78 === Components instantiating other Components 81 79 82 If one `Component` instantiates another, it typically will use the same `ComponentManager`, instead of creating a new `ComponentManager`. 83 84 {{{ 85 #!python 80 If one `Component` instantiates another, it typically will use the same `ComponentManager`, instead of creating a new `ComponentManager`: 81 82 {{{#!python 86 83 class MyComponent(Component): 87 84 def callOtherComponent(self): … … 97 94 The real value of components becomes clearer when the facilities for extensions are used. As a simple example, the following component provides an extension point that lets other components listen to changes to the data it manages (in this case a list of to-do items), following the widely known observable pattern: 98 95 99 {{{ 100 #!python 96 {{{#!python 101 97 from trac.core import * 102 98 … … 132 128 Now that we have an extendable component, let's add another component that extends it: 133 129 134 {{{ 135 #!python 130 {{{#!python 136 131 class TodoPrinter(Component): 137 132 implements(ITodoObserver) … … 152 147 Now that we've declared both a component exposing an extension point, and another component extending that extension point, let's use the to-do list example to see what happens: 153 148 154 {{{ 155 #!python 149 {{{#!python 156 150 comp_mgr = ComponentManager() 157 151 todo_list = TodoList(comp_mgr) … … 172 166 }}} 173 167 174 This output obviously comes from the `TodoPrinter`. Note however that the code snippet above doesn't even mention that class. All that is needed to have it participating in the action is to declare the class. That implies that an extending class needs to be imported by a python script to be registered. The aspect of loading components is however separate from the extension mechanism itself.168 This output obviously comes from the `TodoPrinter`. Note however that the code snippet above doesn't even mention that class. All that is needed to have it participating in the action is to declare the class. That implies that an extending class needs to be imported by a Python script to be registered. The aspect of loading components is however separate from the extension mechanism itself. 175 169 176 170 == Component Lifecycle and State == #component_lifecycle … … 203 197 ''Extension points will '''only''' use enabled components.'' 204 198 205 This means that the extension point methods (like `todo_added` in the example above) of a ''disabled'' component (that implements a certain extension point interface) ''won't be called''. Note also that even disabled components can be activated (instantiated), but only by constructing them manually (as mentioned before).199 This means that the extension point methods (like `todo_added` in the example above) of a ''disabled'' component (that implements a certain extension point interface) ''won't be called''. Note also that even disabled components can be activated (instantiated), but only by constructing them manually, as mentioned before. 206 200 207 201 Enabling a component is done in the `[components]` section of [wiki:TracIni trac.ini]. This is implemented in `trac.env.Environment.is_component_enabled()`. Whether a component is enabled or disabled is checked ''only once'' when an extension point that component implements is first used. … … 209 203 '''Miscellaneous notes:''' 210 204 * Components can be marked "abstract". This is done simply by adding a member field `abstract = True` to the component class. 211 {{{ 212 #!python 205 {{{#!python 213 206 class MyAbstractComponent(Component): 214 207 abstract = True … … 222 215 223 216 The typical use of components in Trac starts with a top-level '''service provider''' that we'll pick up and use directly. Take, for example, `trac.perm.PermissionSystem`: 224 {{{ 225 #!python 217 {{{#!python 226 218 permission_system = trac.perm.PermissionSystem(env) 227 219 actions = permission_system.get_permission_actions() … … 230 222 ''Note that `trac.env.Environment` inherits `trac.core.ComponentManager`, so you'll typically see components initialized with an environment.'' 231 223 232 These are the first few lines of `PermissionSystem` as of r5790 (or [browser:trunk/trac/perm.py@5790#L230 in context]): 233 {{{ 234 #!python 224 These are the first few lines of `PermissionSystem` as of r5790 ([browser:trunk/trac/perm.py@5790#L230 in context]): 225 {{{#!python 235 226 class PermissionSystem(Component): 236 227 """Sub-system that manages user permissions.""" … … 244 235 1. implements the `IPermissionRequestor` interface 245 236 1. has an extension point for registering all the Components implementing `IPermissionRequestor` ([browser:trunk/trac/perm.py@5790#L40 in context]): 246 {{{ 247 #!python 237 {{{#!python 248 238 class IPermissionRequestor(Interface): 249 239 """Extension point interface for components that define actions.""" … … 253 243 }}} 254 244 255 ''Note that interface authors have not always been consistent about declaring the “`self`”parameter in signatures.''245 ''Note that interface authors have not always been consistent about declaring the `self` parameter in signatures.'' 256 246 257 247 When we use `PermissionSystem`, the plugin system will have automatically gathered up all implementations of `IPermissionRequestor` and placed them in `PermissionSystem`'s list of `requestors`. … … 261 251 262 252 Next in `PermissionSystem` there is a declaration of an `ExtensionOption` called `store`: 263 {{{ 264 #!python 253 {{{#!python 265 254 store = ExtensionOption('trac', 'permission_store', IPermissionStore, 266 255 'DefaultPermissionStore', … … 272 261 The above adds an option called `permission_store` to `trac.ini`, declares that the component named by the option implements `IPermissionStore`, and sets its default to `DefaultPermissionStore`. See [browser:trunk/trac/config.py trac.config] for `ExtensionOption` and friends. Methods of service providers such as `PermissionSystem` are commonly a thin forwarding layer over such an `ExtensionOption`. For example: 273 262 274 {{{ 275 #!python 263 {{{#!python 276 264 def get_all_permissions(self): 277 265 """Return all permissions for all users. … … 285 273 286 274 ---- 287 See also: TracDev , TracPluggableModules275 See also: TracDev