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)
使い方
- アプリケーション単位ごとにdirectory(モジュール)を作る。
- jinja2のテンプレートはアプリケーション以下templates/を見に行くのでそこに置く。
- 適当に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使いたかっただけなんですけどねー。