Pyramid でポニーを表示する 7 つの方法
========================================
.. author:: default
.. categories:: none
.. tags:: none
.. comments::
まえがき
--------------------
.. note::
この記事は、
`2012 Python アドベントカレンダー (Webフレームワーク) `_ で
Django が大人気だったことに対抗して書かれました。
Django のマスコットキャラといえば Django Pony ですね。
.. figure:: http://djangopony.com/media/img/small/wallpaper.png
:align: center
それに対して Pyramid はこれです。
.. figure:: http://www.pylonsproject.org/static/images/pyramid-tee-banner.png
:align: center
……どうしてこうなった。これでは Django 人気にはとても対抗できません。
今時代が求めているのはゆるキャラです!
実は Django Pony が誕生する以前からポニーは Paste に含まれていて、
Pylons ユーザの間で親しまれていました。
.. seealso::
`Django vs Pylons: Pylons fails to deliver a pony?
`_
そこでこの記事では、マスコットキャラ不在の問題を解消するとともに、
Pyramid のセールスポイントである拡張性の高さを示すために、
Pyramid を拡張するあらゆる手段を駆使してポニーを表示してみたいと思います。
Pyramid のインストール
------------------------------
なぜか「Pyramid はこわい」というイメージが広がっているようなので、
Pyramid を使ったことがない人にも安心して読んでもらえるように
一から丁寧に説明していきます。
よく分かっている人はスキップしても OK です。
最初に virtualenv を使って Pyramid をインストールする環境を作ります。
今回は何となく Python 3 で作ってみました。
::
$ cd pyramid_pony_demo
$ python3 virtualenv.py --no-site-packages .
$ . bin/activate
pip を使って pyramid をインストールします。
依存関係にあるパッケージがまとめてインストールされるので多少時間がかかります。
::
(pyramid_pony_demo)$ pip install pyramid
pcreate コマンドでプロジェクトを作成します。
::
(pyramid_pony_demo)$ pcreate -s starter pyramid_pony_demo
...
Welcome to Pyramid. Sorry for the convenience.
Pyramid を使ったプロジェクトのひな形ができました。
(ちなみに最後のメッセージ "Sorry for the convenience." が長い間
謎だったんですが、どうやら `アメリカの有名なコメディが元ネタ
`_ みたいですね。
知らんわ、そんなの)
プロジェクトのセットアップを行います。
ここでもいくつかの依存パッケージがインストールされます。
::
(pyramid_pony_demo)$ cd pyramid_pony_demo
(pyramid_pony_demo)$ python setup.py develop
pserve で実行します。
::
(pyramid_pony_demo)$ pserve --reload development.ini
http://localhost:6543/ にアクセスすれば、 Pyramid のロゴが
表示されるはずです。
はい、簡単ですね。
ビューでポニーを表示する
------------------------------
今回のために `add-on `_ を用意しました。
以下のコマンドでインストールしてください。
::
$ pip install https://github.com/knzm/pyramid_pony/archive/master.zip
インストールが終わったら development.ini を開いて、
::
pyramid.includes =
pyramid_debugtoolbar
となっている箇所を見つけて、次のように1行追加してください。
::
pyramid.includes =
pyramid_debugtoolbar
pyramid_pony
http://localhost:6543/pony にアクセスすると、知っている人にはおなじみの
ポニー(の ASCII アート)が表示されます。 "add horn!" でユニコーンにも変身します。
::
,_.-('--.
.( '-.'.\'\_,
/ `-.`_;;-./(/=,
|.-.-'.' .`.\
{ .-.' //,|\\
/-'./ '._ <| \)
{ -./ |\ |
{_/ \ | |
.--"""--..,___,.;' |( o/
.' | `"`
/ /
_.-'| `-._
.' .'/| _ `\
/ .-'| \ , _.-`'-.__.' | /
|( / | | / `'------'` \ \/ /
\ ) \ / /`._ /`-./ /
|.' / )', \._ `\ \___ /
/ .'_.; \ \ `) | / /`
'._;.-,) `\ \/ | .-' /
( ) |_/ |_.'
jgs .-' /
\_.'
add horn!
Home
これは main 関数に以下のように書いたのと同じです。
::
config.add_route("pony", "/pony")
config.add_view("pyramid_pony.pony.view", route_name="pony")
not found ビューでポニーを表示する
----------------------------------------
再度 development.ini を開いて先ほど編集した行を以下のように書き換えてください。
::
pyramid.includes =
pyramid_debugtoolbar
pyramid_pony.not_found
今度は http://localhost:6543/ のトップページ以外のどこにアクセスしても
ポニーが表示されるようになります。
これは main 関数に以下のように書いたのと同じです。
::
config.add_notfound_view("pyramid_pony.pony.view")
before renderer イベントを使ってポニーを表示する
--------------------------------------------------
development.ini で :mod:`pyramid_pony.not_found` を :mod:`pyramid_pony.before_render` に
書き換えてください。
http://localhost:6543/pony でやっぱりポニーが表示されます。
:mod:`pyramid_pony.before_render` では、 :class:`BeforeRender` イベントにサブスクライバを
登録して、テンプレートに値を渡しています。
::
def add_global(event):
req = event['request']
event["home"] = req.script_name or "/"
url = req.path
if req.params.get("horn"):
data = UNICORN
event["link"] = "remove horn!"
event["url"] = req.path
else:
data = PONY
event["link"] = "add horn!"
event["url"] = req.path + "?horn=1"
data = base64.b64decode(data)
animal = zlib.decompress(data).decode('ascii')
event["animal"] = animal
def view(request):
return {}
def includeme(config):
config.add_route("pony", "/pony")
config.add_subscriber(add_global, BeforeRender)
config.add_view(view, route_name="pony", renderer='pyramid_pony:pony.mako')
見ての通り view 関数の中では何もしていません。
pony.mako の内容は以下の通りです。
::
Pony
${animal}
${link}
Home
テンプレートの中で参照している変数 (``${animal}`` など) は、
:func:`add_global` 関数で追加されたものです。
route ファクトリを使ってポニーを表示する
----------------------------------------
同じようにして :mod:`pyramid_pony.before_render` を :mod:`pyramid_pony.route_factory` に書き換えると、
http://localhost:6543/pony でポニーが表示されます。
(面倒くさくなってきたので、これ以降の節ではこのステップの説明を省略します)
:mod:`pyramid_pony.route_factory` では :func:`config.add_route` に ``factory`` 引数を
指定しています。こうすることで view が呼ばれた時に ``request.context`` の中身が
指定した factory のインスタンスになっています。
::
class PonyContext(object):
def __init__(self, request):
self.request = request
if request.params.get("horn"):
self.data = UNICORN
self.link = "remove horn!"
self.url = request.path
else:
self.data = PONY
self.link = "add horn!"
self.url = request.path + "?horn=1"
@reify
def home(self):
self.request.script_name or "/"
def decode(self, data):
data = base64.b64decode(data)
return zlib.decompress(data).decode('ascii')
def view(request):
context = request.context
data = context.data
html = TEMPLATE.format(
animal=context.decode(data),
url=context.url,
link=context.link,
home=context.home)
return Response(html)
def includeme(config):
config.add_route("pony", "/pony", factory=PonyContext)
config.add_view(view, route_name='pony')
view の中では ``context`` の属性参照とメソッド呼び出ししかしていないので、
非常にすっきりしています。
レスポンスアダプターを使ってポニーを表示する
--------------------------------------------------
次に ``pyramid.includes`` に指定するのは :mod:`pyramid_pony.response_adapter` です。
:mod:`pyramid_pony.response_adapter` では view で :class:`Pony` クラスのインスタンスを
返しています (:class:`Unicorn` は :class:`Pony` のサブクラスです)。
::
def view(request):
if request.params.get("horn"):
return Unicorn(request)
else:
return Pony(request)
def includeme(config):
config.add_route("pony", "/pony")
config.add_response_adapter(pony_response_adapter, Pony)
config.add_view(view, route_name="pony")
それでもポニーが表示されるのは、以下のようなレスポンスアダプターが
登録されているからです。この関数はビューがレスポンスを返した後で、
それが :class:`Pony` クラスのインスタンスだった場合に呼ばれます。
::
def pony_response_adapter(pony):
html = TEMPLATE.format(
animal=pony.animal,
url=pony.url,
link=pony.link,
home=pony.home)
return Response(html)
tween でポニーを表示する
------------------------------
``pyramid.includes`` に :mod:`pyramid_pony.tween` を指定してください。
このモジュールでは、以下のような tween が定義されています。
::
def pony_tween_factory(handler, registry):
def pony_tween(request):
if request.path == '/pony':
return pony_view(request)
else:
return handler(request)
return pony_tween
def includeme(config):
config.add_tween('pyramid_pony.tween.pony_tween_factory')
tween は 'between' から作られた造語で、その意味からも分かる通り
Pyramid がビューを呼び出す過程の途中で呼ばれます。
tween は WSGI における WSGI ミドルウェアに相当します。
view predicate を使ってポニーを表示する
--------------------------------------------------
最後に Pyramid 1.4 の新機能であるサードパーティー predicate を使ってみます。
``pyramid.includes`` に :mod:`pyramid_pony.view_predicate` を指定してください。
:mod:`pyramid_pony.view_predicate` では 2 つのビューが定義されています。
::
def pony_view(request):
home = request.script_name or "/"
link = "add horn!"
url = request.path + "?horn=1"
animal = decode(PONY)
html = TEMPLATE.format(animal=animal, url=url, link=link, home=home)
return Response(html)
def unicorn_view(request):
home = request.script_name or "/"
link = "remove horn!"
url = request.path
animal = decode(UNICORN)
html = TEMPLATE.format(animal=animal, url=url, link=link, home=home)
return Response(html)
2つのビューは同じ ``route_name`` に対応付けられています。
どちらのビューが呼び出されるかを決めているのが :meth:`config.add_view` の ``horn=`` 引数です。
::
class HornPredicate(object):
def __init__(self, val, config):
self.val = val
def text(self):
return 'content_type = %s' % (self.val,)
phash = text
def __call__(self, context, request):
return bool(request.params.get("horn")) == bool(self.val)
def includeme(config):
config.add_route("pony", "/pony")
config.add_view_predicate('horn', HornPredicate)
config.add_view(pony_view, route_name="pony", horn=False)
config.add_view(unicorn_view, route_name="pony", horn=True)
このように、 view predicate を使うと条件分岐を明示的に書かなくても
条件に合った適切なビューが自動的に呼び出されるようになります。
.. note::
この例のようなリクエストパラメータによる条件分岐であれば、サードパーティー
predicate の代わりに ``request_param`` を使って書くこともできます。
詳しくは `Pyramid のドキュメント `_ を参照してください。
あとがき
--------------------
いかがだったでしょうか。
Pyramid の拡張性の高さが分かってもらえたのではないかと思います。
あと、今回プロジェクト側のコードは最初に生成してから 1 行も書き換えていないことに
注目してください。変更したのは設定ファイルだけです。
つまり、便利な add-on が増えると、ほとんどコードを書かなくても
開発ができてしまうということです。
将来 Pyramid ユーザが増えて add-on も沢山公開されて
皆が幸せになれる、そんな未来が来るといいなと思っています。
Pyramid にポニーを対応させたのは実はこれが初めてではありません。
akhet 2.0 という偉大な先例があります。
今回作成した add-on の中にも、akhet から丸々コピーしたファイルが
含まれています。
ただ、今回この記事を書いている間に :mod:`akhet.pony` が Python 3 で正常に動かないことを
発見したので、 `pull request `_
を送っておきました。
これで Django vs Pyramid で Pyramid にはポニーがない、なんて言われなくて済みますね!