Pyramid の設計擁護

時々 Pyramid の設計の様々な様相に対する挑戦が申し立てられます。 続く議論にコンテキストを与えるために、私たちは、ここで設計に関する意思 決定とトレードオフのうちのいくつかを詳述します。ある場合には、フレーム ワークをより一層よくすることができ、それを改善するために取られる将来の ステップについて記述することを認めます; ある場合には、注意として、挑戦 を単に記録します。明らかに、常に皆を喜ばせることができるとは限らないた めです。

Pyramid Provides More Than One Way to Do It

Python のポピュラーな文化の基準は、 “TIOOWTDI” です (「やり方はひとつだけ」 – これは Perl の「やり方は一つじゃない」の頭文字 “TIMTOWTDI” に対する 軽視的でからかい半分の言及です)

Pyramid は良くも悪くも “TIMTOWTDI” なシステムです。例えば、 Pyramid は URL を view callable に解決する方法を複数 含んでいます: url dispatchtraversal です。 configuration のための複数の方法があります: imperative configuration, configuration decoration, ZCML (オプションで pyramid_zcml 経由で)。 多数の異なる種類の永続化とテンプレートのシステムで作動します。などなど。 しかしながら、これらの物事を行うのに重複する方法のほとんどは、 存在する理由や目的がないわけではありません: 私たちには serve すべき多く の聴衆がいます。そして私たちは、ウェブフレームワークレベルの TIMTOWTI が、 はるかに油断ならず有害な、 Python ウェブコミュニティー内での高レベルな 重複の数々を実際に 防ぐ と信じています。

Pyramid は、 repoze.bfg としてその生を開始しました。 それ以前の何年もの Zope の経験を持つ人々のチームによって書かれています。 traversal に関するアイデアと view lookup が動作する方法 は、完全に Zope から取られました。 Pyramid によって提供される認可 (authorization) サブシステムは Zope からの派生です。アプリケーションを fork せずに 拡張する ことができるというアイデアも Zope からの派生です。

これらの特徴の実装は、 Pyramid の作者達が仕事で顧客のための CMS タイプのシステムを自分達が慣れたやり方で構築できるように 要求 されました。 そのような特徴を持つシステムは、 Zope 自身を除いて他にありませんでした。 また、 Zope 自身は、その寿命のサインを示し始めていました。私たちは、 初期の設計ミスの結果によって阻害されるようになっていました。 Zope のドキュメンテーションの不足もまた、 work around を難しくしていました: 一箇所でそれらを指す「それですべて」といえるような包括的なドキュメント集がなく、 適切に説明するには大きすぎて自己一貫性がないことから、 Zope アプリケーションで働くために賢い人々を雇うことが難しい状況でした。 repoze.bfg を開発する前に、作者達は当然条件を満たした他のフレー ムワークを探しましたが、非 Zope フレームワークで条件を満たすものは ありませんでした。そこで、私たちは repoze.bfg の構築に乗り出しました。

しかし、調査の結果どの 単一の フレームワークも私たちが要求した特徴を すべて持つものはないという事実にもかかわらず、多くの既存のフレームワーク には優れた、時には非常に魅力的なアイデアがあることが明らかになりました。 特に、 URL dispatch は URL をコードにマッピングするより直接的な メカニズムです。

そのため、 Zope を除いて私たちのニーズに適合したフレームワークを見つけ ることはできませんでしたが (そして私たちは BFG に Zope の多くのアイデア を組み込みましたが)、さらに他のフレームワークの中で見つけた魅力的な特徴 (例えば url dispatch) をエミュレートしました。 BFG の最初の パブリックリリースの後、時間が進むにつれて、システムに含まれる Zope 主義に 対してアレルギーを持つ人々をサポートするために様々な特徴が付け加えられました。 例えば ZCML を単独で使用するのではなく imperative configurationconfiguration decoration を 使用してアプリケーションを設定する機能や、 interface オブジェクトの required な使用の除去などです。すぐに、このシステムが非常に一般的で、 元 Zope ユーザにも非 Zope ユーザにもアピールすることははっきりしました。

この一般化の結果、 BFG は Pylons 1 の機能セットと 90% を共有し、 したがってターゲット市場が非常に類似していることが明らかになりました。 それらが非常に似ていたので、 2つのシステムのどちらかを選ぶことは、その 他の面では超党派の開発者にとってはフラストレーションの溜まる訓練でした。 さらに、 Pylons と BFG の開発者コミュニティが同じユーザセットに対して 競争しているのは、2つのフレームワークがどれくらい類似しているかを考えると 奇妙なことでした。そこで Pylons と BFG のチームはマージ計画を立てるため の活動を共に始めました。 BFG に足りない特徴 (特に view handler クラス、フラッシュメッセージ、その他のマイナーな欠落) が、元 Pylons ユーザ に親しさを供給するために加えられました。その結果が Pyramid です。

Python ウェブフレームワークの世界は現在、分断された状態にあるという悪評 が立っています。私たちは、 Pyramid におけるコンポーネントの融合 が少なくとも 2 つの、現在は非常に distinct なユーザセットに訴えること を本当に望んでいます: Pylons と BFG のユーザです。 Pylons と BFG から 最良の概念を単一のコードベースの中へ統一し、それらの背後にある先祖からの 悪い概念を捨て去ることで、私たちの努力をより集約し、より多くのコードを 共有し、意味のない競争ではなく単一のユニットとして私たちの努力を促進する ことができるでしょう。私たちは、努力の 非常に大きな 重複に繋がる pack mentality をショートカットできることを望みます。このような mentality は、 競争的だが信じられないほど似たようなアプリケーションとラ イブラリ(お互い互換性がない特定の低レベルスタックの上に構築された) によっ て表わされます。さらに、信頼できる Python ウェブフレームワークの選択を 少なくとも1つ縮小するでしょう。また、 Zope や TurboGears のような他の コミュニティーのユーザに対して、物事を慣れた方法で行う十分な柔軟性を 可能にしながら要求する特徴を提供することで、私たちはそのようなユーザを 引きつけることを望んでいます。これらのゴールを達成するためにある程度の 機能のオーバーラップは想定され、避けることができません。少なくとも私た ちが高レベルの無意味な複製を防ぐつもりならば。もし私たちが仕事を十分に 果たしていれば、様々な聴衆は、ある種の想像上のウェブフレームワーク DMZ で互いに発砲しあうのではなく、共存し協力することができるでしょう。

Pyramid Uses A Zope Component Architecture (“ZCA”) Registry

Pyramid はアプリケーションレジストリとして内部で Zope の Zope Component Architecture (ZCA) “コンポーネントレジストリ” を 使用します。これはいくらか議論のポイントです。 PyramidZope の系列です。したがって、開発者がその初期で ZCA レジストリ を使用することは自然でした。しかしながら、私たちは ZCA レジストリの使用 には issues と consequences があることを理解しています。私たちはできる 限りそれに対処しようとしました。以下は Pyramid で ZCA レジストリ を使用すること、およびその使用によるトレードオフの内省です。

Problems

ZCA コンポーネントレジストリの中のデータにアクセスするために使用される グローバル API は、特に pretty でなく直感的でもありません。また、時々 それはまったく plain obtuse です。同様に ZCA のグローバル API を使用す るコードのカジュアルソースコードリーディングをする人に対する概念的負荷 は多少高いです。 ZCA 新参者が zope.component.getUtility() の グローバル API を使用した典型的な「無名のユーティリティ」の検索を行なう コードを読むことを考慮してください:

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

このコードが実行された後、 settings は Python 辞書になります。しかし、 普通の人はコードを読むだけではそれは知りようもありません。上記のコード断片には 明らかに、理解しやすさに多くの問題があります。

まず、 “utility” とは何でしょうか。もちろん、この議論のために、そして 上記のコードの目的にとって、それはそれほど重要ではありません。本当に知り たければ、 これ を読めば良いでしょう。しかしながら、コードの読者はコードをパースするた めに依然としてその概念を理解する必要があります。これは問題その1です。

次に、この ISettings というのは一体何でしょう。それは interface です。それはここで重要ですか? そうではありません。 単に同一性に基づく検索のためのキーにマーカーとして使用しているだけです: それは辞書 API を持ったオブジェクトを表していますが、この文脈において それはあまり重要ではありません。これが問題その2です。

第三に、 getUtility 関数は何を行いますか? それは ISettings 「ユーティリティ」の検索を行なって、それを返すはずです……さて、ユーティリティとは。 ここまでで interface についての理解と、この質問に答えるために 「ユーティリティ」概念に対する依存性をどのように構築したか注意してください: 非常に悪いサインです。さらに答えが循環的であることに注目してください。 本当に 悪いサインです。

4番目に、 getUtility はデータを得るためにどこを見るのでしょうか? ええ、もちろん「コンポーネントレジストリ」です。 コンポーネントレジストリとは何でしょうか。問題その4。

5番目に、何らかの魔法のレジストリが近くにある (hanging around) こと を受け入れたとして、このレジストリはどこに ある のでしょうか? はてさて... “あちこち (around)”? それは、確かにこの文脈で最良の答えでしょう (より具体的な答えは、内部についての知識を要求します)。複数のレジストリ があり得ますか? はい。そうすると、それは どの レジストリから検索しま すか? ええ、もちろん「カレント」レジストリです。 Pyramid においては、 カレントレジストリはスレッドローカル変数です。スレッドローカル変数を参照する API の使用は、それがどのように作動するか理解することを非局所的にします。

今、単にレジストリが hanging around しているという事実を buy in しました。 しかし、レジストリはどのようにして populate されるのでしょうか? なぜ config.add_view のようなディレクティブを呼び出すコードによって? しかしながら、特にこの場合 ISettings の登録はフレームワーク自体によって 内部で作られます: それはユーザ設定の中には全く存在していません。 これは理解するのが非常に難しいです。問題その6。

ここには明らかに、 ZCA を使用することで Pyramid フレームワークを 拡張しようとするコードの読者が負担する必要のある一定の認知負荷があります。 仮に彼または彼女が既にエキスパート Python プログラマでも、 そしてウェブアプリケーション領域のエキスパートであってもです。 これは準最適です。

Ameliorations

最初に、主要な改善: Pyramid は、 アプリケーション開発者が ZCA 概念あるいはその API を理解することを期待しませんアプリケーション 開発者が Pyramid アプリケーションの生成中に ZCA 概念または API を理解する必要があるとすれば、ある評価軸において失敗しています。

代わりに、フレームワークは ZCA レジストリの存在を ZCA API を使用する特殊 目的 API 関数の背後に隠します。例えば pyramid.security.authenticated_userid 関数を例に取ります。この関数 は、現在のリクエスト中にある userid を返します。あるいは userid が 現在 のリクエストの中にない場合は None を返します。 アプリケーション開発者はそれを以下のように呼び出します:

1
2
from pyramid.security import authenticated_userid
userid = authenticated_userid(request)

これでカレントユーザー id を得ることができます。

しかしながら、内部では authenticated_userid の実装はこのように なっています:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def authenticated_userid(request):
    """ Return the userid of the currently authenticated user or
    ``None`` if there is no authentication policy in effect or there
    is no currently authenticated user. """

    registry = request.registry # the ZCA component registry
    policy = registry.queryUtility(IAuthenticationPolicy)
    if policy is None:
        return None
    return policy.authenticated_userid(request)

このようなラッパーを使用して、アプリケーション開発者に ZCA API を見せな いように、私たちは常に努力しています。アプリケーション開発者は ZCA API のことを知るべきではありません: ドメインと密接に関係するいくつかの オブジェクトを引数に取って結果を返す Python 関数を呼び出すべきです。 そこからの帰結は、 Pyramid を使って書かれたアプリケーションの 読者は ZCA API を理解する必要もないということです。

ZCA API をアプリケーション開発者およびコードの読者に見せないようにして おくことは、ドメイン固有性を増強する一つの形です。アプリケーション開発 者は、ウェブフレームワークがどのように物事を行うかの細かい詳細な仕組み を理解したいとは思いません。人々は、自分が活動している領域に近い概念を 扱うことを望みます: 例えば、ウェブ開発者は ユーティリティ のことでは なくて ユーザ のことを知りたいでしょう。 Pyramid は、エンド ユーザに対して露出された機能としてではなく、実装詳細として ZCA を 使用します。

しかし、アプリケーション開発者とは異なり、 フレームワーク開発者 は ZCA レジストリ API を理解しなければなりません。これには traversal や view lookup のようなあらかじめ用意されたフレームワークのプラグポイント を通して Pyramid の機能をオーバーライドしたい人々も含まれます。

Pyramid フレームワークの開発者はフレームワーク開発者に対する ZCA レジストリ API の概念負荷の問題について非常に関心を持っていたので、 代替のレジストリ実装 repoze.component が実際に開発されました。このパッケージは、完全に 機能し十分テストされたレジストリ実装を持ち、その API は ZCA レジストリ API よりはるかに良いものでしたが、その作業の大部分は放棄されました。 また Pyramid の中でも使用されていません。最終的に ZCA レジストリ がより良い適合を示すことが証明されたので、私たちは Pyramid の中で それを使用し続けました。

Note

私たちは repoze.component に含まれるレジストリ実装を使用する ことを支持して ZCA レジストリを廃止するのではなく、それを使用し続ける ことにしましたが、その大きな理由は ZCA のインタフェース概念が インタフェース階層を使用するために必要だからです。それは (コンテキスト タイプ継承のような) 多くのシナリオで役に立ちます。この機能を可能にする インタフェースのような何らかのマーカー型を考え出すことは、単に車輪の 再発明のように思われました。

フレームワークの開発者および機能を拡張しようとする人に ZCA レジストリ API を理解させることはトレードオフです。私たち (Pyramid 開発者) は、 ZCA レジストリによって得られる特徴が好きです。また、私たちはそれが 何を行うか、またそれがどのようにして作動するかを理解することの重みにずっと 耐えてきました。 Pyramid の作者は ZCA を深く理解し、それを使用す るコードを他のコードと同じくらい容易に読むことができます。

しかし、潜在的にフレームワークを拡張したい開発者が ZCA レジストリ API にオリジナルの開発者ほど満足していないことは認識しています。したがって、 サードパーティの Pyramid フレームワーク開発者のことを考慮して、 私たちは妥協点を示しています (draw some lines in the sand)。

私たちは、すべての中核コードの中で zope.component.getUtilityzope.component.getAdapter のような ZCA のグローバル API 関数を使用 することを、ルールではなく例外としました。したがって、次のようにする代わりに:

1
2
3
from pyramid.interfaces import IAuthenticationPolicy
from zope.component import getUtility
policy = getUtility(IAuthenticationPolicy)

Pyramid コードは通常このようになります:

1
2
3
4
from pyramid.interfaces import IAuthenticationPolicy
from pyramid.threadlocal import get_current_registry
registry = get_current_registry()
policy = registry.getUtility(IAuthenticationPolicy)

後者は冗長ですが、間違いなく何が起こっているかがより明確です。 Pyramid 中核コードはすべて、 ZCA のグローバル API ではなく、この パターンを使用します。

Rationale

これらは、 ZCA レジストリを使用するという Pyramid の意思決定に 関係する主な根拠です:

  • 歴史。この質問に対する答えの自明でない部分は「歴史的理由」です。 Pyramid の設計の多くは Zope から直接取られています。 Zopeは 多くのトリックを行うために ZCA レジストリを使用します。 Pyramid はこれらのトリックを模倣します。そして ZCA レジストリ はそのようなトリックの数々についてうまく働くので、 Pyramid でも 同じ目的にそれを使用します。例えば Pyramidtraversal を使って requestview callable にマッピングする 方法は、ほとんど完全に Zope から持ち込まれています。 ZCA レジストリは、 このリクエストからビューへのマッピングがどのように行われるかの詳細に 重要な役割を果たします。
  • 機能。ZCA コンポーネントレジストリは、本質的にスーパー辞書とでも呼べ るものを提供します。それは単一のキーに基づいて値を検索するより複雑な 検索を可能にします。この検索能力のうちのいくらかはエンドユーザに非常 に役立ちます。例えば、コンテキストがあるクラスのオブジェクトである場合 だけ、あるいはコンテキストが何らかの interface を実装してい る場合だけ検索されるビューを登録するといったことが可能になります。
  • 単一性 (singularity)。 Pyramid アプリケーションの中に、 「アプリケーション設定」はたった一箇所にしかありません: コンポーネント レジストリの中です。コンポーネントレジストリは、実行時にフレームワーク によってなされる質問に対して アプリケーション の設定に基づいて答えます。 注:「アプリケーション」は「プロセス」と同じではありません。同じ Pyramid アプリケーションの複数の独立して設定されたコピーが 同じプロセス空間で走ることもあります。
  • 構成可能性 (composability)。 ZCA コンポーネントレジストリは命令的に populate されることができます。あるいは、設定ファイルを使って レジストリを populate する既存のメカニズムがあります (オプションの pyramid_zcml パッケージによる ZCML)。設定ファイル駆動による レジストリ population を利用するためのフロントエンドをゼロから書く必要は ありませんでした。
  • プラグ可能性 (pluggability)。 ZCA レジストリの使用は、十分に定義され 広く理解されたプラグインアーキテクチャーによってフレームワークの拡張を 可能にします。フレームワーク開発者および拡張者は ZCA レジストリを理解 しさえすれば、 Pyramid をほとんど任意に拡張することが可能です。 例えば、アプリ開発者が、いくつかのビューを同時に登録するディレクティブ を構築して、コードの中で「マクロ」としてそのディレクティブを使用する ことは比較的簡単です。これは多少他の (非 Zope) フレームワークと異なる 特徴です。
  • テスト容易性。フレームワークコード中で ZCA レジストリを賢く使えば、 コードのテストを行うのがより簡単になります。モンキーパッチやテストの ためにモックオブジェクトを登録する他の機能の代わりに、私たちは ZCA レジストリによって依存性を注入し、次にコードの中で検索を使用することで モックオブジェクトを見つけます。
  • スピード。 ZCA レジストリは、 Pyramid が使用する複雑な検索 シナリオの特定のセットに対しては非常に高速です。何年もの間この目的の ためだけに最適化されています。またこの目的のために、 ZCA レジストリには バグがない(あるいは非常に少ない)ことが実証済の、オプションの C コード が含まれています。
  • エコシステム。 ZCA レジストリを使っていることで、多くの既存の Zope パッケージをほとんど(あるいはまったく)変更することなく Pyramid の中で使用できます。

Conclusion

Pyramid を使用して、単に アプリケーションを開発 するだけなら、 ここで不満を言うことは多くありません。 ZCA レジストリ API を理解する 必要はまったくありません: 代わりに文書化された Pyramid API を 使用してください。しかし、もしかしたら軟弱であるという理由で API ドキュメントを読まないアプリケーション開発者がいるかもしれません。 その場合は代わりに生のソースコードを読むことになります。ドキュメント を読んでいないので、どの関数、クラス、メソッドが Pyramid API を 構成 するかは依然として知りません。そのせいで内部コードを使用する コードを書いたとしたら、 ZCA を使用する実装詳細と悪戦苦闘した末に自分自身を 概念の窮地に追い込むことになるでしょう。もしこれがあなたのことなら、 同情の余地はほとんどありません。私たちが ZCA レジストリを使用している 方法に精通するか、文書化された API だけを使用する必要があるでしょう; そのために、私たちは API を文書化しています。

Pyramid拡張 または 開発 する (新しいディレクティブを作る、 フックの使用 で説明されているよりもっと不明瞭ないくつかのフック を使う、 Pyramid 中核コードを変更する) 場合は、少なくともいくつ かの ZCA 概念を理解する必要性に直面するでしょう。いくつかの場所では、 それは臆面もなく使用され、今後もそれは変わらないでしょう。それが一種独特 であることは私たちも知っています。しかし同時に有用でもあり、時間を取って 少し読み込めば、基本的に理解することができます。

Pyramid “Encourages Use of ZCML”

ZCML は、 Pyramid がアプリケーション設定に使用している Zope Component Architecture レジストリを設定するために使用する ことができる設定言語です。よく、 Pyramid は「ZCML を必要としている」と 言われます。

そうではありません。 Pyramid 1.0 では、ZCML は中核コードの一部と しては含まれません; 代わりに pyramid_zcml アドオンパッケージの 一部として提供されており、それは完全にオプションです。 Pyramid を 使用するために ZCML や他の種類のフレームワーク的なアプリケーション設定 に対する宣言的なフロント・エンドは全く必要ありません。

Pyramid Does Traversal, And I Don’t Like Traversal

Pyramidtraversal とは、 URL パスをリソースツリーの中 の resource オブジェクトへと解決する行為です。この概念を快く思 わない人もいて、彼らはそれが間違っていると信じています。幸いなことに、 Pyramid を使用していて、リソースツリーの観点に基づいて アプリケーションをモデル化したくなければ、それを使用する必要は全くあり ません。代わりに、URL パスをビューへマップするために URL dispatch を使用してください。

トラバーサルが一方的に間違っていると信じる一部の人がいるという考えは 理解できます。それがほとんど常に間違っていると信じる人々は、すべての データをリレーショナルデータベースの中に持っています。リレーショナル データベースはその性質上階層的ではないので、ツリーのようにトラバース することができません。

しかしながら、トラバーサルが一方的に間違っていると考える人々は、多くの 永続化メカニズムが階層的 である ことを考慮に入れることを怠っています。 例として、ファイルシステム、 LDAP データベース、 ZODB (あるいは 別の種類のグラフ) データベース、 XML ドキュメント、 Python モジュールの ネームスペースなどがあります。フロントエンドを階層型データストアにグラフ としてモデル化することは多くの場合に便利で、ビューをオブジェクトへ適用 するためにトラバーサルが使用されます。そのようなオブジェクトは、 トラバースされているツリーのリソースであるか (ZODB の場合のように) 、 あるいは少なくともいくつかのものがそれらの代わりを務めるものであるか (ファイルシステムから読み出されたファイルに対するラッパーの場合のように) のいずれかです。

さらに、多くのウェブサイトの構造は性質上、たとえそれを駆動するためのデー タが階層的でなかったとしても階層的です。例えば、新聞社のウェブサイトは 多くの場合非常に階層的です: セクションの中にセクションがあり、その中に またセクションがあるという風に無制限に続きます。この構造を URL が示すよ うにしようとして、かつ構造が不定 (入れ子のセクションの数がある定数の代 わりに “N” になる可能性がある) であれば、仮にバックエンドがリレーショナ ルデータベースであっても、リソースツリーはこれをモデル化する優れた方法 です。この状況では、リソースツリーは単なるサイト構造です。

また、トラバーサルは URL マッチングの固定された順番に依存しないので、 URL ディスパッチよりアプリケーションの合成可能性が高まります。 URL パター ンマッチングの正しい順番を維持することと比較すると、ビューのリソースへ のマッピングの周辺に1セットの異種の機能性をより予想可能な形で構成する (そして後でそれを増やす)ことができます。

しかし、このポイントは究極的には議論の余地があります。トラバーサルを使用 したくなければ、使用する必要はありません。代わりに URL ディスパッチを 使用してください。

Pyramid Does URL Dispatch, And I Don’t Like URL Dispatch

Pyramid では、 url dispatch とは 1セットの順序付けられた ルーティング定義に対してパターンマッチングを行なうことで URL パスをビュー callable に解決する行為です。ルーティング定義は順番に検査されます: 一致 する最初のパターンが URL をビュー callable に関連付けるために使用されま す。

一部の人々はこの概念で不快で、それが間違っていると信じています。彼らは通常 Zope に深く没頭している人々です。 Zope は traversal 以外にコードを URL にマップするメカニズムを提供していません。これは主として Zope が事実上 ZODB の使用を要求するからです。 ZODB は階層的オブジェクト ストアです。 Zope はリレーショナルデータベースもサポートしていますが、 典型的には、そのデータベースに対して問い合わを行うコードは ZODB オブジェクト グラフのどこかに存在しています (あるいは、少なくともそれはオブジェクト グラフ中のノードと関係する view です)。また、このコードにたどり 着くためにトラバーサルが必要とされます。

あなたがこれと同様にトラバーサルを使用したかったとしても、私は究極的に は URLディスパッチが有用であると主張しましょう。実際に Pyramid の中で URL ディスパッチとトラバーサルを 組み合わせる ことができます (Combining Traversal and URL Dispatch を参照)。そのような使用法の一例: オブジェクト グラフ上に Zope 2 の “Zope Management Interface” UI のようなもの (あるいは任意の管理インターフェース) をエミュレートしたければ、 config.add_route('manage', '/manage/*traverse') のようにルーティング を登録し、次に view 設定で route_name 引数を使用することにより (例えば config.add_view('.some.callable', context=".some.Resource", route_name='manage'))、 “management” ビューをコードに関連付けることが できます。この方法で物事を構成 (wire things up) して、その後で誰かが、 例えば /manage/ob1/ob2 に walk up to した場合、管理インタフェースが 表示されるでしょう。しかし /ob1/ob2 に walk up to to した場合は デフォルトオブジェクトビューが表示されるでしょう。あなたが利口なら (かつ、恐らくマゾヒストなら)、これらのハイブリッド設定に pull in することのできる他のトリックもあります。

さらに、もしあなたが URL ディスパッチ嫌いだったとして、いつの日かレガシー リレーショナルデータベース構造を使用しなければならないアプリケーションを 書くように依頼されたら、ビューと URL パスの間の1回限りの関連性のために URL ディスパッチを使用することが有用だと知るでしょう。オブジェクトグラフに 事実上ちょっとしたコードのためのエントリーポイントを表わすようなノードを 追加することは、時々まったく無意味なことです。その場合はルーティングを 使用すればそれで済みます。ルーティングが一致すれば、関連付けられたビューが 呼ばれます; 一致しない場合 Pyramid はトラバーサルの使用に切り替え ます。

しかし、このポイントは究極的には議論の余地があります。 Pyramid を使用し、あなたが本当に URL ディスパッチを使用したくなければ、使用する 必要は全くありません。代わりに、ちょうど Zope の中で行うように、URL パスをビューへとマッピングするためにトラバーサルを排他的に使用してください。

Pyramid Views Do Not Accept Arbitrary Keyword Arguments

多くのウェブフレームワーク (Zope, TurboGears, Pylons 1.X, Django) では、 view callable の変種が任意のキーワード引数または位置引数を 受け取ることができるようになっています。それらは request.POSTrequest.GET 辞書に含まれる値や、ルートマッチ辞書に含まれる値によって 満たされます。例えば Django ビューは、 r'^polls/(?P<poll_id>\d+)/$ のようなビューと関連付けられた “urlconf” の中の情報と一致する位置引数を 受け取ります:

1
2
def aview(request, poll_id):
    return HttpResponse(poll_id)

Zope も同様に、トラバーサルによって見つかったリソースオブジェクトの任意 のメソッドに任意のキーワード引数および位置引数を加えることができます:

1
2
3
4
5
from persistent import Persistent

class MyZopeObject(Persistent):
     def aview(self, a, b, c=None):
         return '%s %s %c' % (a, b, c)

このメソッドが published callable であることの結果として呼び出される場合、 リクエストの中で位置引数およびキーワード引数の名前と一致するキーを求めて Zope リクエストオブジェクトの GET および POST 名前空間が探索されます。 そして、メソッドは (可能なら)そこに mention された値で満たされた引数 リストで呼ばれます。 TurboGears および Pylons 1.X も同様に作動します。

初期状態 (out of the box; 箱から出した状態) では、 Pyramid は これらの特徴のどれも持たないように構成されます。デフォルトで、 pyramid ビュー callable は常に request だけを受け取り、他の 引数はありません。論理的根拠: この引数特定のマッチングを積極的に使うと 高コストになり得ます。また、 Pyramid の主なゴールの1つとして パフォーマンスがあります。したがって私たちは、ビューの引数リストに unpack するためにマジックを提供する代わりに、デフォルトではビュー callbale の本体内で request オブジェクトに問い合わせることにより情報を 得てもらうことに決めました。

しかしながら、 Pyramid 1.0a9 からは、ユーザコードが期待される ビュー callable の呼ばれ方に影響を及ぼすことができるようになりました。 これにより、任意の引数で呼ばれるビュー callable からシステムを構成する ことが可能になります。 ビューマッパーの変更 を参照してください。

Pyramid Provides Too Few “Rails”

設計上 Pyramid は特に主張の強いウェブフレームワークではありません。 それは比較的倹約的な (parsimonious) 特徴セットを持っています。組み込み の ORM を含んでおらず、特定のデータベースバインディングも持っていません。 フォーム生成フレームワークが含まれていません。管理用のウェブユーザインター フェースがありません。組み込みのテキストインデックシングを持っていません。 Pyramid は、どのようにコードを構成するか指図しません。

そのような主張の強い機能は、 Pyramid の上に 構築された アプリケーションおよびフレームワークの中に存在します。基礎として Pyramid を使用して構築された、より高レベルのシステムが出現すること が意図されます。 Pyramid Applications are Extensible; I Don’t Believe In Application Extensibility を見てください。

Pyramid Provides Too Many “Rails”

Pyramid は、他のウェブフレームワークにはないいくつかの特徴を提供 します。これらの特徴は、単純な特注の (bespoke) ウェブアプリケーションを 構築しているなら意味を成さないかもしれないユースケースのために意図され た特徴です:

  • resource tree の walk を意味する traversal を使用して URL をコードにマッピングするオプションの方法。
  • 複数の独立した I18N translation string ファクトリ。各々の ファクトリは自分自身のドメインを名乗ることができます。

これらの特徴は Pyramid の作者たちにとって重要です。 Pyramid の作者たちは、しばしば CMS スタイルのアプリケーションを 構築するために委任されます。そのようなアプリケーションは複数のデプロイ があるので、多くの場合フレームワーク的です。個々のデプロイはそれぞれ、 サブアプリケーションのわずかに異なる構成を要求します。また、フレームワーク およびサブアプリケーションはしばしば 拡張可能 である必要があります。 アプリケーションに複数のデプロイがあるので、プラグ可能性と伸長性は重要です。 アプリケーション (デプロイあたり1つ) のマルチフォークの維持は非常に 望ましくない (undesirable) ので。 traversal を使用するシステム を外部から拡張する方が、 URL dispatch を使用するシステムで 同じことをするより簡単なので、個々のデプロイはドメインモデルオブジェクト の永続的なツリーから構成される resource tree を使用し、 view callable コードをツリー中のリソースにマッピングするために traversal を使用します。リソースが異なるユーザ集合によって所有 されアクセス可能なので、リソースツリーは非常に粒度の細かいセキュリティ 宣言を含んでいます。インタフェースはユニットテストおよび実装交換可能性 をより簡単にするために使用されます。

特注のウェブアプリケーションでは、通常単一の正統なデプロイがあります。 そしてしたがって、多数のコードフォークの可能性はありません。拡張可能性 は必要ではありません; コードは単に in-place で変更されます。セキュリティ 要件は多くの場合それほど細かい粒度ではありません。上記にリストされた特徴 を使用することは、多くの場合そのようなアプリケーションには行き過ぎ (overkill) でしょう。

あなたがこれらの特徴を好きでない場合、それは Pyramid を使用でき ない、また使用すべきでないことを意味しません。それらはすべてオプションで、 それらのことを前もって知っている必要がないことを確かめるために多くの時間が 費やされました。上記の特徴を無視することにより、 Pyramid を使用 して純粋に特注の “Pylons 1.X スタイル” のアプリケーションを構築すること ができます。特注のウェブアプリケーションを構築した後で急に有名になり、 複数の場所にデプロイしなければならないため拡張性が必要になってから、 これらの特徴が便利なことに気がつくかもしれません。

Pyramid Is Too Big

Pyramid の圧縮した tar 玉はほぼ 2MB だ。きっと巨大に違いない!」

いいえ。それにはテストコードとヘルパーのテンプレートが同梱されています。 ここで、パッケージツリーのサブディレクトリに含まれている内容物の内訳を 示します:

docs/

3.0MB

pyramid/tests/

1.1MB

pyramid/paster_templates/

804KB

pyramid/ (pyramd/testspyramid/paster_templates を除いて)

539K

実際の Pyramid ランタイムコードは、ドキュメントとパッケージ生成 に使用されるヘルパーテンプレート、およびテストコードを除いて tar 玉の 合計サイズの約 10% です。パッケージに含まれる約 19,000 行の Python コード の中で、通常の動作中に実際に実行される可能性のあるものは、テストと paster テンプレート Python ファイルを除いて Python コードのおよそ 5,000 行 を占めます。これは Pylons 1.X と comparable です。それには、テストを除いて 2,000 行を少し超える Python コードが含まれています。

Pyramid Has Too Many Dependencies

それは真実です。これを書いている時点 (Pyramid 1.3) で、 Pyramid が推移的に依存する Python パッケージ配布物の総数は、 Python 3.2 あるいは Python 2.7 を使用していれば 10 です。 Python 2.6 を使用していれば Pyramid は 12 のパッケージ配布物を取得します。これは 0 よりはるかに多い パッケージ依存性です: 様々な Python のマイクロフレームワークや Django が誇るメトリック。

しかし、 Pyramid 1.2 は Python 2.7 で 15 のパッケージに、Python 2.6 で 17 パッケージに依存していました。したがって、現時点で進歩しています。 Pyramid 1.3 で完了した Python 3 への移植は、よりよいパッケージング上の 決断を強いることによって、かなりの数の依存性を削ぎ落とすことを助けました。

将来的に、さらにテンプレートシステムへの依存性をコアから外部に移動させて、 アドオンパッケージにしようとしています。これによって、インクルードするか どうかをフレームワークではなく開発者が決められるようになります。 これは、コアの依存性の数を約 5 まで減らすでしょう。残されたのは 5 つの コア依存性だけです。

Pyramid “Cheats” To Obtain Speed

Pyramid はパフォーマンスを稼ぐために cheat (ずる) をしているという 不満を他のウェブフレームワークの作者から何度も聞きました。批判された cheat 的なメカニズムの 1 つは、高速な検索を行うために zope.interface によって提供される C 拡張を (推移的に) 使用していることです。批判された 別の cheat 的メカニズムは、外部 (extraneous) 関数呼び出しの宗教的な回避です。

より良いパフォーマンスを得るために cheat 的な方法がある場合、私たちは できるだけそれを利用しようとします。私たちは積極的に Pyramid を 最適化しています。これにはコストが伴います: 中核コードの中にはより可読性に 優れた表現ができた箇所があります。改善として、私たちはこれらの箇所に大量の コメントを書きました。

Pyramid Gets Its Terminology Wrong (“MVC”)

「私は MVC ウェブフレームワークのユーザですが、混乱しています。 Pyramid はコントローラーをビューと呼んでいます! そして コントローラーがありません。」

このキャンプにいる人は、既存の「MVC」フレームワークがどのようにその用語 を使用するかに関する話を期待して来たのかもしれません。例えば、モデルとは ORM モデルのこと、コントローラーは URL へのマッピングを持つクラスのこと、 ビューはテンプレートのこと、と恐らく期待します。 Pyramid は確かに これらの概念の各々を持っています。また、各々は恐らくあなたの既存の 「MVC」ウェブフレームワークとほとんど同じように 動きます 。私たちは 単純に MVC 用語を使用しません。なぜなら、ウェブフレームワーク空間に おける使用法を歴史上の現実性と調和させることができないからです。

人々は、ウェブアプリケーションに一般的なデスクトップ GUI プラットフォームと 同じ用語を使用することで同じ属性を与えて、一般的なウェブフレームワーク中で 様々なコンポーネントがどのように結合するかに関してある種の評価基準を提供する ことを強く望みます。しかし、著者の見解では「MVC」は一般にウェブとあまり 適合しません。 ウィキペディアの Model-View-Controller に観する記事 から引用すると:

MVC には様々に異なる派生形がありますが、制御フローは一般に以下の通りです:

  ユーザは、何らかの方法 (例えばマウスボタンを押す) でユーザインタ
  フェースと対話します。

  コントローラーは、ユーザインタフェースからの入力イベントを扱います。
  しばしば登録済のハンドラやコールバックによってイベントを受け取り、
  そのイベントを適切な (モデルに理解できる) ユーザアクションに変換します。

  コントローラーは、ユーザアクションをモデルに通知し、それは恐らく
  モデルの状態の変化を引き起こすでしょう。 (例えば、コントローラーは
  ユーザのショッピングカートを更新します) [5]

  ビューは適切なユーザインタフェースを生成するためにモデルにクエリを
  行います (例えば、ビューはショッピングカートの内容をリストします)。
  ビューがモデルからそれ自身のデータを得ることに注意してください。

  コントローラーは、 (いくつかの実装で) ビューに対してそれ自体を描画
  させるためにより一般的な指示を出すことがあります。他の実装では、
  ビューは画面更新を要求するモデルの状態変化 (オブザーバー) によって
  自動的に通知されます。

  ユーザインタフェースはさらにユーザとのインタラクションを待ち、
  サイクルを再開します。

著者からすると、このウィキペディアの定義は、まるで誰かが現在のウェブ フレームワークにおける用語「MVC」の使用を説明するために編集したかのようで、 可能な最も一般的な用語を使った回りくどい表現による概念のように見えます。 私は、そのような広い定義が MVC パターンのオリジナルの著者によって常に 同意されるかどうか疑わしく思います。しかし そうであっても 、ほとんどの MVC ウェブフレームワークはこの疑わしい一般的な定義さえ満たさないように 見えます。

例えば、「ビューがモデルからそれ自身のデータを得ることに注意してください」 と主張されるように、テンプレート (ビュー) は常にモデルに対して直接クエリー を行うでしょうか? 恐らく、そうではありません。どちらかといえば「コントロー ラー」がこれを行います。それは「ビュー」 (テンプレート) がより簡単に扱える ようにデータを加工 (massaging) します。「コントローラー」が JSON を返す 場合、何をしますか? コントローラーは JSON を生成するためにテンプレートを 使用しますか? そうでなければ、そのとき「ビュー」は何でしょうか? 多くの MVC スタイルの GUI ウェブフレームワークは、ビューがモデルの変更を検知する ために hook up されたなんらかのイベントシステムを持っています。現在の形式 のウェブには、そのような機能はまったくありません:それは事実上 pull-only です。

したがって、現実性を踏まえて誤りを犯さないことへの関心から、そして四角 い杭であるウェブを丸い穴である「MVC」の中へ押し込もうとする代わりに、 私たちはちょうど 2 つのものがあると言いましょう (punt and say): リソースとビューです。リソースツリーはサイト構造を表わします。ビューは リソースを示します。テンプレートは実際には単に任意のビューの実装詳細です: ビューは、レスポンスを返すためにテンプレートを必要としません。「コント ローラー」は、ありません: それはまったく存在していません。「モデル」は、 リソースツリー、またはフレームワークと完全に分離した「ドメインモデル」 (SQLAlchemy モデルのような) によって表わされます。現在のウェブの制約の もとでは、これはより合理的な用語のように見えます。

Pyramid Applications are Extensible; I Don’t Believe In Application Extensibility

ある制約に従って書かれた Pyramid アプリケーションはすべて 拡張 可能 です。この特徴は Pyramid ドキュメンテーションの 既存の Pyramid アプリケーションの拡張高度な設定 の章で議論されています。 それは Pyramid 内部を含めて Zope Component Architecture を 使用することにより可能になります。

この文脈における「拡張可能」の意味とは:

  • アプリケーションの振る舞いを、アプリケーションの特定の デプロイ の 中で、オリジナルのアプリケーションのソースを修正せずにオーバーライド または拡張できます。
  • 基礎的なアプリケーションの振る舞いがオーバーライドまたは拡張可能に なるように、アプリケーション作成時にオリジナルの開発者が拡張のための プラグポイントを用意する必要がありません。
  • オリジナルの開発者は、オプションでアプリケーション特有のプラグポイント のセットを用意することを選ぶかもしれません。それはデプロイ担当者によって フックされるかもしれません。デプロイ担当者が ZCA によって提供される機 能を使用することに決めれば、オリジナルの開発者はそのようなプラグポイントを 導入するメカニズムに関してあまり深く考える必要はありません。

多くの開発者が、拡張可能なアプリケーションの作成に価値がないと信じてい るようです。彼らは、その代わりに、各デプロイに対して振る舞いをオーバー ライドするために与えられたアプリケーションのソースを修正することがより 合理的であると提言します。その後で、バージョン管理におけるブランチや マージに関する多くの議論が典型的に続きます。

すべてのアプリケーションを拡張可能にする必要がないことは明らかです。大 多数のウェブアプリケーションは単一のデプロイだけを持っており、したがっ て拡張可能である必要はまったくありません。しかしながら、いくつかのウェ ブアプリケーションには多数のデプロイがあります。また、いくつかには 多 くの デプロイがあります。例えば、汎用的なコンテンツ管理システム(CMS) は、特定のデプロイのために拡張される必要のある基本機能を持っているかも しれません。その CMS システムは多くの場所に多くの組織のために展開するか もしれません。この CMS の若干のデプロイは、第三者によって中心的に展開し、 グループとして管理されるかもしれません。上流のソースと同期してシステム の各ソフトウェアブランチを絶えず維持することに比べて、事前に用意された プラグポイントによってそのようなシステムをデプロイ毎に拡張できることは 有用です: 上流の開発者は、同じコードベースに対するあなたの変更と矛盾す るような厄介で些細な変更をコードに加えるかもしれません。デプロイの一生 の間そのような変更を繰り返しマージすることは困難で、時間を浪費します。 また、それほど侵略的でない方法で特定のデプロイのためにアプリケーション を修正できることは、多くの場合に有用です。

Pyramid アプリケーションの拡張性に関して一切考えたくなければ、 その必要はありません。拡張性を完全に無視することができます。しかしながら、 既存の Pyramid アプリケーションの拡張 に定義された規則のセットに従えば、アプリケーション を拡張可能に 作る 必要はありません: フレームワークの中で書くどんな アプリケーションも、基礎的なレベルで自動的に拡張可能に なります 。 アプリケーションを拡張するためにデプロイ担当者が使用するメカニズムは 必然的に粗く (coarse) なります: 典型的には、ビュー、ルートおよびリソース はオーバーライドできるでしょう。ほとんどの小さな (そしていくつかの主要な) カスタマイズに関して、多くの場合にこれらは必要な唯一のオーバーライド用 のプラグポイントです: デプロイが要求する動作を完全にはアプリケーション が行わない場合、デプロイ担当者は多くの場合ビュー、ルートあるいはリソース をオーバーライドし、それに行ってほしいことを オリジナルの開発者によって 必ずしも予想されない方法で 素早く行うことができます。ここで、そのような 特徴の利点を示すいくつかの例シナリオを挙げます:

  • あるデプロイが異なるスタイルを必要とする場合、 主要なテンプレートと CSS を、個別の Python パッケージの中でオーバーライドすることができます。
  • あるデプロイが、より多くのあるいは異なる情報を露出するために異なる アプリケーションページを必要とする場合、そのページをレンダリングする ビューを個別の Python パッケージの中でオーバーライドできます。
  • デプロイが追加機能を必要とする場合、オーバーライドパッケージにビュー を加えることができます。

このようなタイプの修正は、上流パッケージの基本デザインが変わらない限り、 改訂の必要なしに上流パッケージの多くのリリースを越えてしばしば残り 続けます。

アプリケーションを外部から拡張することは万能薬でなく、ブランチとマージ に似たいくつかの危険をもたらします: 時々、上流での大きな変化によって あなたの修正のいくつかを見直し更新する必要があります。しかし、通常は 無意味な textual なマージコンフリクトに対処する必要がないでしょう。 そのような上流パッケージに対する重要でない変更は、上流パッケージが 更新される時にはしばしば起こりますが、アプリケーションを外部から拡張 する場合、 textual なマージは決して発生しないからです。あなたの修正も、 for whatever its worth 、一箇所の、正統で、十分に定義された場所に 含まれるでしょう。

新しい機能やバグフィックスを得るためにアプリケーションをブランチさせて 絶えずマージすることは、明らかに有用です。 Pyramid アプリケーショ ンでも、他のアプリケーションの場合と同じやり方ができて、同じくらい有用 です。しかし、 Pyramid で書かれたアプリケーションのデプロイの 場合は、アプリケーションが事前にプラグポイントを定義していなくても、 この必要を回避することが可能です。競合するウェブフレームワークの推進者 がブランチとマージを支持して、この特徴を見過ごすことはありえます。 なぜなら、彼らの選択したフレームワークで書かれたアプリケーションが 箱から出した状態では同じような基本的な方法で拡張可能ではないからです。

特定の拡張性を気にしながらアプリケーションを書かなくても Pyramid アプリケーションは基本的に拡張可能ですが、あなたが適度に冒険的ならば、 さらに、それを1ステップ進めることができます。 Zope Component Architecture に関してもっと学習すれば、アプリケーションを開発する際に、 よりドメインに特化した設定プラグポイントを露出するために任意にそれを 使用することができます。露出されるプラグポイントは、 Pyramid 自体によって自動的に提供されるものよりは粒度が粗い (corse) 必要がありません。 例えば、あらかじめ決まった目的のために一揃いのビューを形成するための 独自ディレクティブを構成して (例えば restview やその類)、他の人々が カスタマイズパッケージの includeme の中で宣言をする際にこの ディレクティブを参照することを許すことができます。このためのコストがあります: デプロイ担当者のためのカスタムプラグポイントを定義するアプリケーション 開発者は、 ZCA を理解する必要があるでしょう。さもなくば、自分自身で同様 の拡張性システムを開発する必要があるでしょう。

究極的には、 Pyramid によってアプリケーションに与えられた拡張性 の特徴がよいか悪いかに関するどんな議論もほとんど無意味です。特定の Pyramid アプリケーションによって提供される拡張性の特徴を、 そのデプロイの特定のセットのための修正に影響を与えるために利用する必要は ありません。アプリケーションの拡張性プラグポイントを完全に無視し、 代わりに、あたかも他のウェブフレームワークも使用して書かれた アプリケーションをデプロイしているように、アプリケーションのデプロイ用 の修正を管理するためにバージョン管理のブランチとマージを使用することが できます。

Zope 3 Enforces “TTW” Authorization Checks By Default; Pyramid Does Not

Challenge

Pyramidview の実行時にのみ自動的な認可チェックを行ないます。 Zope 3 はコンテキストオブジェクトを security proxy でラップして、 属性アクセス中にさらにセキュリティ検査を行います。 私はこれが好きです。なぜなら:

  1. セキュリティプロキシメカニズムを使用する場合、特定の HTML 要素 (form フィールドのような) を条件付きで表示したり、コンテキスト オブジェクトに関してアクセスしたユーザが所有する許可に依存して ある属性が修正されるのを防ぐビューを持つことができます。
  1. さらに、私は Twisted Web を使用して、REST API によってリソースを露出 したい。もし Pyramid が Zope3 のセキュリティプロキシによって属性アク セスに基づいた認可を行なえば、 Pyramid と Twisted ベースのシ ステムの両方で認可ポリシーを同じ方法で実施することができます。

Defense

Pyramid は Zope 2 に精通している人々によって開発されました。 Zope 2 には「through the web」セキュリティモデルがあります。この TTW セキュリティモデルは Zope 3 のセキュリティプロキシの前身でした。 時間が経つにつれて、 Pyramid 開発者 (Zope 2で働いている) がその ようなサイトを作るうちに、私たちは少数のプロジェクトでコード解釈の間の 認可チェックが非常に有用だと分かりました。しかし大抵の場合、元々 delegation の必要がなかったプロジェクトでは TTW 認可チェックによって開発速度が通常 遅くなりました。特に、信頼されていないユーザが私たちのアプリケーション によって実行される任意の Python コードを書くことを認めていなかったなら、 ウェブセキュリティ検査を通じた負担が、正当化するにはあまりにもコストが かかりすぎるということが証明されました。私たちは (集団的に) ここ何年も の間 untrusted な開発者がその上でコードを書くことを認められているアプリ ケーションを書いていません。したがって、新しいウェブフレームワーク中で、 デフォルトでこのモデルを落とすことは筋が通っているように思えました。

また、私たちはすべてのウェブアプリケーションに同じツールキットを使用す る傾向があるので、2つの異なるウェブフレームワークの下で同じ制限実行コード が使用できることは、実際の関心事にはなりません (it’s just never been a concern)。

デフォルトでセキュリティプロキシを無効にすることの正当性にもかかわらず、 Zope 3 セキュリティプロキシが生来ウイルス的であることを踏まえて、 それを使用する唯一の要件は、単一のオブジェクトをセキュリティプロキシで ラップして、プロキシセキュリティ検査が起こることを望む時に通常そのオブ ジェクトにアクセスが起こるのが確実であることです。既存のアプリケーション 用の Pyramid トラバーサーをオーバーライドすることは可能です (トラバーサーの変更 を参照)。 Zope 3 のような振る舞いを得る ために、トラバースされたそれぞれのオブジェクト (contextroot を含む) に対して Zope 3 セキュリティプロキシのラップ オブジェクトを返す異なるトラバーサーをプラグインすることは可能です。 これは、多くの努力なしで、より Zope 3 に類似した環境を作成する効果が あるでしょう。

Pyramid Uses its Own HTTP Exception Class Hierarchy Rather Than webob.exc

Note

This defense is new as of Pyramid 1.1.

pyramid.httpexceptions に定義された HTTP 例外クラスは、 webob.exc に定義されたものに非常に似ています (例えば HTTPForbidden など)。それらは同じ名前 を持ち、大部分は同じ振る舞いをします。そして実装も非常に類似しています。 しかし、まったく同一ではありません。ここに、それらが webob から独立して いる理由を示します:

  • それらを独立にすることで、 HTTP 例外クラスが pyramid.response.Response をサブクラス化することを可能にします。 Pyramid におけるルーティングの動作方法により、これによってわずかに レスポンス生成の速度が向上します。 webob.response.Response をモンキー パッチすることにより同じ速度向上が得られるかもしれませんが、通常それは モンキーパッチが有害で間違っているということが判明するケースです。
  • それらを独立にすることで、さらにレスポンス生成を高速化する __call__ の代替ロジックを提供することを可能にします。
  • それらを独立にすることで、例外クラスが RequestClass (pyramid.request.Request) の適切な値を提供することを可能にします。
  • それらを独立にすることで、 webob.exc に含まれる Python 2.4 で動作 させるための後方互換性コードについて考えることから解放されます。 Pyramid 1.1+ で Python 2.4 はすでにサポート対象外です。
  • 私たちは、モジュールの中で 2 つのクラス (HTTPNotFoundHTTPForbidden) の振る舞いを変更して、 Pyramid 内部でそれらを notfound および forbidden 例外に使用できるよう にします。
  • それらを独立にすることで、例外クラスの docstring に対して Pyramid 特有のドキュメンテーションを提供するように影響を及ぼすことを可能にします。
  • それらを独立にすることで、レスポンスオブジェクトが例外として使用され た場合に Python 2.6 で無意味な deprecation 警告が出るのを沈黙させる ことができます (self.message に関連)

Pyramid has Simpler Traversal Machinery than Does Zope

Zop のデフォルトトラバーサーは:

  • トラバースの間に、開発者がトラバース名前スタックを変化させることが できます (パス要素を加えたり削除したりすることができます)。
  • 現在トラバースされているオブジェクトからパス中の次の要素を得るために adaptation を使用して、さらに __bobo_traverse__, __getitem__, そして最終的に __getattr__ にフォールバックします。

Zope のデフォルトトラバーサーは、 REQUEST['TraversalNameStack'] を 変化させることによりトラバーサル中に開発者がトラバーサル名前スタックを 変化させることを可能にします。 Pyramid のデフォルトトラバーサー (pyramid.traversal.ResourceTreeTraverser)は、これを行う方法を提供し ません; それはリクエスト属性としてスタックを維持しません。また、仮にそ うだったとしても、それはトラバースしている間リソースオブジェクトにリク エストを渡しません。この特徴は、時々手軽である一方で、 Zope の上に構築 されたフレームワーク (CMF や Ploneのような) の中で乱用され、トラバーサル がビューと一致しなかった時に何が起こっていたか正確に伝えることをしばしば 困難にしました。私は、デフォルトトラバーサーに特別のハニーポットを構築 するのではなく、この特徴を望む人々にトラバーサーを交換させる方が良いと 感じました。

Zope は、名前に基づいてリソースツリーの次の要素を得るために多数のメカニズム を使用します。それは、最初に ITraversable に対して現在のリソースの adaptation を試みます。それが失敗する場合、リソース上のいくつかのマジック メソッド (__bobo_traverse__, __getitem__, __getattr__) の 試行にフォールバックします。 Zope を使い repoze.zope2 の publisher を再実装している間に得た経験によって、私は下記のことを信じるように なりました:

  • デフォルト トラバーサーは、できるだけ単純であるべきです。 Zope の publisher は、1つのトラバース方法が失敗した時に別の方法にフォールバッ クするので、追いかけたり再現したりするのがやや困難です。さらに、それ は遅いです。
  • トラバーサル機構の単なる要素ではなく、 トラバーサー全体 が代替可能で あるべきです。 Pyramid には、大量の小さなコンポーネントではなく、少数の 大きなコンポーネントがあります。トラバーサー全体が代替可能な場合、それ はデフォルトトラバーサーの一部を代替可能にすることのアンチパターンです。 そのようにすることは「取っ手の上に取っ手」パターンです。それは不運にも Zope において少々固有の症状です。「取っ手の上に取っ手」パターンでは、 大きなコンポーネントを切り替えるために使用されるのと同じメカニズムを 用いて、大きなコンポーネントの代替可能なサブコンポーネントが設定可能に なります。例えば Zopeでは adapter を登録してデフォルトトラバーサーを切り 替えることができます。しかし、さらに (あるいは別の方法として) 複数の adapter を登録することで、デフォルトトラバーサーがどのようにトラバース するかをコントロールすることもできます。大きなコンポーネントを完全に交 換することも、大きなコンポーネントのデフォルト実装の取っ手を回す (=機能 を調整する) ことも、どちらもできる結果として、いつ (あるいはどんな時に) 大きなコンポーネントを完全にオーバーライドしなければならないかを誰も 理解できませんでした。これにより、人々が単に機能を調整するために デフォルトコンポーネントを利用することに依存するようになってくるため、 時間とともに大きな「代替可能な」コンポーネントとフレームワークそれ自身 は共に錆びていきました。デフォルトコンポーネントは事実上フレームワーク の一部になります。それは、デフォルトコンポーネントを代替可能にするという ゴールを完全に破壊します。 Pyramid の中では、典型的に、コンポーネントが 代替可能ならば、そのコンポーネントは調整部分を持たない (solid state) で しょう。そのコンポーネントによってコントロールされる振る舞いに影響を 及ぼしたければ、コンポーネントに用意された機能調整用の取っ手を回す代わり に、コンポーネントを交換することになるでしょう。

Microframeworks Have Smaller Hello World Programs

「マイクロフレームワーク」を自称しているフレームワークは存在します: BottleFlask の 2 つはポピュラーになっています。 Bobo はマイクロフレームワークを自称して いませんが、その意図するユーザーベースはほとんど同じです。他にも多くの ものが存在します。実際、私たちはすでに (何らかの公式プロジェクトとしてで はなく、単に教育ツールとしてですが) Pyramid を使用してマイクロフレーム ワークを作成しています (ビデオでは Pyramid の前身である BFG を使用していますが、生じるコードは ピラミッドでも利用可能です) マイクロフレームワークとは、ある共通の特徴を備えた小さなフレームワーク のことです: それぞれのマイクロフレームワークは、ユーザが単一の Python ファイル内で完全に機能するアプリケーションを作成することを可能にします。

何人かの開発者やマイクロフレームワークの作者は、 Pyramid の “hello world” 単一ファイルプログラムが、彼らの好きなマイクロフレームワークで 書かれた等価なプログラムより (約5行以上) 長いと指摘します。 確かにその通りです (Guilty as charged)。

このロスは、努力が足りなかったからではありません。 Pyramid はマイクロ フレームワークが優勢と主張するのと同じ状況で役に立ちます: 単一ファイル アプリケーション。しかし Pyramid は、現在のマイクロフレームワークに匹敵 する hello-world LoC (Line of Code = 行数) を達成するために、より大きな アプリケーションを確実にサポートする能力を犠牲にしません。 Pyramid の 設計は、代わりに、素朴な宣言的な設定スキームに関連した、いくつかの共通 の落とし穴を回避しようとします。続くサブセクションでは、論理的根拠に ついて説明します。

Application Programmers Don’t Control The Module-Scope Codepath (Import-Time Side-Effects Are Evil)

以下のような Python ファイルを含むディレクトリ構造を想像してください:

.
|-- app.py
|-- app2.py
`-- config.py

app.py の内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from config import decorator
from config import L
import pprint

@decorator
def foo():
    pass

if __name__ == '__main__':
    import app2
    pprint.pprint(L)

app2.py の内容:

1
2
3
4
5
import app

@app.decorator
def bar():
    pass

config.py の内容:

1
2
3
4
5
  L = []

  def decorator(func):
      L.append(func)
      return func

これらのファイルを保持するディレクトリに cd して、上記のディレクトリ構造 およびコードを前提として python app.py を実行すると何が起こるでしょうか? decorator デコレータは 2 回使用されると推測されます。 app.py の中でデコレートされた関数 foo で 1回、 app2.py の中でデコレートされた関数 bar で 1回です。 デコレータが使用される都度 config.py の中のリスト L は append されるので、2要素を含むリストが表示されることを期待しますよね。 悲しいことに違います:

[chrism@thinko]$ python app.py
[<function foo at 0x7f4ea41ab1b8>,
 <function foo at 0x7f4ea41ab230>,
 <function bar at 0x7f4ea41ab2a8>]

目視による検査では、その結果 (リスト中の3つの異なる関数) はありえない ように思えます。定義した関数は 2つだけで、それぞれの関数を一度ずつ デコレートしました。したがって、私たちは decorator デコレータが 2回しか走らないと確信します。しかしながら、その確信は間違っています。 app.py モジュール中でモジュールスコープのコードが 2度実行されている からです。コードは、スクリプトが起動されるとき (Python app.py による) に __main__ として一度実行され、次に app2.py がその同じファイル をインポートする時に app として再び実行されます。

これはマイクロフレームワークとの比較とどんな関係があるのでしょうか。 現在の多くのマイクロフレームワーク (例えば Bottle や Flask) は、 モジュールスコープで定義されたオブジェクトに設定デコレータを取り付ける ことを奨励しています。これらのデコレータは、外部の Python モジュールに 定義されたグローバル変数であるシングルトンレジストリを populate する 任意の複雑な登録コードを実行します。これは上記の例と類似しています: 上記の例における「グローバルレジストリ」はリスト L です。

Groundhog マイクロフレームワーク で同じパターンを使用する場合に何が起こるか確かめましょう。上記 app.py の内容をこれに置き替えてください:

1
2
3
4
5
6
7
8
9
from config import gh

@gh.route('/foo/')
def foo():
    return 'foo'

if __name__ == '__main__':
    import app2
    pprint.pprint(L)

上記 app2.py の内容をこれに置き替えてください:

1
2
3
4
5
import app

@app.gh.route('/bar/')
def bar():
    'return bar'

上記 config.py の内容をこれに置き替えてください:

1
2
from groundhog import Groundhog
gh = Groundhog('myapp', 'seekrit')

“gh” Groundhog アプリケーションのルーティングテーブル内にいくつのルーティング 設定が登録されるでしょうか? 3と答えたら正解です。カジュアルコードリーダー (また任意の分別ある開発者) は、いくつ登録されていることを期待するでしょうか? 2と答えたら正解です。二重登録は問題になるでしょうか。このアプリケーションを 裏で支える私たちの Groundhog フレームワークの route メソッドでは、あまり 問題になりません。ルーティングが一致しない場合に1つのルーティングに対して2度 失敗する必要があるので、それはほんの少しアプリケーションを遅くするでしょう。 では、他のフレームワーク、他のアプリケーションあるいは別のデコレータに 関して問題になるでしょうか。誰にも分かりません。不慮のコード二重実行の インパクトが何かを予測できるようになるためには、そのアプリケーション全体、 そのフレームワーク全体、および実行の時間的な順序関係 (chronology) を理解 する必要があります。

外部レジストリの populate を行なうデコレータの使用を推奨することには、 意図しない結果があります: アプリケーション開発者は、今や Python モジュール スコープのコードを実行するすべてのコードパスの所有権を主張しなければ なりません。モジュールスコープのコードは、現在のデコレータに基づく マイクロフレームワークでは必ずただ一度だけ実行されると仮定されます; それが二度以上実行されれば、不思議なことが起こり始めるでしょう。この 不変式を維持することはアプリケーション開発者の責任です。しかしながら、 あいにくこれは実際には不可能なタスクです。なぜなら、 Python プログラマは モジュールスコープのコードパスを所有しておらず、また将来も決してそう ならない からです。それができるという考えをあなたに納得させようとする 人は誰も、単に誤解しています。あなたがコードのテストを実行するために 使おうとしたテストランナーは、しばしば奇妙な順番で任意のコードのインポート を行ない、上記で実証されたようなバグを見つけ出します。 API ドキュメンテーション生成ツールも同じことをします。さらに Python の reload コマンドを使用したり sys.modules からオブジェクトを削除 したりすることが安全だと考えている人さえいます。どちらも、インポート時の 副作用があるコードに対して使用された場合にとてもおかしな効果があります。

グローバルレジストリ書き換え型のマイクロフレームワークのプログラマは、 したがって、前節の中で行ったようにモジュールスコープコードが二度以上 実行された場合に何が起こる かもしれない のかに関して、ある時点で茶 柱占いを始める (start reading the tea leaves) 必要があるでしょう。 Python プログラマがモジュールスコープのコードパスを任意のコード (特に外部レジストリに populate するコード) を実行するために使用できる と考えて、この仮定が現実によって挑戦された場合、アプリケーション開発者 は必然的に不明瞭な徴候の根本的原因を見つけるために、しばしば苦痛に満ちた 綿密なデバッグプロセスを経験する必要があります。その解決策は、多くの場合 アプリケーションのインポート順を再整理するか、インポート文をモジュール スコープから関数本体に移動させることです。そうすることの論理的根拠は、 修正を達成したチェックインメッセージの中で十分に表現することができず、 その問題が再び起こらないように開発チームの他のメンバーのために十分簡潔に 文書化することもできません。それは再び起こるでしょう。特に、あなたが pdb を使用してモジュールスコープコードをステップ実行する間に学習した レッスンをまだ内面化していない他の人々とプロジェクトに取り組んでいるならば。 これは、アプリケーション開発者としては実際かなりひどい状況になっている ことに気がつきます: デコレータに基づくマイクロフレームワークによって 提示されるドキュメンテーションではそれに関して警告がないので、あなたもしくは その仕事のために契約したあなたのチームは、恐らく考えもしなかったでしょう。

外部のデータ構造に populate するデコレータに基づく早期の (eager) 設定に 多大な投資をしている人々 (マイクロフレームワークの作者のような) は、私が 上記で概説した状況は変則的で不自然だと主張するかもしれません。 彼らは、それがまったく起こらないと主張するでしょう。アプリケーションを 2〜3 個のモジュールを越えて成長させるつもりでなければ、それは恐らく真実 です。しかしながら、コードベースが成長し、多数のモジュールに拡大していく うちに、モジュールスコープのコードが複数回実行される状況はますます生じ やすくなり、ますます予測しにくくなるでしょう。モジュールスコープのコード の二重実行が起こらないと責任を持って主張することはできません。それは いずれ起こります; 単に運や時間、アプリケーションの複雑さの問題です。

こうした状況が不自然でないことをマイクロフレームワークの作者が認めれば、 彼らはモジュールスコープのコードの二重実行 (あるいは三重実行など) の 結果として実際の損害が起こらないと主張するかもしれません。この主張は 信じない方が賢明です。複数回実行の潜在的な結果は、アプリケーションと フレームワークコードの微妙な関係に加えてコード実行の時間的な順序関係を 含んでいるので、あまりにも数が多く予測することができません。フレーム ワーク作者がすべての状況で何が起こるかを知ることは文字通り不可能です。 しかし、状況のある制限されたセットに対して特別に全知の能力を与えられた としても、フレームワーク作者は新しい機能をコーディングする時にほとんど 確実に二重実行の異常性のことを気にしないでしょう。彼は機能を加えようと 思っていて、 1% の複数実行のケースによって引き起こされるかもしれない問題 に対して保護を忘れます。しかしながら、どんな 1% のケースも、プロジェクト 上のあなたの苦痛の 50% を引き起こす可能性があります。そのため、それが 起きなければ良いのですが (so it’d be nice if it never occured)。

信頼できるマイクロフレームワークは、実際のところこの問題を回避するバックドア を提供します。そのようなマイクロフレームワークでは、デコレータに基づく 設定を完全に廃止することができます。以下のようにすることを要求する代わりに:

1
2
3
4
5
6
7
8
gh = Groundhog('myapp', 'seekrit')

@gh.route('/foo/')
def foo():
    return 'foo'

if __name__ == '__main__':
    gh.run()

デコレータシンタックスを廃止して、「ほとんど完全に命令的」にできるようにします:

1
2
3
4
5
6
7
8
def foo():
    return 'foo'

gh = Groundhog('myapp', 'seekrit')

if __name__ == '__main__':
    gh.add_route(foo, '/foo/')
    gh.run()

これは Pyramid ドキュメンテーションの中で推奨される一般的な稼働モードです。 いくつかの既存のマイクロフレームワーク (特に Flask) は同様にそれを可能 にします。 (Pyramid 以外の) どのフレームワークもそれを推奨しません。 アプリケーションが 2つや3つ、4あるいは10のモジュールを越えて成長するこ とを予想しなければ、どのモードを使用するかは恐らくあまり重要ではありま せん。しかしながら、あなたのアプリケーションが大きくなる場合、命令的な 設定はより良い予測を提供することができます。

Note

賢明な読者は Pyramid が設定デコレータも含んでいることに気づいたでしょう。 おや! これらのデコレータは同じ問題を持っていませんか? いいえ。これら のデコレータは、実行された時に外部 Python モジュールに populate しま せん。それらは、単に取り付けられている関数 (およびクラスやメソッド) を書き換えます。これらの書き換えは、後で実行される予測可能で構造的な インポート過程を持った走査プロセスの間に見つけられます。モジュールに 局所的な書き換えは、実際に二重インポートに対する最良の状況です; もし モジュールがインポート時に単にそれ自体とその内容を書き換えるなら、 モジュールが 2度インポートされても、それは OK です。なぜなら、それぞれの デコレータの発動が、他のモジュールの中のレジストリのような共有資源 ではなく、常にそのデコレータが取り付けられたオブジェクトの独立した コピーを書き換えるからです。これは二重登録が行なわれないという効果が あります。

Routes Need Relative Ordering

次の単純な Groundhog アプリケーションを考えてみてください:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from groundhog import Groundhog
app = Groundhog('myapp', 'seekrit')

app.route('/admin')
def admin():
    return '<html>admin page</html>'

app.route('/:action')
def action():
    if action == 'add':
       return '<html>add</html>'
    if action == 'delete':
       return '<html>delete</html>'
    return app.abort(404)

if __name__ == '__main__':
    app.run()

このアプリケーションを起動して URL /admin を訪れると “admin” ページが 見えるでしょう。これは意図した結果です。しかしながら、もしファイル中の 関数定義の順序を再配置したら、どうなるでしょうか。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from groundhog import Groundhog
app = Groundhog('myapp', 'seekrit')

app.route('/:action')
def action():
    if action == 'add':
       return '<html>add</html>'
    if action == 'delete':
       return '<html>delete</html>'
    return app.abort(404)

app.route('/admin')
def admin():
    return '<html>admin page</html>'

if __name__ == '__main__':
    app.run()

このアプリケーションを起動して URL /admin を訪れると、今度は 404 エラーが返されるでしょう。これは恐らくあなたが意図したものではありません。 関数定義順を再配置した場合に 404 エラーが表示される理由は、このマイクロ フレームワークのルーティングデコレータによって表現されたルーティング 宣言が 順番 を持ち、その順番が問題になるからです。

予想された結果を達成した最初のケースでは、モジュールスコープでデコレータ によってルーティングパターンを加えることにより、最初にパターン /admin を持つルーティングを加え、次にパターン /:action を持つルーティング を加えました。 PATH_INFO/admin であるリクエストがこの アプリケーションに入力された場合、ウェブフレームワークは アプリケーションのルーティングパターン各々に対してそれらがモジュールに 定義された順でループします。その結果、ルーティングパターン /admin に関連付けられたビューが起動されます: それは最初に一致します。 すべて世はこともなし、です。

予想された結果を達成しなかった2番目のケースでは、最初にパターン /:action を持つルーティングを加えました。次に、パターン /admin を持つルーティングを加えました。 PATH_INFO/admin である リクエストがこのアプリケーションに入力された場合、ウェブフレームワークは アプリケーションのルーティングパターン各々に対してそれらがモジュールに 定義された順でループします。その結果、ルーティングパターン /:action に関連付けられたビューが起動されます: それは最初に一致します。 404 エラーが送出されます。これは望んだ結果ではありません; 単にビュー関数を定義した順番によってそれは起こりました。

これは、 Groundhog のルーティングがインポートされた順番でルーティング マップに加えられ、リクエストが入ってきたときにも同じ順でマッチされる からです。 Bottle は、この記述の時点では Groundhog と同様Python 実行時 に定義された順にルーティングマッチを行います。それに対し Flask は、 ルーティングマッチをインポート順に基づいて順序付けません; Flask は ルーティングの「複雑さ」に基づいてアプリケーションに追加される ルーティングを並び替えます。他のマイクロフレームワークは、様々な ルーティング順の戦略を持っています。

あなたのアプリケーションは十分に小さいのでルーティング順に関する問題は 起こらないかもしれません。しかしながら、アプリケーションが大きくなれば、 ルーティング順を指定したり予想したりすることが、アプリケーションが 大きくなるにしたがって一層困難になるでしょう。ある時点で、恐らくより明 示的にルーティング順をコントロールし始める必要があるでしょう。特に拡張 性を要求するアプリケーションでは。

あなたのマイクロフレームワークが複雑さに基づいてルーティングマッチの 順序を決めるなら、「複雑さ」が何を意味しているのかを理解する必要があるでしょう。 また、任意の「より複雑な」ルーティングより前に「より複雑でない」 ルーティングをマッチさせるために、そのルーティングが確実に最初になるように 試行錯誤して注入する必要があるでしょう。

あなたのマイクロフレームワークが関数デコレータ定義の相対的なインポート / 実行に基づいてルーティングマッチの順番を決めるなら、それらのすべての 文が「正しい」順で実行されることを保証する必要があるでしょう。また、 アプリケーションを大きくしたり、拡張しようとするときはいつも、 このインポート / 実行順を意識する必要があるでしょう。これは最小の アプリケーション以外では維持するのが困難な不変式です。

いずれの場合も、あなたのアプリケーションはその設定が実行されるように 設定デコレータを含む非 __main__ モジュールを何らかの形でインポート しなければなりません。これはあなたを少し不快にしますか? するはずです。 なぜなら Application Programmers Don’t Control The Module-Scope Codepath (Import-Time Side-Effects Are Evil) なので。

Pyramid では、デコレータインポート時の順番を使用しません。また、 ルーティングマッチ順を定義するために、あるルーティングと別のルーティングの 相対的な複雑さを推測することも試みません。Pyramid では、相対的なルーティング 順を pyramid.config.Configurator.add_route() メソッドの複数の実行 の時間的な順序関係によって命令的に維持しなければなりません。繰り返し add_route を呼び出した順番は、ルーティングマッチの順番になります。

この命令的な順序付けを維持する必要性が本当にあなたを困らせる場合、 ルーティングマッチの代わりに traversal を使用することができます。 それはコードを URL にマップするための完全に宣言的な (かつ完全に予測可能な) メカニズムです。URL ディスパッチは、小さな、拡張可能でないアプリケーション に対してはより理解しやすい方法なのに対して、トラバーサルは、大規模な アプリケーションや任意に拡張可能である必要があるアプリケーションに 大変適しています。

“Stacked Object Proxies” Are Too Clever / Thread Locals Are A Nuisance

いくつかのマイクロフレームワークでは、 論理的にはグローバルでない オブジェクトに対するハンドルを得るために import 文を使用します:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from flask import request

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # this is executed if the request method was GET or the
    # credentials were invalid

Pylons 1.X ウェブフレームワークは同様の 戦略を使用しています。 Pylons ではこれを “Stacked Object Proxies” と 呼んでいるので、この議論の目的のために私もそれに倣います。

Python において、インポート文 (import foo, from bar import baz) は外部の Python モジュール内でグローバルに定義されたオブジェクトへの 参照を得るために最も頻繁に実行されます。しかしながら、正常なプログラム では、関数本体のスコープに従う寿命を持ったオブジェクトへの参照を得るために インポート文は使用されません。例えば、関数の本体部で定義されたループ カウンタを表わす i という名前の変数をインポートしようとすることは 不条理でしょう。例えば、私たちは以下のコードから i をインポート しようとは決してしません:

1
2
3
def afunc():
    for i in range(10):
        print i

その性質上、 WSGI サーバーが長寿命のウェブフレームワークを呼び出した結果 生成された request オブジェクトはグローバルにはなりえません。なぜなら 単一のリクエストの寿命はフレームワークを実行するプロセスの寿命よりも はるかに短くなるからです。ウェブフレームワークによって生成された request オブジェクトは、実際 Python 標準ライブラリや通常のライブラリコードの中で 定義されるインポート可能なオブジェクトに対する類似性よりも、上記例における i ループカウンタとの類似点をより多く持っています。

しかしながら、 stacked object proxies を使用するシステムでは、 import を使ってユーザが簡単にコードを書く方法を提供 (offer users a nice spelling involving import) できるようにする目的で、モジュール スコープを外れて (out to) request のようなローカルスコープの オブジェクトが推奨されています。それらのシステムでは、私が疑わしい理由 と考えるもののために、 from framework import request が、より健全な from myframework.threadlocals import get_request; request = get_request() の代わりに (後者がより明示的であっても) request を 得る正統な方法としてユーザに提示されます。

マイクロフレームワークがスレッドローカル変数をまったく使用しなければ、 それは 最も 明示的でしょう。 Pyramid ビュー関数には request オブジェ クトが渡されます; Pyramid の多くの API は、request オブジェクトが明示的 に渡されることを要求します。スレッドローカル変数として現在の Pyramid request を検索することは 可能 です。しかしそれは「緊急時には ガラスを割ってください」タイプの活動です。この明瞭さは Pyramid ビュー 関数をより容易にユニットテスト可能にします。というのも、テストのセット アップ中に適切な「ダミーの」 request (または同様のスコープを持つ他の) オブジェクトを生成するために、フレームワークに依存する必要がないからです。 さらに、これによって任意のシステム (例えばモンキーパッチを行わない async サーバー) で動作する可能性が高くなります。

Explicitly WSGI

一部のマイクロフレームワークはアプリケーションオブジェクトの run() メソッドを提供していて、このメソッドは簡単に実行するために標準サーバー 設定を行います。

Pyramid は、現在のところルーターが簡便な run() API の背後にある WSGI アプリケーションであるという事実を隠そうとしていません。それは、 Pyramid アプリケーションを起動するために WSGI サーバーをインポートし、 その WSGI サーバーのドキュメンテーションに従ってそれを使用するように 人々に単に伝えます。

run() の背後のサーバ実行ステップを抽象の彼方に置くことによって削減 される追加の行は、一部のマイクロフレームワークにおいて API と関係する dubious な二次の意思決定を drive したように見えます。例えば Bottle は app.run() メカニズムによってサポートする WSGIサーバーのそれぞれに対 して ServerAdapter のサブクラスを含んでいます。これは bottle.py の中に次のモジュールに依存するコードが存在することを意味 します: wsgiref, flup, paste, cherrypy, fapws, tornado, google.appengine, twisted.web, diesel, gevent, gunicorn, eventlet, rocketrun メソッド にその名前を渡すことで、実行したいサーバーの種類を選択します。理論的に は、これは素晴らしく思えます: 名前を渡すだけで gunicorn 上で Bottle を試してみることができます! しかしながら、 Bottle を完全にテスト するためには、これらのサードパーティシステムをすべてインストールして、 そのすべてが機能しなければなりません; Bottle の開発者は、これらのパッケー ジの各々に対する変更を監視し、彼らのコードが依然としてそれらと適切に インタフェースを持つことを確かめなければなりません。これは、テストに 要求されるパッケージを大幅に拡大します; これは 多数の 要求です。要求 (されるパッケージ) の衝突やビルド時の問題によって、これらのテストを完全 に自動化することは恐らく難しいでしょう。

その結果、1ファイルアプリケーションに対しては、私たちは現在わざわざ run() ショートカットを提示していません; 人々には、選択した WSGI サーバーをインポートし、かつ手動でそれを実行するようにと伝えています。 サーバー抽象レイヤーを望む人々に対しては、 PasteDeploy を使用することを 提案します。 PasteDeploy に基づいたシステムでは、サーバーが WSGI アプリ ケーションとインタフェースを持つことができることを保証する負担は、 ウェブフレームワークの開発者ではなくサーバー開発者に課せられます。 そのためよりタイムリーで、正しいことが期待されます。

Wrapping Up

これは最も単純な Pyramid アプリケーションの図解されたバージョンで、 コメントは私たちが Microframeworks Have Smaller Hello World Programs 節で 議論したことを考慮に入れています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from pyramid.response import Response         # explicit response, no TL
from wsgiref.simple_server import make_server # explicitly WSGI

def hello_world(request):  # accepts a request; no request thread local reqd
    # explicit response object means no response threadlocal
    return Response('Hello world!')

if __name__ == '__main__':
    from pyramid.config import Configurator
    config = Configurator()       # no global application object.
    config.add_view(hello_world)  # explicit non-decorator registration
    app = config.make_wsgi_app()  # explicitly WSGI
    server = make_server('0.0.0.0', 8080, app)
    server.serve_forever()        # explicitly WSGI

Pyramid Doesn’t Offer Pluggable Apps

include() を使用して一つの同じ設定に 多数の外部ソースを結合するのが「Pyramid 流」です。アプリケーションを構成 するために任意の数の include が行えます; include はさらに他の include の 内部からも行えます。 include の内部では、その外部で使用できる任意の ディレクティブ (add_view() などのような) も使用することができます。

Pyramid には衝突探知システムがあり、 2つの include された外部コンポーネント が同じ設定を衝突する方法で加えようとした場合 (例えば、両方の外部コンポーネント が同じ名前を使用してルーティングを加えようとした場合や、両方の外部コンポーネント が同じ predicate のセットを持ったビューを加えようとした場合) にエラー を投げます。これらの特徴のことを「プラグ可能なアプリケーション」から システムを構成するために使用できるものと呼ぶことは大変魅力的です。 しかし、実際にはこの主張には多くの問題があります:

  • 用語の混乱があります。 Pyramid には、実際には複数形の “applications” に 対応する概念はありません。単に複数のソースから単一の WSGI アプリケー ションを生成するために設定を構成する方法があるだけです。設定を include するか disinclude することにより WSGI アプリケーションが振る 舞いを獲得できたとしても、一旦それらすべてが一緒に設定されると、 実際のところ1つの「アプリケーション」 (外部コンポーネントから追加された ルーティングやビューなどの設定の意味で) と別のアプリケーションの境界 を区別するために使用することができる機構を Pyramid は提供しません。
  • Pyramid は、本当に、神に誓って「適当な場所からアプリをダウンロードして、 プラグインしてシステムを作成する」という「プラグ可能な」アプリケーション を統合することを可能にするために十分な “rails” を提供しません。 Pyramid それ自体は主張の強いフレームワークではないので (特別の種類の データベースを要求せず、 URL をコードにマップする方法を多数提供し ている、等々)、誰かがアプリケーションの半完成品を作成して、カジュアルに 再配布したものをどこかの Pyramid ユーザが受け取って、パッケージから機能を config.include するだけで動く、ということは起こりそうもありません。 これは特に、ブログ、Wiki、 Twitter クローン、コメントシステムなどのよう な高レベルのコンポーネントにまさに該当します。インテグレータ (「プラグ 可能アプリ」として宣伝されたパッケージをダウンロードした Pyramid 開発者) は、例えばどんなタイプの永続化システムを使用しているかということに関して ほとんど確実に異なる選択を行なっているでしょう。また、インテグレータは 「プラグ可能アプリケーション」の要求を満たす (appease the requirements) ため、異なるデータベースをセットアップしたり、彼のアプリケーションが プラグ可能なアプリを隠すこと (あるいはその逆) を防ぐために彼自身のコード に変更を加えたり、その他任意の数の変更を要求されるかもしれません。

この理由で、 Pyramid はプラグ可能なアプリケーションではなく「拡張可能な」 アプリケーションを持つと主張します。どんな Pyramid アプリケーションも、 その設定文が config.include によって取り入れることができるものに 構成されている限り、フォークせずに拡張することができます。

さらに、一人の開発者あるいはチームが config.include を使用して有効にしたり 無効にしたりできる、1セットの相互運用 (interoperating) コンポーネントを 作成することは完全に筋が通っています。開発者またはチームは “rails” を 提供できるでしょう (プロジェクトを作成するために使われる技術に関する 高レベルの選択を行なうことによって)。したがって、すべてのコンポーネントを まとめてプラグインすることに問題はないでしょう。 任意の ユーザに コンポーネントを配布する必要がある場合のみ、その問題は姿を現します。 任意のサードパーティーのために動く必要のある「プラグ可能なアプリケーション」 に関して Django が同様の問題を持っていることに注目してください。 たとえそれが Pyramid よりさらに多数の rails を提供していたとしてもです。 Pyramid が提供する rails が 「プラグ可能なアプリケーション」を作るために 十分でなかったとしても、ローカルの修正なしでストーリーは現実に働きます (story really work)。

本当にプラグ可能なアプリケーションは、ウェブフレームワークよりはるかに 高いレベルで作成される必要があります。なぜなら、ウェブフレームワークは そのようなアプリケーションを箱から出した状態で動かすのに十分な制約を 実際には提供することができないからです。実際には、その代わりにそれら (=プラグ可能なアプリケーション) をアプリケーションにプラグインする必要が あります。これらの制約を提供し、本当にアプリケーションをプラグインする 方法を提供できるアプリケーションを Pyramid で構築することは壮大なゴールと なるでしょう (Joomla, Plone, Drupal が心に浮かびます)。

Pyramid Has Zope Things In It, So It’s Too Complex

時々、メーリングリストにこのようなメッセージを投稿せざるをえないという 気持ちになる人がいるようです:

Pyramid をちょっと見たんだけど ... 僕には複雑すぎてどんなメリットが
あるのかよく分からないよ.. そろそろ django に step back するかどうか
考えるべきじゃないかという気がしてるんだ .. 僕はずっと zope の
(役に立たない?) 複雑さを嫌っていて、単純な考え方が好きなんだ。

(実際には、本物の電子メールから意訳されたものです)

この批判について順番に見ていきましょう。

Too Complex

もしこの hello world プログラムを理解することができるなら、あなたは 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()

Pyramid には (印刷すると) 650 ページにも及ぶドキュメンテーションがあり、 非常に基礎的なトピックから最も進んだトピックまでカバーしています。 ドキュメント化されていないものは文字通り 何もありません 。さらに、 最高の とても頼りになるコミュニティーがあります。freenode.net の #pyramid IRC チャンネル (irc://freenode.net#pyramid) を訪れてみてください。

Hate Zope

あなたがそのように感じたとしたら残念です。 Zope ブランドは、この数年に わたり間違いなく一定のシェアを取っており、その insular (島国的) で不可解 な点に関して評判があります。しかし、単語 “Zope” は、修飾子がなければ文字 通り全く意味を持ちません。 Zope のどの 部分 が嫌いですか? “Zope” は技術 ではなくブランドです。

それが Zope 2 ウェブフレームワークのことなら、 Pyramid はそれとは違います。 Pyramid の主要な設計者や開発者なら誰でも知っていることです。 私たち は Zope 2 がユーザビリティの問題や制限を持っていることを知っていたので、 Pyramid の部分的な前身 (repoze.bfg) を書きました。 repoze.bfg (そして今の Pyramid) は、これらの問題を扱うために書かれました。

それが Zope 3 ウェブフレームワークのことなら、 Pyramid は はっきりと それとは違います。 Zope 3 の多数の技術を利用することはすでに Grok プロジェクトの縄張りとされている領域です。それらが両方とも ウェブフレームワークであるという自明な事実を除けば、 Pyramid は Grok とはとても、とても異なっています。 Grok はエンドユーザに多くの Zope 技術を露出します。それに対して、あなたが Pyramid を使用する際に Zope 特有の概念を理解する必要があれば、私たちはある非常に基礎的な評価軸 で失敗しています。

それが単に単語 Zope のことなら: これは単に guilt by association (連座制) です。ソフトウェアの一部が内部で zope.foo という名前のパッケージを 使用していますが、だからといってそれを使用するソフトウェアが “Zope” に はなりません。その名前に単語 Zope を持った多くの 素晴らしい ソフトウェア が書かれています。 Zope はある種のモノリシックなソフトウェアではありません。 また、そのソフトウェアの多くは外部的に使用可能です。そして、本当はこれを defence することはこのドキュメントの仕事ではないのですが、 Zope は10年以上の間存在してきて、信じられないほど大きく活動的な コミュニティーがあります。もしこれが信じられない場合、 http://pypi-ranking.info/author を見れば、目を見張る ような現実に気がつくでしょう (eye-opening reality check)。

Love Simplicity

数年間の努力が、このパッケージとそのドキュメンテーションの仕上げに注がれ、 開発者が使用できるくらい可能な限り単純になるように人事が尽くされました。 もちろんすべてはトレードオフです。また、「単純」ということに関して人々は 自分の考えを持っています。 Pyramid が複雑であると考えているなら、 スタイルの違いがあるのかもしれません。そのような開発者は当然意見が一致 しないでしょう。

Other Challenges

他の challenge があればぜひ Pylons-devel メーリングリストに 送ってください。私たちは、設計変更を検討することでそれに取り組むか、 少なくともここにそれを記そうと思います。