高度な設定

アプリケーションの拡張性をサポートするために、 PyramidConfigurator は、デフォルトで設定の衝突を検知し、他のパッケージ やモジュールから設定を命令的に取り込むことを可能にします。さらにそれは デフォルトで、分離された 2 つのフェーズで設定を行ないます。これは、いくつかの 状況で相対的な設定命令の順序をオーバーライドすることを可能にします。

衝突検知

これは、おなじみの最も単純な Pyramid アプリケーションの一例で、 命令的に設定されています:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
    return Response('Hello world!')

if __name__ == '__main__':
    config = Configurator()
    config.add_view(hello_world)
    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()

このアプリケーションを開始する時、すべては問題なく動くでしょう。 しかし、既に追加されているビューと同じ predicate 引数のセットで 別のビューを設定に追加しようとした場合、何が起こるでしょうか。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
    return Response('Hello world!')

def goodbye_world(request):
    return Response('Goodbye world!')

if __name__ == '__main__':
    config = Configurator()

    config.add_view(hello_world, name='hello')

    # conflicting view configuration
    config.add_view(goodbye_world, name='hello')

    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()

アプリケーションは今度は2つの衝突するビュー設定命令を持っています。 再びアプリケーションを開始しようとした時、それは開始しません。代わりに、 以下のような結果に終わるトレースバックを受け取るでしょう:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Traceback (most recent call last):
  File "app.py", line 12, in <module>
    app = config.make_wsgi_app()
  File "pyramid/config.py", line 839, in make_wsgi_app
    self.commit()
  File "pyramid/pyramid/config.py", line 473, in commit
    self._ctx.execute_actions()
  ... more code ...
pyramid.exceptions.ConfigurationConflictError:
        Conflicting configuration actions
  For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>,
        None, None, None, None, None, False, None, None, None)
  Line 14 of file app.py in <module>: 'config.add_view(hello_world)'
  Line 17 of file app.py in <module>: 'config.add_view(goodbye_world)'

このトレースバックは、以下のことを伝えようとしています:

  • ビュー設定命令に関する衝突情報を受け取っています (For: の行)
  • 2つの衝突する文があり、 For: 行の下に示されています: app.py の 14 行目にある config.add_view(hello_world. 'hello') と、 app.py の 17 行目にある config.add_view(goodbye_world, 'hello')

システムに両方のビュー設定に対する predicate 値が正確に同じであると 伝えようとしたので、これらの 2 つの設定命令は衝突した状態にあります。 hello_worldgoodbye_world の両方のビューが、同じ状況の下で 応答するように設定されています。その状況とは、 view name (name= 述語によって表わされている) が hello であることです。

これは Pyramid が解決できない曖昧性を示しています。この状況を 報告せずに許可するのではなく、デフォルトで Pyramid は ConfigurationConflictError エラーを上げて、アプリケーションの 作動を停止します。

衝突検知はあらゆる種類の設定に対して起こります: 命令的な設定、あるいは scan の実行に起因する設定。

手動による衝突解決

手動で衝突を解決する多くの方法があります: 衝突しないように登録を変更する ことによって、 pyramid.config.Configurator.commit() を戦略的に 使用することによって、または “autocommitting” configurator の使用によって。

正しいやり方

衝突を解決する最も正確な方法は「必要なことを行う」ことです: 衝突する 設定命令をなくすために設定コードを変更してください。これがどのように終わるかの 詳細は、完全にアプリケーションで行っている設定命令に依存します。 ConfigurationConflictError の中で提供されている詳細を使用して、 問題となっている衝突を特定して、それに従って設定コードを修正してください。

既存のアプリケーションを拡張しようとする間に衝突が発生していて、その アプリケーションには以下のような設定を行なう関数がある場合:

1
2
def add_routes(config):
    config.add_route(...)

config を引数としてこの関数を直接呼び出さないでください。代わりに、 pyramid.config.Configuration.include() を使用してください:

1
config.include(add_routes)

関数を直接呼び出す代わりに include() を使用すると、呼び出し側のコードに定義されている設定ステートメントが include された関数の設定をオーバーライドするので、自動的な衝突の解決が ある程度行われます。 自動的な衝突の解決外部ソースからの設定インクルード も参照してください。

config.commit() の使用

設定呼び出しの間で commit() メソッド を使用することによって、設定を手動でコミットすることができます。 例えば、 commit を加えた結果、以前に検討したアプリケーションで衝突が 発生することを防ぎます。これは、衝突を生成するアプリケーションです:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
    return Response('Hello world!')

def goodbye_world(request):
    return Response('Goodbye world!')

if __name__ == '__main__':
    config = Configurator()

    config.add_view(hello_world, name='hello')

    # conflicting view configuration
    config.add_view(goodbye_world, name='hello')

    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()

2つの add_view 呼び出しの間に commit() の呼び出しを行うことで、 それらの衝突を防ぐことができます:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
    return Response('Hello world!')

def goodbye_world(request):
    return Response('Goodbye world!')

if __name__ == '__main__':
    config = Configurator()

    config.add_view(hello_world, name='hello')

    config.commit() # commit any pending configuration actions

    # no-longer-conflicting view configuration
    config.add_view(goodbye_world, name='hello')

    app = config.make_wsgi_app()
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()

上記の例において、2つの add_view 呼び出しの間で commit() の呼び出しを行いました。 commit() はすべての待機中の 設定命令を実行します。

commit() の呼び出しは常に安全です。 それは、すべての待機中の設定アクションを実行して、設定アクションのリストを 「クリーン」にします。

autocommitting な configurator を使用している場合、 commit() の効果がないことに注意 してください (autocommitting な Configurator の使用 を参照)。

autocommitting な Configurator の使用

configurator のコンストラクタにパラメーター autocommit=True を使用 することによって、衝突検知を回避するための重いハンマーを使用することも できます。例えば:

1
2
3
4
from pyramid.config import Configurator

if __name__ == '__main__':
    config = Configurator(autocommit=True)

Configurator に渡された autocommit パラメーターが True の場合、 衝突検知 (そして 2フェーズ設定) は無効になります。設定命令は直ちに 実行されるようになり、後続の設定が前のものをオーバーライドします。

autocommitTrue の場合、 commit() は効果がありません。

ユニットテストを行なうコードの中で Configurator を使用する際、 通常テストコード中の衝突検知や2フェーズ設定に関心がないので、 autocommitting Configurator を使用することは通常よい考えです。

自動的な衝突の解決

あなたのコードが include() メソッドを 使用して外部設定をインクルードする場合、いくつかの衝突は自動的に解決されます。 「インクルード」の結果作られる設定命令は、「インクルード」メソッドの 呼び出し元で起こる設定命令によってオーバーライドされます。

自動的な衝突の解決は以下のゴールをサポートします: Pyramid アプリケーション を再利用したいと思ったユーザが、そのコードをハックせずに「外部から」 このアプリケーションの設定をカスタマイズしたければ、パッケージから 設定関数を「インクルード」して、その設定命令のうちのいくつかだけを インクルードを行うコード内でオーバーライドできること。もしインクルード されたコード中の設定命令を呼び出し元のコードまで移動させた場合に衝突が 起きるとしても、インクルードを行うコード内の設定命令によって衝突は生成 されません。

衝突検知を提供しているメソッド

これらは衝突検知を提供する configurator のメソッドです:

add_view(), add_route(), add_renderer(), set_request_factory(), set_session_factory(), set_request_property(), set_root_factory(), set_view_mapper(), set_authentication_policy(), set_authorization_policy(), set_renderer_globals_factory(), set_locale_negotiator(), set_default_permission(), add_traverser(), add_resource_url_adapter(), add_response_adapter().

add_static_view() は、衝突を考慮する add_route および add_view メソッドによって実装されているため、 さらに間接的な衝突検知を提供します。

外部ソースからの設定インクルード

一部のアプリケーションプログラマは、設定命令を再利用およびオーバーライド することが簡単であるのと同じ方法で設定コードを分解するでしょう。 例えば、そのような開発者は、 route をアプリケーションに追加するために 使用される関数を分解するかもしれません:

1
2
def add_routes(config):
    config.add_route(...)

config を引数としてこの関数を直接呼ぶのではなく。 代わりに、 pyramid.config.Configuration.include() を使用します:

1
config.include(add_routes)

関数を直接呼ぶのではなく include を使用することで、 自動的な衝突の解決 が働くようになるでしょう。

include() は、引数として module を受け取ることもできます:

1
2
3
import myapp

config.include(myapp)

これが適切に動作するためには、 myapp モジュールに特殊名 includeme を持つ callable が含まれている必要があります。それは (例として上に示した add_routes callable と同じように) 設定を行ないます。

include() はさらに、関数または モジュールに対する dotted Python name を受け取ることもできます。

2フェーズ設定

設定を行うために autocommitting でない Configurator が使用される 場合 (デフォルト)、設定の実行は2つのフェーズで起こります。第1フェーズでは、 “eager” 設定アクション (レンダラーの登録のような、他のものよりも先に 起こる必要のあるアクション) が実行されます。そして、 eager アクションの 結果に依存する各々のアクションのために 識別子 (discriminator) が計算 されます。第2フェーズでは、衝突検知を行うためにすべてのアクションの 識別子が比較されます。

これにより、内部的な順序の制約がない設定メソッドについては、設定メソッド 呼び出しの実行順序は重要ではありません。例えば、 autocommitting でない configurator が使用される場合、 add_view()add_renderer() の相対順序は重要では ありません。このコードスニペットは:

1
2
config.add_view('some.view', renderer='path_to_custom/renderer.rn')
config.add_renderer('.rn', SomeCustomRendererFactory)

これと同じ結果になります:

1
2
config.add_renderer('.rn', SomeCustomRendererFactory)
config.add_view('some.view', renderer='path_to_custom/renderer.rn')

たとえビュー命令がカスタムレンダラーの登録に依存しても、 2フェーズの設定により、設定命令が実行される順序は重要ではありません。 add_rendereradd_view の後で呼ばれても、 add_view.rn レンダラーを見つけることができるでしょう。

autocommitting な configurator を使用する場合、同じことは真ではありません (autocommitting な Configurator の使用 を参照)。 autocommitting な configurator が使用されている場合、 2フェーズの設定は無効になります。 また、設定命令は依存関係の順番で並べられなければなりません。

add_route() のようないくつかの設定 メソッドには内部的な順序の制約があります: それらが関係する route には 相対順序が必要です。そのような順序制約は、2フェーズの設定によって免除 されません。 route は、依然として設定実行順に追加されます。