Werkzeugチュートリアル惰訳

データベースにURLを格納するTinyURLクローンを作成する
Werkzeug0.2チュートリアルへようこそ。

このライブラリは、データベースレイヤのためのSQLAlchemyを利用するアプリケーション、
テンプレートにjinjaを利用するアプリケーション、そして、もちろんWSGIレイヤのために
werkzeugを利用するアプリケーションで使用されることを想定しています。

私たちがチュートリアルのアプリケーション用にこれらのライブラリを決めた理由は、
Djangoが過去にした設計決定のうちのいくつかに固執したいということです。

そのうちの1つは、アクションメソッドを備えたコントローラー・クラスの代わりに
ビューファンクションを使用することです。それはRailsPylonsにおいても共通の思想です。
他には、デザイナーにやさしいテンプレートを提供していることです。

Werkzeugのexample folderhttp://dev.pocoo.org/projects/werkzeug/browser/examplesには
あなたが好きなときに参照できる他のテンプレートエンジンを利用した一組のアプリケーションが
含まれています。そしてそこには、そのアプリケーションのソースコードもあります。

チュートリアルを実行するのには、まず、jinjaとSQLAlchemyを導入してください。
あなたがeasy_insatllを実行できる環境であるならば、以下のようにします。

sudo easy_install Jinja
sudo easy_insatll SQLAlchemy

私の環境では、以下のような実行結果となりました。

C:\>easy_install SQLAlchemy
Searching for SQLAlchemy
Best match: sqlalchemy 0.5.2
Processing sqlalchemy-0.5.2-py2.5.egg
Removing sqlalchemy 0.3.10 from easy-install.pth file
Adding sqlalchemy 0.5.2 to easy-install.pth file

Using c:\python25\lib\site-packages\sqlalchemy-0.5.2-py2.5.egg
Processing dependencies for SQLAlchemy
Finished processing dependencies for SQLAlchemy

C:\>easy_install Jinja
Searching for Jinja
Reading http://pypi.python.org/simple/Jinja/
Reading http://wsgiarea.pocoo.org/jinja/
Reading http://jinja.pocoo.org/
Best match: Jinja 1.2
Downloading http://pypi.python.org/packages/2.5/J/Jinja/Jinja-1.2-py2.5-win32.egg#md5=27b0804a126c2c0ebd4a9dacffb9dcbb
Processing Jinja-1.2-py2.5-win32.egg
creating c:\python25\lib\site-packages\Jinja-1.2-py2.5-win32.egg
Extracting Jinja-1.2-py2.5-win32.egg to c:\python25\lib\site-packages
Adding Jinja 1.2 to easy-install.pth file

Installed c:\python25\lib\site-packages\jinja-1.2-py2.5-win32.egg
Processing dependencies for Jinja
Finished processing dependencies for Jinja

もしあなたがWindows上でチュートリアルを試そうとしているなら、"sudo"ははずして実行してください。
(setuptoolsのインストールはわすれずに)

OS Xなら、portから、Linuxなら、package managerでpython-jinjaとpython-sqlalchemyとして実行してください。

Part0:フォルダ構成

チュートリアルを開始する前に、Werkzeugアプリケーションとテンプレート、静的ファイルのためのフォルダを
作成する必要があります。チュートリアルアプリケーションは、チビ"shorty"と呼ばれます。
また、私たちが使用する最初のディレクトリ階層は以下のように見えます。

manage.py
shorty/
__init__.py
templates/
static/

__init__.pyとmanage.pyファイルは、当分の間空ファイルです。
最初に作成したshortyフォルダはpython packageです。二つ目に作成したものは管理ユーティリティを
保持するためのものです。

Part1:The WSGI Application - PEP333 Python Web Server Gateway Interface v1.0

Djangoや他のフレームワークとは違い、WerkzeugはダイレクトにWSGIレイヤにオペレーションします。
デベロッパのためにセントラルWSGIアプリケーションを実行するファンシーな魔法はありません。
あなたが、最初に書くのは、基底WSGIアプリケーションオブジェクト実装です。それは、関数にも
callable classにもなりえるでしょう。

callable classには、関数にはないアドバンテージがあります。
一つは、callable classにいくつかのコンフィグパラメーターを渡すことができます。
更に、インラインのWSGIミドルウェアを使用することもできます。
インラインWSGIミドルウェアは、基本的には私たちのアプリケーションオブジェクトの”内部に”適用できるミドルウェアです。
これは、アプリケーション (session middlewares, メディアファイルをサーブするような etc.)にとって
不可欠なミドルウェアに対する良い考えです。

以下に shorty/application.pyファイルというWSGIアプリケーションを実装するための最初のコードを示します。

#!/usr/bin/env python
# -*- coding: cp932 -*-
# via. Werkzeug Tutorial Part 1: The WSGI Application
# %PYTHONPATH%/werkzeug-0.4.1-py2.5.egg/docs/tutorial.html

from sqlalchemy import create_engine
from werkzeug import Request, ClosingIterator
from werkzeug.exceptions import HTTPException

from shorty.utils import session, metadata, local, local_manager, url_map
from shorty import views
import shorty.models

class Shorty (object):
    def __init__(self,db_uri):
        local.appliaction = self
        sefl.database_engine = create_engine(db_uri, convert_unicode=True)

    def init_database(self):
        metadata.create_all(self.database_engine)

    def __call__(self, environ, start_response):
        local.application = self
        request = Request(environ)
        local.url_adapter = adapter = url_map.bind_to_environ(environ)
        try:
            endpoint, values = adapter.match()
            handler = getattr(views,endpoint)
            response = handler(request,**values)
        except HTTPException, e:
            response = e
        return ClosingIterator(response(environ,start_response),
                               [session.remove,local_manager.cleanup])
    

このコードの大部分が始まりです。
ステップ by ステップで進めていきましょう。

最初に一組のインポート文があります。
From SQLAlchemyは新しいデータベースエンジンを生成するファクトリ関数をインポートしています。
database engineは、データベース管理と、コネクションプールを保持しています。
次にWerkzeugネームスペースが提供しているいくつかのオブジェクトインポートがあります。
requestオブジェクト、リクエストの終了時に後始末を行ってくれる特別なイテレータ
そして全てのHTTP exceptionのためのベースクラスです。

私たちがutilsモジュールをまだ書いていないので、次の5つのインポートは動作しません。
しかしながら、オブジェクトのうちのいくつかを既にカバーすべきです。

sessionオブジェクトはPHP風のセッションオブジェクトではなくSQLAlchemyデータベースセッション
オブジェクトです。基本的にデータベース・セッション・オブジェクトはデータベース用に、
まだコミットされていないオブジェクトを追跡します。

Djangoとは異なり、インスタンス化されたSQLAlchemyモデルは、セッションによって
既に追跡されています!メタデータオブジェクトはさらに、テーブル情報を追跡するために
SQLAlchemyに使用されます。

デベロッパは、全てのデータベース用テーブルを作成するためにメタデータ・オブジェクトを
容易に使用できます。また、SQLAlchemyは、外部キーおよび同様の材料を調べるために
それを使用します。

localオブジェクトは、ユーティリティモジュールに作成する基本的名スレッドローカルオブジェクトです。
このオブジェクト上の全ての属性は現在のリクエストに結び付けられます。
私たちは、暗黙にスレッドセーフな方法で、このオブジェクトを利用することができます。

local_managerオブジェクトは、リクエスト終わりで全てのlocalオブジェクト(スレッドローカル)
のプロパティ削除のための追跡方法を保持しています。

URLルーティング情報を保持するURLmapを最後にインポートしています。
比較することができるDjangoを知っているなら、urls.pyモジュールにURLパターンを指定することができる。
PHPを使用しているなら、ビルトインの"mod_rewrite"と比較可能です。

デベロッパは、全てのモデルを保持しているmodelsモジュールとビュー関数を保持しているviewsモジュールを
インポートします。
たとえインポートを使わないとしても、全てのテーブルがメタデータプロパティ上に登録されるように
そこにあります。

それでは、アプリケーションクラスを見てみましょう。
このクラスのコンストラクタは、データベースのタイプおよび、データベースログインクレデンシャル(信任状)
か、データベースロケーションをさすデータベースURIを受け取ります。

SQLiteの例は次のとおりです。'sqlite:////tmp/shorty.db'(スラッシュは4つです)

コンストラクタでは、データベースURIでdatabase engineを生成し、convert_unicodeパラメータで
SQLAlchemyにstringを全てunicodeオブジェクトに変換するよう伝えます。

他には、local object(スレッドローカル)へアプリケーションを拘束しています。
これは、実際必要ありません。ただし、python shellでアプリケーションを弄りたければ
有用です。
アプリケーションをインスタンス化することで、カレントスレッドを拘束します。
そして全てのデータベース関数は、想定どおり動作するでしょう。

もしスレッド拘束を行わなければ、Werkzeugは、SQLAlchemyのためのセッションを作っている場合
データベースを見つけることができないと苦情を吐くでしょう。

コンストラクタの下に定義されているinit_database関数は、使用する全てのテーブルを作成するために
使用することができます。

その定義の後に、リクエストをディスパッチする関数が定義されています。
ここでは、Requestクラスのコンストラクタにenvironment(環境)を渡すことによって、
新しいrequestオブジェクトを生成しています。さらにもう一度、スレッドローカルにアプリケーションを
拘束しています。拘束を行わなければ、アプリケーションはまもなく壊れます。

このとき、URL mapに現在のWSGI environmentオブジェクトで関連付けられている
新しいURL map adapterを生成します。これは、基本的に、入ってくるリクエスト情報の環境を
判断して、それが必要とする環境から情報を取得します。
これは例えば外部URLのためのサーバーの名前、スクリプトのロケーションなどです。
そのため、私たちはURL builderを使用して絶対パスを生成することができます。

さらに、utilsモジュール中でadapterをURL生成に使用することができるように、
私たちはアダプターをローカル(スレッドローカル)に拘束します。

その後、1つの、try/exceptで、HTTP exceptionをキャッチしています。
これは、adapterがマッチングしている間、あるいはビュー関数の中で生じるかもしれない
HTTP exceptionを補足しているのです。

アダプターが私たちの現在のリクエスト用の妥当なendpointを見つけない場合、
それは、私たちがレスポンス・オブジェクトのように使用することができるNotFound例外を投げるでしょう。
endpointは基本的に私たちがリクエストを扱いたい関数の名前です。
私たちは、endpointの名前を持った関数をちょうど得て、それにリクエストおよびURL値を渡します。

関数の最後では、response objectをWSGIアプリケーションとして呼び出しています。
そして、この関数の戻り値(iterableとして)にclosing iterator classとともに、後始末用コールバック関数を渡します.
(callbacks:スレッドローカルの保持するデータのクリーンアップ、カレントSQLAlchemyセッションの削除)

次のステップでは、新たに2つの空ファイルshorty/views.pyとshorty/models.pyをつくります。
だから、事前にインポートしています。後で、この二つのファイルについてコードしていきます。

Part 2 : Utilities

今までで、基本的に、WSGIアプリケーション自体の実装は終了しています。しかし、
インポートしているクラスや関数が動作するように、utilityモジュールへさらにいくらかの
コードを加える必要があります。

当分の間、私たちは、アプリケーション動作に必要なオブジェクトを追加していくことになります。
以下に shorty/utils.pyファイルのコードを示します。

#!/usr/bin/env python
# -*- coding: cp932 -*-
# The Utilities

from sqlalchemy import MetaData
from sqlalchemy.orm import create_session, scoped_session
from werkzeug import Local, LocalManager
from werkzeug.routing import Map, Rule

local = Local()
local_manager = LocalManager([local])
# proxy objectが返る
application = local('application')

metadata = MetaData()
session = scoped_session(lambda: create_session(application.database_engine,
                                                transactional=True),local_manager.get_ident)

url_map = Map()
def expose (rule, **kw):
    def decorate(f):
        kw['endpoint'] = f.__name__
        url_map.add(Rule(rule,**kw))
        return f
    return decorate

def url_for(endpoint, _external=False, **values):
    return local.url_adapter.build(endpoint, values, force_external=_external)

最初に再び、ひと塊のコンポーネントをインポートしています。
そして、既知のlocal object(スレッドローカル)とlocal managerオブジェクトを生成します。
ここで新しいことは、local objectをstringと共に呼び出すとproxy objectが返ることです。
この返ってきたプロキシは、常にlocal object上のその名前(local.__call__("application"))をもった属性を指します。
For example application now points to local.application all the time.
(local.applicationは常に、applicationをさします)

しかしながら、もし上のことを行わなかった場合、local.applicationには何もオブジェクトが拘束されていないので
RuntimeErrorを得ることになるでしょう。

次の3行は、SQLAlchemy0.4 またはそれ以降のバージョンで動作するWerkzeugアプリケーションで基本的に必要です。
私たちは、全てのテーブルのために新しいmetadataを生成します。そしてそのときに、scoped_session ファクトリ関数
を使って新しいscoped_sessionを生成します。

これは、基本的に、werkzeug localが行うよう、カレントコンテキストを決定し、かつ、
現在のアプリケーションのデータベース・エンジンを使用するために同じアルゴリズムを使用するよう
SQLAlchemyに命令します。

もし、同一pythonインタプリタ上でアプリケーションオブジェクトの複数インスタンス化を提供したくないなら、
別の箇所でも、カレントlocal objectからアプリケーションオブジェクトを引くコードを無くせばシンプルになります。
この方法は、Djangoでも使われています。しかし、複数のこのようなアプリケーションオブジェクトの結合を不可能にします。

残りのモジュールは、私たちがviewsモジュールで使用するコードです。
基本的なアイディアとしては、Djangoのようなセントラルurls.pyや、PHPがやるようなURLリライトのための.htaccessではなく、
decoratorsを利用して、ビュー関数のためのURLディスパッチルールを決定する方法です。

これは一つの方法にすぎません。ルール定義のハンドル方法は無数にあります。

utils.pyのurl_for関数は、endpointによってURLを生成する単純な方法を提供します。
私たちは、viewsやmodelモジュールでこの関数を利用することになります。

Intermission : And Now For Something Completely Different
(休憩:全然関係ない話)

あなたは、日々の似通っている開発タスクに多くの時間がさかれています。
そのうちの一つが開発サーバーの立ち上げ(もし、あなたがphpユーザーなら:Werkzeugは、開発のためにApacheに依存しません。
完璧に素晴らしい推奨方法は、開発用pythonに同梱されているwsgiref serverを利用することです。)、次にpythonインタプリタ
開始させてdatabase modelsとの格闘、データベースを初期化、などなど、

Werkzeugはそのようなmanagement scriptsを信じられないくらい簡単に作成します。
以下に完全にフィーチャーされたmanagement scripts実装を示します。
最初に作成したmanage.pyファイルの中にこの実装を入れます。

#!/usr/bin/env python
# -*- coding: cp932 -*-
# manage.py

from werkzeug import script

def make_app ():
    from shorty.application import Shorty
    return Shorty('sqlite:////tmp/shorty.db')

def make_shell ():
    from shorty import models,utils
    application = make_app()
    return locals()

action_runserver = script.make_runserver(make_app,use_reloader=True)
action_shell = script.make_shell(make_shell)
action_initdb = lambda : make_app().init_database()

script.run()

werkzeug.script は、script documentation(file:///C:/Python25/Lib/site-packages/werkzeug-0.4.1-py2.5.egg/docs/script.html)
で詳細に説明してあるのでここでは割愛します。

もし、上述したコードと、あなたのコードの比較と行番号による例外チェックを行いたいなら。
何が重要かというと、python manage.py shellを実行することで、tracebackなしで、インタラクティブpythonインタプリタ
取得できるということです。

すぐにでも、スクリプトを走らせて、database modelsモジュールを書き始めることができるのです!ひゃっほー!

Part 3 : Database Models

既に私たちはmodelsモジュールを作ることができます。
なぜなら、applicationは、tableと一つのモデルだけを持つシンプルでprettyなものだからです。

#!/usr/bin/env python
# -*- coding:cp932 -*-
# models.py

from datetime import datetime
from sqlalchemy import Table , Column, String, Boolean, DateTime
from shorty.utils import session,metadata,url_for,get_random_uid

url_table = Table('urls',metadata,
                  Column('uid',String(140),primary_key=True),
                  Column('target',String(500)),
                  Column('added',DateTime),
                  Column('public',Boolean)
                  )
class URL(object):
    def __init__(self, target, public=True, uid=None, added=None):
        self.target = target
        self.public = public
        self.added = added or datetime.utcnow()
        if not uid:
            while True:
                uid = get_random_uid()
                if not URL.query.get(uid):
                    break
        self.uid = uid

    @property
    def short_url(self):
        return url_for('link',uid=self.uid,_external=True)

    def __repr__(self):
        return '<URL %r>' % self.uid

session.mapper(URL, url_table)    

このモジュールは、かなり真っ直ぐです。テーブル作成に必要なSQLAlchemyモジュールを全てインポートします。
そして、このテーブルのために、クラスを加えて、マッピングします。
SQLAlchemyに関する詳細な説明に関しては、excellent tutorial(http://www.sqlalchemy.org/docs/04/ormtutorial.html)
を見ましょう。

コンストラクタでは、自由に使用できるIDが見つかるまで、ユニークなIDを生成します。
sqlalchemy.utils.get_random_uid関数が見当たらないことにならないように、sqlalchemy.utilsモジュールに
追加しておきましょう。
今回は、606行目あたりに以下のget_random_uid関数を追加しておきました。

# For Werkzeug Tutorial Application util function
URL_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
def get_random_uid ():
    from random import sample, randrange
    return ''.join(sample(URL_CHARS, randrange(3,9)))

今日はここまで。