Pyramid で Zope コンポーネントアーキテクチャを使う

内部的に、 PyramidZope Component Architecture の コンポーネントレジストリを application registry として使用します。 Zope コンポーネントアーキテクチャーは会話では “ZCA” と呼ばれます。

従来の Zope アプリケーションでデータにアクセスするために使用される zope_component API は不明瞭なところがありました。 例えば、これは従来の Zope アプリケーションに現われるような、 zope.component.getUtility() グローバル API を使用した 典型的な「無名ユーティリティ」の検索です:

1
2
3
from pyramid.interfaces import ISettings
from zope.component import getUtility
settings = getUtility(ISettings)

このコードが実行された後で、 settings は Python 辞書になります。 しかし、「一般人」がコードをざっと読むだけでこれを理解することは困難です。 開発者によって zope.component.getUtility API が使用されている場合、 コードのカジュアルな読者にとって概念的な負荷は高いです。

ZCA は Pyramid のような フレームワーク を構築するには優れた ツールですが、 zope.component API の不透明さにより、それは必ずしも アプリケーションを構築するための最良のツールとは限りません。そのため、 Pyramid はアプリケーション開発者に対して ZCA の存在を見せない ようにする傾向にあります。 Pyramid アプリケーションを作成するために ZCA を理解する必要はありません; その使用は事実上フレームワークの単なる 実装詳細です。

しかし、既に Zope アプリケーションを書くことに慣れている開発者 は、今もしばしば Pyramid アプリケーションを構築する際に ZCA を 使用することを望みます; pyramid はこれを可能にします。

Pyramid アプリケーションで ZCA グローバル API を使う

Zope は、同一の Python プロセスで動作するすべての Zope アプリケーション に対して単一の ZCA レジストリ (「グローバル」 ZCA レジストリ) を使用します。 そのため、単一のプロセスで複数の Zope アプリケーションを起動することは事実上 不可能です。

しかし、デプロイの容易さのためには、1プロセスで複数のアプリケーションが 実行できることは多くの場合に有用です。例えば、 PasteDeploy “composite” を使用することによって、同じプロセスで独立した個別の WSGI アプリケーションを起動することができます。それぞれのアプリケーションは 特定の URL プリフィックスのリクエストに応答します。これによって、例えば /turbogears で TurboGears アプリケーションを、 /pyramidPyramid アプリケーションを実行することが可能になります。 両方のアプリケーションは、単一の Python プロセス内の同じ WSGI サーバを使用して実行されます。

ほとんどのプロダクション Zope アプリケーションは比較的大規模で、メモリの 制約によって Python プロセスあたり複数の Zope アプリケーションを実行する ことは実用的ではありません。しかし、 Pyramid アプリケーションは 非常に小さくメモリをほとんど消費しないかもしれません。したがって、1プロセス あたり複数の Pyramid アプリケーションが実行できることは合理的な ゴールです。

単一のプロセスで複数の Pyramid アプリケーションを起動できるように するために、 Pyramid はデフォルトで アプリケーションごとに 個別の ZCA レジストリを使用します。

これは合理的なゴールを提供していますが、 Pyramid アプリケーション を構築するために典型的な Zope アプリケーションを構築するのに 用いられる利用パターンを適用しようとすると、いくつかの問題を引き起こします。 特別なことをしなければ、 zope.component.getUtilityzope.component.getSiteManager のような ZCA 「グローバル」 API は ZCA 「グローバル」レジストリを使用します。そのため、これらの API は Pyramid アプリケーションの中で使用された場合にうまく動かないように 見えるでしょう。なぜなら、それらが Pyramid アプリケーションに 関連付けられたコンポーネントレジストリではなく、 ZCA グローバルレジストリを 参照するからです。

これを修正するためには3つの方法があります: ZCA グローバル API を まったく使わないか、 pyramid.config.Configurator.hook_zca() を 使用するか、あるいはスタートアップ時点で ZCA グローバルレジストリを Configurator コンストラクタに渡すか。このセクションでは これら3つの方法についてすべて記述します。

グローバル ZCA API を使わない

zope.component.getSiteManager, zope.component.getUtility, zope.component.getAdapter, zope.component.getMultiAdapter のような ZCA 「グローバル」 API 関数は、厳密に言えば必須ではありません。 すべてのコンポーネントレジストリには、同じ機能を持つメソッド API があります; それを代わりに使うことができます。例えば、下記の registry 値が Zope コンポーネントアーキテクチャーのコンポーネントレジストリであると 仮定すると、次のコード片は zope.component.getUtility(IFoo) と等価です:

1
registry.getUtility(IFoo)

完全なメソッド API は zope.component パッケージの中で文書化されます。 しかし、大部分は「グローバル」 API のほぼ忠実なミラーです。

「グローバル」 ZCA API の使用をやめて、代わりにレジストリのメソッド インタフェースを使用すれば、あとは Pyramid コンポーネントレジストリ を取得する方法を知る必要があるだけです。

それをするのに 2 つの方法があります:

  • Pyramid ビューかリソースコードの中で pyramid.threadlocal.get_current_registry() 関数を使用する。 これは常に「現在の」 Pyramid アプリケーションレジストリを返します。
  • Pyramid ビューコードの中で request オブジェクトの registry という名前の属性を使用する (例えば request.registry)。 これは実行中の Pyramid アプリケーションに関係付けられた ZCA コンポーネントレジストリです。

pyramid.threadlocal.get_current_registry() についての 詳細は Thread Locals を参照してください。

hook_zca を使って ZCA グローバル API を有効にする

次の慣用句的な Pyramid スタートアップコードの断片を考えてください:

1
2
3
4
5
6
7
from zope.component import getGlobalSiteManager
from pyramid.config import Configurator

def app(global_settings, **settings):
    config = Configurator(settings=settings)
    config.include('some.other.package')
    return config.make_wsgi_app()

上記の app 関数が実行される時に Configurator が構築されます。 configurator が作成される場合、それは 新しい application registry (ZCA コンポーネントレジストリ) を作成します。 Configurator コンストラクタが呼ばれる時に registry 引数が省略された場合、 または Configurator コンストラクタに registry 引数として None 値が渡された場合、新しいレジストリが構築されます。

リクエスト中は、 Configurator によって作成されたアプリケーションレジストリ が「現在の値」になります。これは、リクエストを扱うスレッドの中で get_current_registry() を呼び出すと、 アプリケーションに関連付けられたコンポーネントレジストリが返ることを 意味します。

その結果、アプリケーション開発者は get_current_registry を使用して レジストリを取得して、 グローバル ZCA API を使わない と同じように ユーティリティその他にアクセスすることができます。 しかし、依然としてグローバル ZCA API は使用することができません。 特別な処置をしなければ、 ZCA グローバル API は常に ZCA グローバル レジストリ (zope.component.globalregistry.base の中のもの) を返します。

これを「修正」して、 ZCA グローバル API に「現在の」 Pyramid レジストリを使用させるためには、セットアップコード内で hook_zca() を呼ぶ必要があります。 例えば:

1
2
3
4
5
6
7
8
from zope.component import getGlobalSiteManager
from pyramid.config import Configurator

def app(global_settings, **settings):
    config = Configurator(settings=settings)
    config.hook_zca()
    config.include('some.other.application')
    return config.make_wsgi_app()

オリジナルのスタートアップコードに config.hook_zca() を呼び出す 1行を追加しました (6行目)。この行による内部的な影響は、次のコードに類似 したことが行われるというものです:

1
2
3
from zope.component import getSiteManager
from pyramid.threadlocal import get_current_registry
getSiteManager.sethook(get_current_registry)

これは、 Pyramid リクエストを実行しているスレッドの中で ZCA グローバル API が Pyramid アプリケーションレジストリを使用 するようにします。

hook_zca を呼ぶことは、通常 Pyramid アプリケーションの中で グローバル ZCA API を使用できるようにする問題を「修正」するには十分です。 しかし、それはまた、同じプロセスで動作している Zope アプリケーションが Zope グローバルレジストリの代わりに Pyramid のグローバルレジストリ を使用するようになることを意味します。事実上、オリジナルの問題の逆です。 そのような場合には、次のセクション ZCA グローバルレジストリを使って ZCA グローバル API を有効にする の ステップに従ってください。

ZCA グローバルレジストリを使って ZCA グローバル API を有効にする

スタートアップ時に、 Pyramid アプリケーションに対して、 新しいレジストリを構築する代わりに ZCA グローバルレジストリを使用するように 指定することができます:

1
2
3
4
5
6
7
8
9
from zope.component import getGlobalSiteManager
from pyramid.config import Configurator

def app(global_settings, **settings):
    globalreg = getGlobalSiteManager()
    config = Configurator(registry=globalreg)
    config.setup_registry(settings=settings)
    config.include('some.other.application')
    return config.make_wsgi_app()

上記の 5, 6, 7 行目が興味のある行です。5行目は、グローバル ZCA コンポーネントレジストリを検索しています。6行目は、コンストラクタに registry 引数としてグローバル ZCA レジストリを渡して、 Configurator を作成しています。7行目は、グローバルレジストリに Pyramid 特有の設定を「セットアップ」します; これは通常レジストリが 作成されるときというよりは構築される時に実行されるコードですが、明示的に レジストリを渡す場合はそれを「手動で」呼ばなければなりません。

この段階では、 Pyramid は新しいアプリケーション固有のレジストリを 作成するのではなく ZCA グローバルレジストリを使用します; デフォルトで ZCA グローバル API がこのレジストリを使用するので、グローバル ZCA API を 使用するときに Zope アプリに期待されるように物事はうまく機能するでしょう。