edgewall/trac

View on GitHub
doc/api/trac_core.rst

Summary

Maintainability
Test Coverage
:mod:`trac.core` -- the Trac "kernel"
=====================================

Component model
---------------

.. module :: trac.core

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.

.. autoclass :: trac.core.Interface
   :members:

.. autoclass :: trac.core.Component
   :members:

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.

.. autofunction :: trac.core.implements

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 `~ComponentManager.is_component_enabled`
below.

.. autoclass :: trac.core.ExtensionPoint
   :members:

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

.. autoclass :: trac.core.ComponentManager
   :members:

In practice, there's only one kind of `ComponentManager` in the Trac
application itself, the `trac.env.Environment`.


More on components
------------------

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.


Exceptions
----------

.. autoexception :: TracBaseError
   :members:

.. autoexception :: TracError
   :members:


Miscellaneous
-------------

.. autoclass :: ComponentMeta
   :members:

.. autofunction :: N_


.. [*] Ok, it *might* happen that more than one component instance get
   created due to a race condition. This is usually harmless, see
   :teo:`#9418`.