CherryPy 入門/まとめ
概要
CherryPyは,軽量なpython WAF.もっと軽量でSinatra-likeに書けるpython WAFにFlask/Bottleがあるが,オブジェクト志向のように書けて,URL Routingとオブジェクトツリーを対応させたい場合にはCherryPyが良いと思う.
Fullstack WAFだとDjangoが圧倒的だと思うが,プロト用途でWebAPIのみ作成するなら個人的にCherryPyがベスト.
ちなみに,google trendで"python flask" vs "python bottle" vs "python cherrypy"だと2011年初頭からFlask > Bottle > CherryPyのよう.
下記まとめ.
Installation
- ubuntu12.04
$ sudo apt-get install python-cherrypy3
Hello CherryPy
- example
#!/usr/bin/env python import random import string import cherrypy import json class SharedData(object): """ """ data = {} data[10] = 10 data[100] = 100 data[1000] = 1000 def __init__(self, ): """ """ pass class StringGenerator(object): @cherrypy.expose @cherrypy.tools.json_out() # return val as json def index(self): return SharedData.data #return str(SharedData.data) #return "Hello world!" @cherrypy.expose def generate(self, length=8): return ''.join(random.sample(string.hexdigits, int(length))) if __name__ == '__main__': cherrypy.quickstart(StringGenerator())
Session
- cherrypy.session["key"] = valueを使う
- server
import random import string import cherrypy class StringGenerator(object): @cherrypy.expose def index(self): return """<html> <head></head> <body> <form method="get" action="generate"> <input type="text" value="8" name="length" /> <button type="submit">Give it now!</button> </form> </body> </html>""" @cherrypy.expose def generate(self, length=8): # mapped to action some_string = ''.join(random.sample(string.hexdigits, int(length))) cherrypy.session['mystring'] = some_string return some_string @cherrypy.expose def display(self): return cherrypy.session['mystring'] if __name__ == '__main__': conf = { '/': { 'tools.sessions.on': True } } cherrypy.quickstart(StringGenerator(), '/', conf)
- client
$ curl -X GET "http://localhost:8080/"
Dispatcher
- urlpath, http method, query-stringによって呼び出すメソッドを変更する
- Dispatcher を継承したクラスを作る
- 登録
conf = { '/': { 'request.dispatch': ForceLowerDispatcher(), } }
cherrypy.dispatch.MethodDispatcher()
- HTTP Methodによって呼び出す関数を変える
Extension from Dispatcher Class
- server
import random import string import cherrypy from cherrypy._cpdispatch import Dispatcher class StringGenerator(object): @cherrypy.expose def generate(self, length=8): return ''.join(random.sample(string.hexdigits, int(length))) class ForceLowerDispatcher(Dispatcher): def __call__(self, path_info): return Dispatcher.__call__(self, path_info.lower()) if __name__ == '__main__': conf = { '/': { 'request.dispatch': ForceLowerDispatcher(), } } cherrypy.quickstart(StringGenerator(), '/', conf)
- client
$ curl -X GET "http://localhost:8080/generate?length=8" $ curl -X GET "http://localhost:8080/GENerAte?length=8"
Restful Dispatching (URL Router)
_cp_dispatch
- server
import cherrypy class Band(object): def __init__(self): self.albums = Album() def _cp_dispatch(self, vpath): if len(vpath) == 1: cherrypy.request.params['name'] = vpath.pop() return self if len(vpath) == 3: cherrypy.request.params['artist'] = vpath.pop(0) # /band name/ vpath.pop(0) # /albums/ cherrypy.request.params['title'] = vpath.pop(0) # /album title/ return self.albums return vpath @cherrypy.expose def index(self, name): return 'About %s...' % name class Album(object): @cherrypy.expose def index(self, artist, title): return 'About %s by %s...' % (title, artist) if __name__ == '__main__': cherrypy.quickstart(Band())
- client
$ curl -X GET "http://localhost:8080/nirvana/" $ curl -X GET "http://localhost:8080/nirvana/albums/nevermind/"
The popargs decorator
- vpath.popの代わり
- 基本的設計
- server
import cherrypy @cherrypy.popargs('name') class Band(object): def __init__(self): self.album = Album() @cherrypy.expose def index(self, name): return 'About %s...' % name @cherrypy.expose def albums(self, name): """ """ albums = ["a", "b", "c"] return 'Albums about %s are %s' % (name, str(albums)) pass @cherrypy.popargs('title') class Album(object): def __init__(self): self.track = Track() pass @cherrypy.expose def index(self, name, title): return 'About %s by %s...' % (title, name) pass @cherrypy.expose def traks(self, name, title): tracks = ["a", "b", "c"] return 'Traks about %s by %s are %s' % (title, name, str(tracks)) pass @cherrypy.popargs('num', 'track') class Track(object): @cherrypy.expose def index(self, name, title, num, track): return 'About %s by %s at num:%s,track:%s' % (title, name, num, track) @cherrypy.expose def details(self, name, title, num, track, **params): """ e.g., $ curl -X GET "http://localhost:8080/nirvana/album/nevermind/track/06/polly/details/" """ arg1 = params["arg1"] arg2 = params["arg2"] print arg1, arg2 return 'Detail About %s by %s at num:%s,track:%s' % (title, name, num, track) pass if __name__ == '__main__': cherrypy.quickstart(Band())
- client
$ curl -X GET "http://localhost:8080/nirvana/album/nevermind/track/06/polly/details?arg1=1&arg2=2"
Multiple Applications
single application
- easiest way
cherrypy.quickstart(Blog()) cherrypy.quickstart(Blog(), '/blog') cherrypy.quickstart(Blog(), '/blog', {'/': {'tools.gzip.on': True}})
multiple applications
- usual way
cherrypy.tree.mount(Blog(), '/blog', blog_conf) cherrypy.tree.mount(Forum(), '/forum', forum_conf) cherrypy.engine.start() cherrypy.engine.block()
RestAPI設計方針
- ここで言うパラメータとパラメータ値はapipath構成要素のこと
- apipath構成要素のパラメータはクラス
- apipath構成要素のパラメータ値はcherrypy.popargs(param_val)
- apipath構成要素のパラメータをクラスにもたせるときは,パラメータの名前と同じメンバ変数とする
- object treeの子でpopargsする時に,親でpopargsした変数をpopargsすること.
- get parameterは**kwargs(マップなら何でもOK)で受け取る
- GETならこんな感じ,/noun1/val1/noun2/val1/noun3/val3?details=k1=v1&k2=v2
- url root pathとroot classを対応させる
- 上記popargsの例がほぼこれ.
- nounsは別にメソッドを作成する
- google play storeを参考にしてもいいかも
- /pathto/nouns/
- https://play.google.com/store/apps
- application一覧のページ
- /pathto/nouns/details?id=noun
- https://play.google.com/store/apps/details?id=jp.co.siliconstudio.sagaofishtaria&hl=ja
- application固有のページ
- nounの詳細は,detailsにGETパラメータで対応させる
- /pathto/nouns/
Tips
Trailing Slash
- /path/to/?を/path/to?にしたい場合
- /path/to/を/path/toにしたい場合
if __name__ == '__main__': conf = { "/": { 'tools.trailing_slash.missing': False } } cherrypy.quickstart(ControllerClass(), "/", conf)
Class Init
- class initは,一度だけ実行される(クラスはシングルトン的な使われ方をされる)
Autoreloader
- URLs
- ファイルに変更があったときにプロセスを再起動
- importしたファイルなら,変更があったときに読み込ませる,読み込ませないが指定可能
CORS (cross origin resource sharing)
- URLS
- http://stackoverflow.com/questions/25733480/cherrypy-handling-no-access-control-allow-origin-header-is-present-error
- http://blog.edutoolbox.de/?p=114 (いろんなprocess pointsでhookとしてfuncを登録可能)
- example
def CORS(): cherrypy.response.headers["Access-Control-Allow-Origin"] = "*" # mean: CORS to def main(): # before starting cherrypy.tools.CORS = cherrypy.Tool("before_finalize", CORS) conf = { "/": { 'tools.trailing_slash.missing': False, 'tools.CORS.on': True } } cherrypy.quickstart(...)
参考URLs
- host16-212.ixd8.itdd.rdpf.sony.co.jp/
- http://cherrypy.readthedocs.org/en/latest/tutorials.html#tutorial-7-give-us-a-rest
- http://cherrypy.readthedocs.org/en/latest/extend.html#dispatchers
- http://cherrypy.readthedocs.org/en/latest/advanced.html
- http://cherrypy.readthedocs.org/en/latest/advanced.html#restful-style-dispatching
- http://cherrypy.readthedocs.org/en/latest/extend.html#per-request-functions