Doge log

Abby CTO 雑賀 力王のオフィシャルサイトです

GAE用のutil

GAEだとそんなに複雑なものも作らないだろうというわけで。
シンプルな構成で作れるものを書いてみた。
GAE用なのでけっこー適当。

import logging
import os
import sys
from os import path
from werkzeug import Request, Response
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError, NotFound
from jinja2 import Environment, FileSystemLoader

url_maps = {}

TEMPLATE_PATH = path.dirname(__file__)
jinja_env = Environment(
    loader=FileSystemLoader(TEMPLATE_PATH, encoding='utf8'),
    autoescape=True
)

def _get_urlmap(name):
    for k in url_maps:
        if name.startswith(k):
            return url_maps[k]

def render_to_response(template, context, mimetype='text/html'):
    namespace = sys._getframe(1).f_globals
    dir = namespace['__name__'].split('.')[0]
    templ_path = path.join(dir, 'templates', template)
    #logging.info(templ_path)
    tmpl = jinja_env.get_template(templ_path)
    return Response(tmpl.render(context), mimetype=mimetype)

def render_text(template, context):
    namespace = sys._getframe(1).f_globals
    dir = namespace['__name__'].split('.')[0]
    templ_path = path.join(dir, 'templates', template)
    #logging.info(templ_path)
    tmpl = jinja_env.get_template(templ_path)
    return tmpl.render(context)

def get_app_url():
    namespace = sys._getframe(1).f_globals
    name = namespace['__name__']
    map = _get_urlmap(name)
    if map:
        return map.get('app_url', '')
    return ''

def expose(rule, **kw):
    url_map = None
    views = None
    namespace = sys._getframe(1).f_globals
    name = namespace['__name__']
    map = _get_urlmap(name)
    if map:
        url_map = map['url_map']
        views = map['views']
    if not url_map:
        raise ValueError("url_setting not found :%s" % name)
    views.add(sys.modules[name])
    def decorate(f):
        kw['endpoint'] = f.__name__
        url_map.add(Rule(rule, **kw))
        #logging.info("add %s %s" % (rule, f.__name__))
        return f
    return decorate


def _import(name):
    app_mod = __import__(name, {}, {}, [])
    app_dir = path.dirname(getattr(app_mod, '__file__'))
    for file in os.listdir(app_dir):
        file, ext = path.splitext(file)
        if not file.startswith('__') and ext == '.py':
            m = __import__('.'.join([name, file]))

class GenericApplication(object):

    def __init__(self, app_module, url=None):
        self.app_name = app_module
        self.app_url = url
        url_map = Map()
        views = set()
        setting = url_maps.get(app_module, None)
        if setting:
            url_map = setting['url_map']
            views = setting['views']
        self.url_map = url_map
        self.views = views
        if not setting:
            url_maps[app_module] = dict(
                    app = self,
                    url_map=self.url_map,
                    views=self.views,
                    app_url=url)
            _import(app_module)

        
    def __call__(self, environ, start_response):
        request = Request(environ)
        adapter = self.url_map.bind_to_environ(environ)
        response = None
        try:
            endpoint, values = adapter.match()
            for view in self.views:
                handler = getattr(view, endpoint, None)
                #logging.info("search %s %s " % (getattr(view, '__name__'),
                #    endpoint))
                if handler:
                    response = handler(request, **values)
                    break
            if not response:
                raise NotFound
        except HTTPException, e:
            logging.warning(e)
            response = e
        return response(environ, start_response)

使い方

  1. アプリケーション単位ごとにdirectory(モジュール)を作る。
  2. jinja2のテンプレートはアプリケーション以下templates/を見に行くのでそこに置く。
  3. 適当にviews.pyとかにexposeでurlマッピングするだけ。
from app import expose, render_to_response

@expose('/')
def index(req):
    return render_to_response('index.html', {})

小規模であればurls.pyのようにわざわざ外にurlの設定を書かなくても十分見渡せる。
(Bespinなんかもexpose)
でかくなった時はサブアプリ化、Dispatchで振り分けるという方法でカバーするのでurlもそこまで増えない。

サブアプリケーション、複雑なURLなど

Dispatchで振り分ける前提なので以下のような感じ。
app2とかapp3とかってモジュールにロジック書いておく。

import wsgiref.handlers
from google.appengine.ext.webapp import util
from werkzeug.utils import DispatcherMiddleware
from app import GenericApplication

def main():
    application = GenericApplication('app1')
    application = DispatcherMiddleware(application,{
        '/app2' : GenericApplication('app2', '/app2'),
        '/app3' : GenericApplication('app3', '/app3')
        })

    import os
    import sys
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
    main()

まあDispatcherMiddleware使いたかっただけなんですけどねー。