例外処理
TurboGears?はエラーの管理やリダイレクトに関しては標準Pythonのtry/exceptブロックを拡張したシステムを持っています。またTurboGearsのエラーシステムはバリデーションエラーとPythonの例外を区別しています。これらはそれぞれ @error_handler() ディレクティブや @exception_handler() ディレクティブで処理されます。
バリデーションエラー
バリデーションエラーはTurboGearsのバリデータフレームワークによって発行されます。これはふつうウィジェットライブラリとともに用いられますが、引数が整数かどうかを確認するときにも役に立ちます。バリデーションエラーは @error_handler() デコレータや、 tg_errors キーワードパラメータを伴ったメソッドを使うことによって制御されます。
このデコレータがどのように動作するのかの例を以下に示します:
from turbogears import controllers, expose, validate
from turbogears import error_handler, validators as v
class Root(controllers.RootController)
@expose()
def index(self, number=-1, tg_errors=None):
"""displays a 'number' (but actually can be anything)"""
if tg_errors:
errors = [(param,inv.msg,inv.value) for param, inv in
tg_errors.items()]
return dict(error_message=errors)
else:
return dict(number=number)
@expose()
@error_handler(index)
@validate(validators={"number":v.Int})
def validated_number(self, number=2):
"""displays an Integer (only)"""
return dict(valid_number=number)
このコードは2つのコントローラメソッドを生成しています。一つはドメインのルートである / であり、もう一つは /validated_number です。両方とも number をパラメータにとってそれを表示しています。このindexメソッドは number パラメータをチェックしておらず何でも通過させてしまいますが、 validated_number メソッドは、エラーをチェックする Int バリデータを伴っています。このエラーは index コントローラに戻されて、indexコントローラはそれを tg_errors エラーとして受け取り、それをうまい具合に処理します。
以下はこれらがどの様に動作するのかを示す、 twill session からの出力です(一部改変しています):
-= Welcome to twill! =-
current page: *empty page*
>> go http://localhost:8080/
==> at http://localhost:8080/
>> show
{"tg_flash": null, "number": -1}
>> go http://localhost:8080/?number=42
==> at http://localhost:8080/?number=42
>> show
{"tg_flash": null, "number": "42"}
>> go http://localhost:8080/?number=blue
==> at http://localhost:8080/?number=blue
>> show
{"tg_flash": null, "number": "blue"}
>> go http://localhost:8080/validated_number
==> at http://localhost:8080/validated_number
>> show
{"tg_flash": null, "valid_number": 2}
>> go http://localhost:8080/validated_number?number=42
==> at http://localhost:8080/validated_number?number=42
>> show
{"tg_flash": null, "valid_number": 42}
>> go http://localhost:8080/validated_number?number=blue
==> at http://localhost:8080/validated_number?number=blue
>> show
{"tg_flash": null, "error_messages": [["number", "Please enter an integer
value", "blue"]]}
興味深いのは一番最後のリクエストです。 validated_number コントローラは明らかに整数ではない、間違った値である blue を通してしまいます。しかしバリデータはこれを発見し、期待通り index コントローラへ tg_errors として戻してくれました。同時にふつうの呼び出しも期待通りに動作します。 tg_errors そのものは失敗したパラメータをキーとし、 formencode.Invalid を値とするふつうの辞書です。このindexメソッドはエラーを処理する一つの(そしてちょっと貧弱な)方法を見せていますが、もっとうまくやる方法があります。
ふつう @error_handler() を使うときは TurboGears? widgets を一緒に使います。以下は上記と同じ基本的なセットアップの例です:
from turbogears import controllers, expose, validate, redirect
from turbogears import error_handler, validators as v
from turbogears import widgets as w
number_form = w.ListForm(
fields = [
w.TextField(
name="number",
label="Enter a Number",
validator=v.Int(not_empty=True))
],
submit_text = "Submit Number!"
)
## This is what the template looks like
# <html xmlns:py="http://purl.org/kid/ns#">
# <body>
# <h1>This is a Form Page!</h1>
# ${form(value_of('data',None), action=action, method="POST")}
# </body>
# </html>
class Root(controllers.RootController):
@expose(template=".templates.welcome")
def index(self,number=-1,tg_errors=None):
return dict(data={'number':number},
form=number_form,
action="/validated_number")
@expose()
@error_handler(index)
@validate(form=number_form)
def validated_number(self, number=2):
return dict(valid_number=number)
先ほどのコードとの一番大きな違いは、 index コントローラがもう明示的には tg_errors を処理しないということです。フォームで便利なのは、どのようにバリデーションエラーを処理すればよいかわかっていることです。あなたはエラーハンドラをセットすれば、もうそれを忘れてしまえばいいのです。
例外とルール
例外はエラーとは分離されてしょりされます。何故ならバリデーションエラーはウェブアプリにおいては予測されることだからです。一方、例外はそれよりもやや深刻です。あなたもそれを分離して処理したいのではないでしょうか。使い方は @exception_handler と tg_exceptions を使うこと以外はエラーのときと非常に似ています。
気を付けなければならないのは、異なるタイプの例外を異なるハンドラで処理したいときです。例えばSQLObjectの例外はValueErrorsと分離して処理したいでしょう。これは rules パラメータ( @error_handler() 内で利用可能です)を使うことで可能です。 rules は適切なPython表現を含んだ文字列を取ります。表現が正しいときにルールはマッチします。以下を見てみましょう:
import turbogears
from turbogears import controllers, expose, validate, redirect
from turbogears import exception_handler
class Root(controllers.RootController):
# Note that the exception handlers don't need to be exposed
# if they're not meant to be accessed directly. It's always a
# wise decison to expose only what is absloutely needed to prevent
# information leaking.
def value_handler(self,tg_exceptions=None):
"""only called for value errors"""
return dict(handling_value=True,exception=str(tg_exceptions))
def index_handler(self,tg_exceptions=None):
"""only called for index errors"""
return dict(handling_index=True,exception=str(tg_exceptions))
@expose()
@exception_handler(value_handler,"isinstance(tg_exceptions,ValueError)")
@exception_handler(index_handler,rules="isinstance(tg_exceptions,IndexError)")
def exceptional(self, number=2):
number = int(number)
if number < 42:
raise IndexError("Number too Low!")
if number == 42:
raise IndexError("Wise guy, eh?")
if number > 100:
raise Exception("This number is exceptionally high!")
return dict(result="No errors!")
以下はtwillの出力です:
current page: *empty page*
>> go http://localhost:8080/exceptional
==> at http://localhost:8080/exceptional
>> show
{"exception": "Number too Low!", "handling_index": true, "tg_flash": null}
>> go http://localhost:8080/exceptional?number=42
==> at http://localhost:8080/exceptional?number=42
>> show
{"exception": "Wise guy, eh?", "handling_index": true, "tg_flash": null}
>> go http://localhost:8080/exceptional?number=blue
==> at http://localhost:8080/exceptional?number=blue
>> show
{"exception": "invalid literal for int(): blue", "tg_flash": null, "handling_value": true}
>> debug http 1
DEBUG: setting http debugging to level 1
>> go http://localhost:8080/exceptional?number=400
# ...
reply: 'HTTP/1.1 500 Internal error\r\n'
# ...
number が42より小さいときに index_handler (出力では handling_index )へ行くことや、整数でない値のときにバリューエラーが起こることに注意してください。また最後のケースは、処理できなかったエラーをCherryPyに組み込まれたハンドリングメカニズムがカバーしていることを示しています。もちろんそういったエラーを rules パラメータ無しの @exception_handler() を使うことでリダイレクトすることも可能です。
最後に注意を
ほとんどの部分において、デコレータの順序はさほど大きな問題ではありません。複合エラー処理とIdentityフレームワークの独自ハンドラメソッドがあるときには、一つの例外が発生します。 @error_handler() デコレータが @identity_require() デコレータよりも下にあったとき、identityのデコレータは完全に素通りになり、セキュリティが危険になってしまいます。そうすることのないように注意してください。
@expose()
@identity.require(identity.not_anonymous())
@error_handler() #BAD, DON'T DO IT
def foo(self,tg_errors=None):
pass
@expose()
@error_handler() #OK, be careful
@identity.require(identity.not_anonymous())
def bar(self, tg_errors=None):
pass
def handler(self, tg_errors=None):
pass
@expose()
@identity.require(identity.not_anonymous())
@error_handler(handler) #GOOD
def baz(self):
pass

