Warning: リポジトリと同期できません (サポートされていないバージョンコントロールシステム "svn です。 Python のライブラリに "svn" が正しくインストールされているか確認してください。)

Trac Component Architecture

原文

Error: Failed to load processor VisitCounter
No macro or processor named 'VisitCounter' found

訳注:
 この章には、コンポーネントのメカニズムが書いてあります。ただし、ちょっと難しいし、プラグ
インを作る際に絶対必要というわけではありません。わからないところは飛ばして読んで、気になっ
たらまたこのページを読んでください(訳も自信ありませんが)。
この章のポイントは以下です。

 * Tracのコンポーネントは、データのモデルに対応するよう作るものではなく、機能を提供する部品
という観点で作るべきであること。
   なお、コンポーネントは全てのアクセスに対してオブジェクトをひとつしか持たない(シングルト
ンである)。
 * コンポーネントの初期化は__init__メソッドで行うこと。ただし、__init__メソッドは引数をもて
ないこと。
  * Tracのインタフェースは、(Javaのインタフェースと異なり)継承に類する概念とはちょっと違
う。
    インタフェースを実装さえすれば、Tracが必要なときに自分の作ったクラスを探し出して呼び出
してくれる。(Javaのように、呼び出す側を変更する必要は無い)

As the heart of Trac, 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".
Tracの心臓部であるtrac.coreは、最小限のコンポーネントカーネルを実装します。trac.codeは、各コンポーネントが簡単にそれぞれの機能を継承するための「メタプラグインAPI」を提供します。メタプラグインAPIにより、あらゆるコンポーネントは「エクステンションポイント」(extension point)を宣言することで、容易にプラグインAPIを他に提供することが出来ます。 (訳注;実際にTracでは、プラグインというべきクラスは全てcomponentクラスを継承している。)

What is a Component?

For our purposes, a component is an object that provides a certain type of service within the context of the application. There is at most one instance of any component: components are singletons. That implies that a component does not map to an entity of the application's object model; instead, components represent functional subsystems.
コンポーネントは、私たちの目的に合うサービスをアプリケーション中で提供するオブジェクトです。どのコンポーネントでも1つのインスタンスしか存在しません(コンポーネントはシングルトンです)。それは、コンポーネントがアプリケーションのオブジェクトモデルのエンティティ(実体)とは対応しないことを意味します。代わりに、コンポーネントは機能的なサブシステムを表します。

Components can declare "extension points" that other components can “plug in” to. This allows one component to enhance the functionality of the component it extends, without the extended component even knowing that the extending component exists. All that is needed is that the original component exposes -- and uses -- one or more extension points.
コンポーネントは、他のコンポーネントが「プラグイン」可能なように「エクステンションポイント」を宣言できます。これで、継承しているコンポーネントが他に存在しても、それを継承することなく、あるコンポーネントの機能性を高めることが可能になっています。 必要であるのは元のコンポーネントが1つ以上の「エクステンションポイント」を宣言して(そして自分がそれを使って)いることだけです。

訳注:要は、エクステンションポイントを宣言したコンポーネントは、後から「プラグイン」してくるコンポーネントの名前を定義する必要がないということ。逆にJavaとかでは、機能を追加するためにクラスを後から作ったら、そのクラスを呼び出すクラスに書き加える必要がある。

A component can extend any number of other components and still offer its own extension points. This allows a plugin to itself offer a plugin API (i.e. extension point). This feature is the basis for a plugin-based architecture.
コンポーネントは、他のいろいろなコンポーネントを拡張することができます。さらに自分自身のエクステンションポイントを提供することができます。これはプラグイン自身が「エクステンションポイント」のような「プラグインAPI」を提供することを可能にします。この機能はプラグインベースのアーキテクチャの基礎となっています。 (訳注:要は自分が作ったプラグインも、Trac本体のようにインタフェースを他に提供することもできる、と)

The actual functionality and APIs are defined by individual components. The component kernel provides the “magic glue” to hook the different subsystems together -- without them necessarily knowing about each other.
実際の機能性とAPIは個々のコンポーネントによって定義されます。 コンポーネントカーネルは、異なる複数のサブシステム(しかもお互いを知らない)をフックするための「魔法の接着剤」を提供します。

Public classes

trac.core.!ComponentManager::

Manages component life cycle, instantiating registered components on demand
コンポーネントのライフサイクルを管理します。(登録されたコンポーネントをオンデマンドにインスタンス化します)

trac.core.Component::

Abstract base class for components.
コンポーネントのAbstract(抽象)基底クラス。

trac.core.!ExtensionPoint::

Declares an extension point on a component that other components can plug in to.
他のコンポーネントがプラグインできる「エクステンションポイント」を、コンポーネント上で宣言します。

trac.core.Interface::

Every extension point specifies the contract that extenders must conform to via an Interface subclass.
すべての「エクステンションポイント」は、拡張する側が従わなくてはならない規約を指定します。Interfaceサブクラスを用いて。

Declaring a component

The simplest possible component is an empty class derived from trac.core.Component:
もっともシンプルなコンポーネントは、trac.core.Componentから継承した空クラスです:

from trac.core import *

class MyComponent(Component):
    pass

In the context of a component manager, this component can already be used:
コンポーネントマネージャのコンテキストでは、このコンポーネントは既に使用可能です。(?)

    comp_mgr = ComponentManager()
    my_comp = MyComponent(comp_mgr)

Remember that components follow the singleton pattern. There is only one active instance of any component per component manager. The component constructor is "magic" in that it checks with the component manager whether there's already an active instance before allocating a new instance. If the component was already instantiated, the existing instance is returned:
コンポーネントはシングルトンパターンに従うことを覚えておいてください。コンポーネントマネージャ毎のどんなコンポーネントでも、アクティブなインスタンスは1つのみ存在します。コンポーネントのコンストラクタは「魔法」(新しいインスタンスを割り当てる前に、アクティブなインスタンスが既にあるか否かに関係なく、コンポーネントマネージャに問い合わせる)を使います。もし、コンポーネントが既にインスタンス化されていれば、コンストラクタはそれを返します:

    my_comp1 = MyComponent(comp_mgr)
    my_comp2 = MyComponent(comp_mgr)
    assert id(my_comp1) == id(my_comp2)

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:
もし、コンポーネントのデータメンバの初期化が必要であれば、initメソッドをオーバライドすることが出来ます。しかし、コンポーネントマネージャはコンポーネントをオンデマンドにインスタンス化できる必要があるので、initは他のどんなパラメータも引数としてはいけません。コンポーネントマネージャがコンストラクタに渡した参照を含めて:

    from trac.core import *

    class MyComponent(Component):
        def __init__(self):
            self.data = {}

    comp_mgr = ComponentManager()
    my_comp = MyComponent(comp_mgr)

Direct Component sub-classes also do not need to worry about invoking the base classes init method (which is empty).
Componentを直接継承するサブクラスは、親クラスのinitメソッドが呼び出すことを考慮する必要はありません。Componentクラスのinitメソッドは空です。

Declaring an extension point

The component above doesn't actually do anything. Making an object a component only makes it act as a singleton in the scope of a component manager, which isn't that exciting in itself.
上記のコンポーネントは実際には何もしません。コンポーネントマネージャのスコープ内にいるシングルトンというだけの(特に面白くもない)振舞いをするコンポーネントを作ります。

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:
エクステンションを機能を使って、はじめてコンポーネントの価値が明確になります。簡単な例として、次のコンポーネントは1つのエクステンションポイントを提供します。このエクステンションポイントは、ToDoリストのアイテムの更新を、(よく知られた)objservalbleパターンを用いて他のコンポーネントに監視させます。

    from trac.core import *
    
    class ITodoObserver(Interface):
        def todo_added(name, description):
            """Called when a to-do item is added."""

    class TodoList(Component):
        observers = ExtensionPoint(ITodoObserver)

        def __init__(self):
            self.todos = {}

        def add(self, name, description):
            assert not name in self.todos, 'To-do already in list'
            self.todos[name] = description
            for observer in self.observers:
                observer.todo_added(name, description)

Here, the TodoList class declares an extension point called observers with the interface ITodoObserver. The interface defines the contract that extending components need to conform to.
ここで、TodoListクラスはITodoObserverインタフェースを用いて、observerから呼ばれるエクステンションポイントを宣言します。インタフェースは、それを拡張(継承?)するコンポーネントが従うべき規約を定義します。

The TodoList component notifies the observers inside the add() method by iterating over self.observers and calling the todo_added() method for each. The Component class performs some magic to enable that: Conceptually, it intercepts access to the extension point attribute and finds all registered components that declare to extend the extension point. For each of those components, it gets the instance from the component manager, potentially activating it if it is getting accessed for the first time.
TodoListコンポーネントは、self.observersを繰り返すこととtodo_added()メソッドを呼び出すことをお互いに行うことで、observerにadd()メソッドを通知します。コンポーネントクラスはいくつかの「魔法」を使います。要するに、それはエクステンションポイントへのアクセスを横取りして、エクステンションポイントを拡張(実装?)すると宣言している全ての登録済みコンポーネントを探します。それぞれのコンポーネントのために、コンポーネントから(初めてアクセスであれば、アクティブ化して)インスタンスを取得します。

Plugging in to an extension poin

Now that we have an extendable component, let's add another component that extends it:
私たちには、拡張可能な親コンポーネントがあるので、それを拡張する別のコンポーネントを追加してみましょう。 (訳注:実際にプラグインを作るときは、こうやってTracが持っているコンポーネントを拡張する形でつくることになります)

    class TodoPrinter(Component):
        implements(ITodoObserver)

        def todo_added(self, name, description):
            print 'TODO:', name
            print '     ', description

This class implements the ITodoObserver interface declared above, and simply prints every new to-do item to the console. By declaring to implement the interface, it transparently registers itself as an extension of the TodoList class.
このクラスは、上で宣言されたITodoObserverインタフェースを継承します。かつ簡単に新しいToDoアイテム全てをコンソールに出力します。インタフェースのimplementsを宣言することで、TodoListのクラスの拡張として、透過的に自身を登録します。(訳注: implementsしたらその時点でTodoPrinterもTracから使えるようになるということか)

Note that you don't actually derive the component from the interface it implements. That is because conformance to an interface is orthogonal to inheritance; and because Python doesn't have static typing, there's no need to explicitly mark the component as implementing an interface.
注:実際にはコンポーネントは、それを実装するインタフェースに由来しない(訳注:親クラスでない)ことに注意してください。それはインタフェースへの順応が継承と直行しているからです。そして、Pythonにはstaticタイプが無いので、インタフェースを実装するからといって明確にコンポーネントをマークする必要は全くありません。

You can specify multiple extension point interfaces to extend with the implements method by simply passing them as additional arguments.
あなたは、メソッドを実装する(単にそれらを追加引数としてパスする方法で)ことで、拡張に使う複数のエクステンションポイントを指定することが出来ます。

Putting it together

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:
ここまでで、エクステンションポイントを提供するコンポーネントと、そのエクステンションポイントを拡張するコンポーネントとの両方を作りました。 そこで、このTodoリストの例を使って、何が起きるか確認してみましょう。

comp_mgr = ComponentManager()
todo_list = TodoList(comp_mgr)

todo_list.add('Make coffee',
          'Really need to make some coffee')
todo_list.add('Bug triage',
          'Double-check that all known issues were addressed')

Running this script will produce the following output:
スクリプトを実行すると、次のように出力されます。

TODO: Make coffee
      Really need to make some coffee
TODO: Bug triage
      Double-check that all known issues were addressed

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.)
この出力はTodoPrinterクラスが行っていることは明らかです。しかしながら、上のコードはTodoPrinterクラスについて一切書いていないことに注意してください。動作させるのに必要であるのはクラスを宣言するだけであることです。(拡張する側のクラスが登録されるためには、 Pythonスクリプトにimportされる必要があるのが前提です。拡張メカニズム自身からどんなに離れてもコンポーネントのローディングの局面ではそうです。)

添付ファイル