Pyramid Configurator の拡張

Pyramid では、カスタムディレクティブによって Configurator を拡張することが できます。カスタムディレクティブは、他のディレクティブを使用したり、 カスタム action を追加したり、 conflict resolution に参加 したり、 introspectable オブジェクトを提供したりすることができます。

add_directive による Configurator へのメソッドの追加

フレームワーク拡張の作者は configurator の pyramid.config.Configurator.add_directive() メソッドを使用することで Configurator に任意のメソッドを追加することができます。 add_directive() を使用することによって、 Pyramid configurator を任意の方法で拡張することが可能になり、 アプリケーション特有のタスクをより簡潔に行なえるようになります。

add_directive() メソッドは2つの 位置引数を受け取ります: メソッド名および callable オブジェクトです。 callable オブジェクトは、通常その最初の引数として configurator インスタンスを取る関数で、他の任意の位置引数とキーワード引数を 受け取ります。例えば:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from pyramid.events import NewRequest
from pyramid.config import Configurator

def add_newrequest_subscriber(config, subscriber):
    config.add_subscriber(subscriber, NewRequest)

if __name__ == '__main__':
    config = Configurator()
    config.add_directive('add_newrequest_subscriber',
                         add_newrequest_subscriber)

一旦 add_directive() が呼ばれると、 ユーザはその後、あたかもそれが configurator のビルトインメソッドで あるかのように、追加されたディレクティブをその名前で呼ぶことができます:

1
2
3
4
def mysubscriber(event):
    print event.request

config.add_newrequest_subscriber(mysubscriber)

add_directive() の呼び出しは、 include() による 外部ソースからの設定インクルード によってインクルードされることを意図して、 しばしば「フレームワーク風」パッケージの includeme 関数内に 「隠蔽」されます。例えば、 pyramid_subscriberhelpers という名前の パッケージに以下のコードを入れた場合:

1
2
3
def includeme(config):
    config.add_directive('add_newrequest_subscriber',
                         add_newrequest_subscriber)

アドオンパッケージ pyramid_subscriberhelpers のユーザは、その後 それをインストールして、続いて以下のようにすることができるでしょう:

1
2
3
4
5
6
7
def mysubscriber(event):
    print event.request

from pyramid.config import Configurator
config = Configurator()
config.include('pyramid_subscriberhelpers')
config.add_newrequest_subscriber(mysubscriber)

ディレクティブ内での config.action の使用

カスタムディレクティブが (上記の pyramid.config.Configurator.add_subscriber() のように) 既存の configurator メソッドだけで仕事を行うことができない場合、 そのディレクティブは pyramid.config.Configurator.action() メソッドを 利用する必要があるかもしれません。このメソッドは、 pyramid.config.Configurator.commit() が呼ばれたときに Pyramid が処理を試みる「アクション」のリストにエントリを追加します。 アクションは、 discriminator (識別子) と、任意のコールバック関数と、 Pyramid のアクションシステムによって使用される他の任意のメタデータを含む 単なる辞書です。

これは「アクション」メソッドを使用するディレクティブの例です:

1
2
3
4
5
6
7
8
def add_jammyjam(config, jammyjam):
    def register():
        config.registry.jammyjam = jammyjam
    config.action('jammyjam', register)

if __name__ == '__main__':
    config = Configurator()
    config.add_directive('add_jammyjam', add_jammyjam)

なかなか手が込んでいますが、これは何を行うのでしょうか。アクションメソッド はいくつかの引数を受け取ります。上記の add_jammyjam という名前の ディレクティブでは、 action() を 2 つの 引数で呼び出しています: 文字列の jammyjamdiscriminator という名前の 最初の引数として渡されます。また、 register という名前のクロージャー 関数は callable という名前の 2 番目の引数として渡されます。

action() メソッドが呼ばれる場合、 それは待機中の設定アクションのリストにアクションを追加します。 同じ識別子の値を持つすべての待機中のアクションは、潜在的に お互い衝突する可能性があります (衝突検知 を参照)。 Configurator の commit() メソッドが 呼ばれた時 (明示的に、または make_wsgi_app() を呼んだ結果として)、 衝突するアクションは 自動的な衝突の解決 を通して 自動的に解決される可能性があります。自動的に衝突を解決することができない 場合、 ConfigurationConflictError 例外が発生し、アプリケーション のスタートアップが停止されます。

したがって上記の例において add_jammyjam ディレクティブのユーザが このようにしたなら:

config.add_jammyjam('first')
config.add_jammyjam('second')

上記の一連の呼び出しに起因してアクションリストがコミットされた時、 ユーザのアプリケーションは開始しません。2つの呼び出しによって生成された アクションの識別子が直接の衝突状態にあるからです。自動的な衝突の解決は この衝突を解決することができず (config.include が使われていないので) 、 ユーザは add_jammyjam の呼び出しの間で連続する呼び出しが互いと衝突 しないことを保証するために中間の pyramid.config.Configurator.commit() 呼び出しを提供していません。

これはアクションメソッドに対する識別子引数の目的を実証しています: それは アクションのユニーク制約を示すために使用されます。同じ識別子を持つ 2 つの アクションは、衝突が自動的にあるいは手動で解決されない限り衝突するでしょう。 識別子は任意のハッシュ可能オブジェクトにすることができますが、一般的には 文字列またはタプルです。 ユーザが曖昧な設定命令を提供しないことを 宣言的に保証するために識別子を使用してください。

しかし、 add_jammyjam のユーザが設定の衝突が起きない以下のような方法で 使用した場合を考えてみましょう。

config.add_jammyjam('first')

今度は何が起こるでしょうか。 add_jammyjam メソッドが呼ばれた時、 待機中のアクションリストにアクションが追加されます。 commit() によって待機中の設定 アクションが処理され、衝突が生じない場合、 add_jammyjam 内の action() メソッドの第 2 引数として 渡された callable が引数なしで呼ばれます。 add_jammyjam 内の callable は、 register クロージャー関数です。それは、単純にユーザが add_jammyjam 関数に jammyjam 引数として渡したものをなんでも 値 config.registry.jammyjam に設定します。したがって、ユーザが ディレクティブを呼び出した結果として、レジストリの jammyjam 属性に 文字列 first が設定されるでしょう。 callable は、衝突検知が働く ようになるまでユーザがディレクティブを呼び出した結果を遅延するために、 ディレクティブによって使用されます。

action() メソッドには他に args, kw, order, introspectables といった引数が存在します。

argskw は、もし渡されれば、 callable 関数が呼ばれるときの 引数として使用される値として存在します。例えば、ディレクティブはそれらを 以下のように使用するかもしれません:

1
2
3
4
5
6
def add_jammyjam(config, jammyjam):
    def register(*arg, **kw):
        config.registry.jammyjam_args = arg
        config.registry.jammyjam_kw = kw
        config.registry.jammyjam = jammyjam
    config.action('jammyjam', register, args=('one',), kw={'two':'two'})

上記の例において、このディレクティブがアクションを生成するために使用され、 そのアクションがコミットされる時、 config.registry.jammyjam_args('one',) に設定されて、 config.registry.jammyjam_kw{'two':'two'} に設定されるでしょう。 正直なところ、 callable がクロージャー関数の場合 argskw はあまり有用ではありません。なぜなら、それらを渡すまでもなくディレクティブ 内のすべてのローカル変数に普通にアクセスできるからです。しかし、 callable としてクロージャーを使用していなければ、それらは有用なことがあります。

order は大雑把な順序管理メカニズムです。 order のデフォルトは 整数 0 です; それは他の任意の整数にセットすることができます。 order を共有するすべてのアクションは、より高い order を共有する他のアクション より前に呼ばれるでしょう。これは、別のディレクティブの callable が先に 実行されていることに依存するcallable ロジックを持つディレクティブを書く ことを可能にします。例えば、 Pyramid の pyramid.config.Configurator.add_view() ディレクティブは pyramid.config.Configurator.add_route() メソッドより高い order で アクションを登録します。これにより、 add_view メソッドの callable は、 route_name が渡された場合、この名前による route は add_route によって既に登録されていると仮定することができ、またそのような route が まだ登録されていない場合は設定エラーとすることができます (route_name パラメーターに存在しない route を指定されたビューは決して呼ばれません)。

introspectablesintrospectable オブジェクトのシーケンスです。 introspectable のシーケンスを action() メソッドに渡すことができ、 それにより Pyramid の設定 introspection システムを拡張することが可能です。

Configuration introspection の追加

Note

introspection サブシステムは Pyramid 1.3 からの新機能です。

Pyramid は、デバッグ用ツールが実行中のアプリケーションの設定を見ることの できる、設定 introspection システムを提供します。

すべてのビルトイン Pyramid ディレクティブ (pyramid.config.Configurator.add_view()pyramid.config.Configurator.add_route() など) は、呼び出された時に いくつかの introspectable を登録します。例えば、 add_view によって ビューを登録する場合、このディレクティブは少なくとも 1 つの introspectable を登録します: ビュー登録自体に関する introspectable です。 それは渡された引数に対して人間が判読可能な値を提供します。特定のビュー がレンダラーを使用するかどうか、特定のビューが特定のリクエストメソッドに制限 されているかどうか、あるいは、特定のビューがどの route に対して登録されて いるかを判断するために、後で introspection 質問システムを使用することができます。 Pyramid 「デバッグツールバー」は、 Pyramid の開発者に情報を表示するために 様々な方法で introspection システムを利用します。

introspectable オブジェクトのシーケンスが action() メソッドに渡される場合、 introspection 値がセットされます。これは、 introspectable を使用するディレクティブの 一例です:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def add_jammyjam(config, value):
    def register():
        config.registry.jammyjam = value
    intr = config.introspectable(category_name='jammyjams',
                                 discriminator='jammyjam',
                                 title='a jammyjam',
                                 type_name=None)
    intr['value'] = value
    config.action('jammyjam', register, introspectables=(intr,))

if __name__ == '__main__':
    config = Configurator()
    config.add_directive('add_jammyjam', add_jammyjam)

気づいたかもしれませんが、上記のディレクティブは introspectable オブジェクトを 作成するために Configurator の introspectable 属性 (pyramid.config.Configurator.introspectable) を使用しています。 introspectable オブジェクトのコンストラクタは、 少なくとも4つの引数を要求します: category_name, discriminator, title, type_name です。

category_name はこの introspectable の論理的なカテゴリを表わす文字列です。 通常 category_name は、アクションによって加えられているオブジェクトの型の 複数形です。

discriminator は、 カテゴリ内で ユニークな値です (これはアクション の集合全体でユニークでなければならないアクション識別子とは異なります)。 それは典型的に、このカテゴリ内でこの introspectable に対してユニークな 値を表わす文字列またはタプルです。それはリンクを生成するのに使用され、 他の introspectable に対してリレーションを構成するターゲットの一部として 使用されます。

title は人間が判読可能な文字列で、この introspectable の分かりやすい 要約を表示するために introspection システムのフロントエンドによって使用されます。

type_name は、ソートと表示を目的としてこの introspectable をカテゴリ 内で下位分類するために使用される値です。それは任意の値にすることができます。

introspectable は辞書のようにアクセスすることもできます。任意のキー/値 のペアを格納することができますが、典型的には関連するディレクティブに 渡された引数に関連した値が格納されます。 category_name, discriminator, title および type_name は introspectable に関する メタデータ です。 一方、キー/値ペアとして提供される値はintrospectable によって提供される 実際のデータです。上記の例では、ディレクティブに渡された value 引数の 値を value キーに設定しています。

上記のディレクティブは、 introspectable を変更して、それを introspectable キーワード引数に渡すタプルの最初の要素として action メソッドに渡します。これにより、この introspectable は このアクションと関連付けられます。これ以降、 introspection ツールの 一覧にこの introspectable が表示されるようになります。

introspectable の関連付け

2 つの introspectable は互いに関連を持つことができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def add_jammyjam(config, value, template):
    def register():
        config.registry.jammyjam = (value, template)
    intr = config.introspectable(category_name='jammyjams',
                                 discriminator='jammyjam',
                                 title='a jammyjam',
                                 type_name=None)
    intr['value'] = value
    tmpl_intr = config.introspectable(category_name='jammyjam templates',
                                      discriminator=template,
                                      title=template,
                                      type_name=None)
    tmpl_intr['value'] = template
    intr.relate('jammyjam templates', template)
    config.action('jammyjam', register, introspectables=(intr, tmpl_intr))

if __name__ == '__main__':
    config = Configurator()
    config.add_directive('add_jammyjam', add_jammyjam)

上記の例で、 add_jammyjam ディレクティブは 2 つの introspectable を登録しています。 1 つ目はディレクティブに渡された value と関係しています; 2 つ目はディレクティブに渡された template と関係しています。 ディレクティブ内の概念がそれ自身の introspectable を持っても良いくらいに 重要だと考えるなら、 1 つのディレクティブに対して複数の introspectable を (「主要な概念」に対して 1 つの introspectable を、 関連する概念に対して別の introspectable を) 登録することができます。

上記の intr.relate (pyramid.interfaces.IIntrospectable.relate()) の呼び出しにはカテゴリ名とディレクティブの 2 つの引数が渡されています: 上記の例は、実質的にディレクティブが intr introspectable と tmpl_intr introspectable の関係を築く意思があることを示しています; relate に渡された引数は、カテゴリ名および tmpl_intr introspectable の識別子です。

同じディレクティブによって作られた 2 つの introspectable の間で関係を 作る必要はありません。代わりに、あるディレクティブによって作成された introspectable と別のディレクティブによって作成された別の introspectable の間で、片方の側でもう片方のディレクティブのカテゴリ名と 識別子を伴って relate を呼ぶことによって関係を築くことができます。 ただし、 introspectable を他の存在しない introspectable と関連付けよう とすると、設定のコミット時にエラーが発生します。

introspectable の関係は introspection 値のフロントエンドシステムの 表示結果に表れます。例えば、ビュー登録が route 名を指定すれば、 ビュー callable に関連付けられた introspectable は、それが関係する route への参照を示し、その逆も真です。