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 つの
引数で呼び出しています: 文字列の jammyjam
は discriminator
という名前の
最初の引数として渡されます。また、 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
といった引数が存在します。
args
と kw
は、もし渡されれば、 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
がクロージャー関数の場合 args
と kw
はあまり有用ではありません。なぜなら、それらを渡すまでもなくディレクティブ
内のすべてのローカル変数に普通にアクセスできるからです。しかし、 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 を指定されたビューは決して呼ばれません)。
introspectables
は introspectable オブジェクトのシーケンスです。
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 への参照を示し、その逆も真です。