前回、PythonのWebサービス構築用フレームワークのtornadoについて紹介したが、今回はJSONに特化した拡張ライブラリ-Tornado-JSONについて紹介する。
Tornado-JSONのインストール
tornadoと同様にpipを使おう。
pip install Tornado-JSON
特徴
- Webapiの入出力形式にJSONを採用しそれに特化することで使いやすくしている
- パッケージ名・モジュール名で自動的にURLを決定
- jsonschemaによるバリデーション実行をより使いやすくする
- APIドキュメントの自動生成が可能
Webサービス構築手順
使用するクラス
- from tornado.ioloop import IOLoop
- from tornado_json.routes import get_routes
- from tornado_json.application import Application
- from tornado_json.requesthandlers import APIHandler
- from tornado_json import schema
※Applicationはクラス名がtornadoと一緒だがパッケージが異なる
手順
1. HTTP REquestに応答するためのHandlerを作成する。APIHandlerを継承したクラスを作成し、getやpostメソッドを実装する。
class HelloWorldHandler(APIHandler):
@schema.validate(
output_schema={"type":"string"},
)
def get(self):
return "Hello world!"
※応答結果はreturnで返す
※@schema.validateで応答結果の形式を指定する必要がある
2. 1を入れるパッケージを作成する。ディレクトリを作成し、その中に1で作成したクラスのファイルと__init__.pyを入れる。
$ ls helloworld
__init__.py api.py
3. Handler探索を行う。パッケージを指定することで、その配下にあるAPIHandlerを継承したクラスを探索し自動的にURLを作成する。
import helloworld
routes = get_routes(helloworld)
4. Applicationを作成する。3.で作成したroutesを引数に指定して作成する
settings = {"debug":True }
Application(routes=routes, settings=settings, generate_docs=True)
※tornado.web.Applicationとは引数が異なるので注意!
5. 待ち受けポート番号を設定する。
app.listen(8888)
6. 接続受付を行う。Ctrl-Cしない限りは応答が返らない。
IOLoop.current().start()
起動するとAPIドキュメントが自動生成される。また、応答形式はapplication/jsonとなり、{"status":{実行結果} "data":{get/postのreturn内容}となる
ソース全体
/home/pythonSample/tornado-json
|--helloworld
| |--api.py
| |--__init__.py
|--server.py
<server.py>
from tornado.ioloop import IOLoop
from tornado_json.routes import get_routes
from tornado_json.application import Application
import json
def make_app():
import helloworld
routes = get_routes(helloworld)
print("Routes\n=======\n\n" +
json.dumps([(url, repr(rh)) for url, rh in routes], indent=2)
)
settings = {"debug":True }
return Application(routes=routes, settings=settings, generate_docs=True)
if __name__ == "__main__":
app = make_app()
app.listen(8888)
IOLoop.instance().start()
<api.py>
from tornado_json.requesthandlers import APIHandler
from tornado_json import schema
class HelloWorldHandler(APIHandler):
@schema.validate(
output_schema={"type":"string"},
)
def get(self):
return "Hello world!"
URLの決定
{パッケージ名}/{モジュール名}/{Handler名}となる。Handler名からはHandlerの文字列は取り除かれてすべて小文字となる。 パッケージ名はget_routes()で指定したパッケージ配下にパッケージがある場合に適用される。
上記の例ではURLは「/api/helloworld」となる
URLパラメータの取得
get,postに引数を追加した場合、自動的にURLパラメータを受け取る形になる。パターンはw+の形式となる。引数のパターンはHandler名の後に追加される
class UrlParamHandler(APIHandler):
def get(self, fname, lname):
→ /api/urlparam/(?P<fname>[a-zA-Z0-9_\\-]+)/(?P<lname>[a-zA-Z0-9_\\-]+)/?$
URLの指定
Handler内に__url_names__を定義すると、Handler名に相当するところが置き換わる。また、Handler内に__urls__を定義するとURLが定義した値通りとなる。
ただし、__urls__を定義した場合、__url_names__ = [] としないと自動生成されるURLも登録されてしまう。
クエリの取得
tornado.webと同様にget_query_argumentを使用する。
POSTパラメータの取得
jsonで受け取ることが前提となる。値は、self.body["プロパティ名"]で取得する。そのためにはschema.validateでinput_schemaを定義する必要がある。定義しないと、self.bodyで値は受け取れない。
@schema.validate(
input_schema={
"type": "object",
"properties": {
"title": {"type": "string"},
"body": {"type": "string"},
"index": {"type": "number"},
},
"required": ["title", "body"]
},
output_schema={
"type": "object",
"properties": {
"message": {"type": "string"},
}
},
)
def post(self):
return {
"message": "{} was posted.".format(self.body["title"]),
}
出力処理について
各Handlerのget/postの返り値がそのまま応答値となるが、デフォルトでは以下のように加工して返している。
{ "status":"success", "data":{return値の内容}}
これを変更したい場合は、APIHandler.success()をオーバライドする。
バリデーション
@schema.validateを定義しておけば、指定した定義に基づいたバリデーションの実行が行われる。スキーマ定義には存在しないプロパティに関してはチェックされない(余分なパラメータがあってもエラーとはしない)。
渡すスキーマ定義を別ファイルに定義する場合は、それを読み込むための仕組みが必要(Tornado-JSONにはない)。
エラー処理のハンドリング
バリデーションでエラーとなった場合、デフォルトでは以下の形式で応答が返る。
HTTP STATUS 400 Bad Request
{ "status":"fail", "data":{バリデーションエラーメッセージ}}
また、get/post内で例外がスローされた場合はデフォルトでは以下の形式で応答が返る。
{ "status":"error", "code":{例外に応じたHTTP STATUS(通常は500)}, "message":{HTTP STATUSに応じた内容(Internal Server Errorなど), "data":{例外の内容(debug=Trueで起動した時のみ)}}
dataに関しては、例外が属性:log_messageを持てばその内容が、持たない場合はstr()で例外を変換した内容が入る。tornado.web.HTTPErrorがこの属性を持っている。
上記の応答内容を変更したい場合は、APIHandler.fail()とAPIHandler.error()をオーバライドする。
APIHandler.fail()は例外としてjsonschema.ValidationErrorかtornado_json.exceptions.APIErrorがスローされた場合に呼ばれる。
APIHandler.error()はそれ以外の例外がスローされた場合に呼ばれる。tornado.web.HTTPErrorはこちらで処理されることになる。
独自で例外を作成する場合も、APIErrorを継承した方が例外の応答処理が楽に作れる。
また、404応答を独自に返したい場合、Handlerを作成してApplicationのオプションdefault_handler_classで作成したHandlerを指定すればよい。特に指定しない場合はErrorHandlerが指定される。が、応答がtext/plainとなりjson形式ではないため変更するのが望ましい。
参考
Tornado-JSON — Tornado-JSON 1.2.2 documentation
上記のサンプルコードは以下にあります。