Edgewall Software

Changes between Version 32 and Version 33 of TracDev/ComponentArchitecture


Ignore:
Timestamp:
Dec 15, 2015, 7:40:18 PM (8 years ago)
Author:
figaro
Comment:

Further cosmetic changes, removed link to deprecated page

Legend:

Unmodified
Added
Removed
Modified
  • TracDev/ComponentArchitecture

    v32 v33  
     1[[PageOutline(2-5,Contents,pullout)]]
     2
    13= Trac Component Architecture
    24
    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'''.
     5As 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'''.
    46
    57== What is a Component?
     
    1719== Public classes
    1820
    19 `trac.core.ComponentManager` [[BR]]
    20   Manages component life cycle, instantiating registered components on demand.
    21 
    22 `trac.core.Component` [[BR]]
    23   Abstract base class for components.
    24 
    25 `trac.core.ExtensionPoint` [[BR]]
    26   Declares an extension point on a component that other components can plug in to.
    27 
    28 `trac.core.Interface` [[BR]]
    29   Every extension point specifies the contract that extenders must conform to via an `Interface` subclass.
     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.
    3032
    3133[[Image(comparch.png)]]
     
    3537The simplest possible component is an empty class derived from `trac.core.Component`:
    3638
    37 {{{
    38 #!python
     39{{{#!python
    3940from trac.core import *
    4041
     
    4546In the context of a component manager, this component can already be used:
    4647
    47 {{{
    48 #!python
     48{{{#!python
    4949    comp_mgr = ComponentManager()
    5050    my_comp = MyComponent(comp_mgr)
     
    5353Remember 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:
    5454
    55 {{{
    56 #!python
     55{{{#!python
    5756    my_comp1 = MyComponent(comp_mgr)
    5857    my_comp2 = MyComponent(comp_mgr)
     
    6261If 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:
    6362
    64 {{{
    65 #!python
     63{{{#!python
    6664    from trac.core import *
    6765
     
    7472}}}
    7573
    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.
     74Direct `Component` sub-classes also do not need to worry about invoking the base class', ie `Component`'s, `__init__` method, as it is empty.
    7775
    7876'''Note''': You can't pass data to the constructor of a component.
     
    8078=== Components instantiating other Components
    8179
    82 If one `Component` instantiates another, it typically will use the same `ComponentManager`, instead of creating a new `ComponentManager`.
    83 
    84 {{{
    85 #!python
     80If one `Component` instantiates another, it typically will use the same `ComponentManager`, instead of creating a new `ComponentManager`:
     81
     82{{{#!python
    8683    class MyComponent(Component):
    8784        def callOtherComponent(self):
     
    9794The 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:
    9895
    99 {{{
    100 #!python
     96{{{#!python
    10197    from trac.core import *
    10298   
     
    132128Now that we have an extendable component, let's add another component that extends it:
    133129
    134 {{{
    135 #!python
     130{{{#!python
    136131    class TodoPrinter(Component):
    137132        implements(ITodoObserver)
     
    152147Now 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:
    153148
    154 {{{
    155 #!python
     149{{{#!python
    156150    comp_mgr = ComponentManager()
    157151    todo_list = TodoList(comp_mgr)
     
    172166}}}
    173167
    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.
     168This 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.
    175169
    176170== Component Lifecycle and State == #component_lifecycle
     
    203197  ''Extension points will '''only''' use enabled components.''
    204198
    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).
     199This 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.
    206200
    207201Enabling 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.
     
    209203'''Miscellaneous notes:'''
    210204* 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
    213206  class MyAbstractComponent(Component):
    214207      abstract = True
     
    222215
    223216The 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
    226218permission_system = trac.perm.PermissionSystem(env)
    227219actions = permission_system.get_permission_actions()
     
    230222  ''Note that `trac.env.Environment` inherits `trac.core.ComponentManager`, so you'll typically see components initialized with an environment.''
    231223
    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
     224These are the first few lines of `PermissionSystem` as of r5790 ([browser:trunk/trac/perm.py@5790#L230 in context]):
     225{{{#!python
    235226class PermissionSystem(Component):
    236227    """Sub-system that manages user permissions."""
     
    244235 1. implements the `IPermissionRequestor` interface
    245236 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
    248238class IPermissionRequestor(Interface):
    249239    """Extension point interface for components that define actions."""
     
    253243}}}
    254244
    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.''
    256246
    257247When we use `PermissionSystem`, the plugin system will have automatically gathered up all implementations of `IPermissionRequestor` and placed them in `PermissionSystem`'s list of `requestors`.
     
    261251
    262252Next in `PermissionSystem` there is a declaration of an `ExtensionOption` called `store`:
    263 {{{
    264 #!python
     253{{{#!python
    265254    store = ExtensionOption('trac', 'permission_store', IPermissionStore,
    266255                            'DefaultPermissionStore',
     
    272261The 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:
    273262
    274 {{{
    275 #!python
     263{{{#!python
    276264    def get_all_permissions(self):
    277265        """Return all permissions for all users.
     
    285273
    286274----
    287 See also: TracDev, TracPluggableModules
     275See also: TracDev