taisablog.com

taisa's engineer blog

Python Bottleのソースを読む ルータ編

Pythonの軽量WebフレームワークBottleのソースを読む ルータ編

Class Hierarchy

20160208112338

ここの部分

Bottleのルータについて

bottleのルータは特に継承関係はなくRouteとRouterクラスで構成されている

Routerの役割

A Router is an ordered collection of route->target pairs. It is used to efficiently match WSGI requests against a number of routes and return the first target that satisfies the request. The target may be anything, usually a string, ID or callable object. A route consists of a path-rule and a HTTP method.
The path-rule is either a static path (e.g. `/contact`) or a dynamic path that contains wildcards (e.g. `/wiki/`). The wildcard syntax and details on the matching order are described in docs:`routing`.

RouterはRoute情報のコレクションを保持する。

Routeの役割

This class wraps a route callback along with route specific metadata and configuration and applies Plugins on demand. It is also responsible for turing an URL path rule into a regular expression usable by the Router.

Routeはrouteに対するcallback関数やroute特有の情報を保持するオブジェクト。RouteオブジェクトをRouterが一覧で保持することになる。

ルータの使い方

ルータは以下のようにデコレータを指定する。@getや@postはそれぞれGETのみPOSTのみを受け付ける

@route('/routes')
def routes():
    return 'routes'
@get('/get')
def get():
    return 'get'
@post('/post')
def post():
    return 'post'

デコレータには以下が用意されている

route     = make_default_app_wrapper('route')
get       = make_default_app_wrapper('get')
post      = make_default_app_wrapper('post')
put       = make_default_app_wrapper('put')
delete    = make_default_app_wrapper('delete')
patch     = make_default_app_wrapper('patch')
error     = make_default_app_wrapper('error')
mount     = make_default_app_wrapper('mount')
hook      = make_default_app_wrapper('hook')
install   = make_default_app_wrapper('install')
uninstall = make_default_app_wrapper('uninstall')
url       = make_default_app_wrapper('get_url')

ルータの処理

まず起動時のBottleインスタンス化時(default_appが生成されAppStackにpushされる)に、コンストラクタにてRouterがセットされる。

app = default_app = AppStack()
app.push()

コントローラのデコレータを読み込む

@get('/get')
def get():
    return 'get'

make_default_app_wrapperが呼ばれBottleのdef getが呼ばれる

def make_default_app_wrapper(name):
    """ Return a callable that relays calls to the current default app. """
    @functools.wraps(getattr(Bottle, name))
    def wrapper(*a, **ka):
        return getattr(app(), name)(*a, **ka)
    return wrapper

def getなどはrouteを少しラップしたもの

def get(self, path=None, method='GET', **options):
    """ Equals :meth:`route`. """
    return self.route(path, method, **options)
def post(self, path=None, method='POST', **options):
    """ Equals :meth:`route` with a ``POST`` method parameter. """
    return self.route(path, method, **options)
def put(self, path=None, method='PUT', **options):
    """ Equals :meth:`route` with a ``PUT`` method parameter. """
    return self.route(path, method, **options)
def delete(self, path=None, method='DELETE', **options):
    """ Equals :meth:`route` with a ``DELETE`` method parameter. """
    return self.route(path, method, **options)
def patch(self, path=None, method='PATCH', **options):
    """ Equals :meth:`route` with a ``PATCH`` method parameter. """
    return self.route(path, method, **options)

routeのdecoratorでRouteをインスタンス化(loadでcallback functionを取得しruleとセットで渡す)しBottleのroutesとRouterにrouteを追加する。これらを繰り返しrouteをすべて読み込む。これでpathから特定のactionを呼び出すことができるようになる。

def decorator(callback):
    if isinstance(callback, basestring): callback = load(callback)
    for rule in makelist(path) or yieldroutes(callback):
        for verb in makelist(method):
            verb = verb.upper()
            route = Route(self, rule, verb, callback,
                          name=name,
                          plugins=plugins,
                          skiplist=skiplist, **config)
            self.add_route(route)
    return callback