Doge log

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

Nevowにurlresolverを装備させる

Nevowはそのままだと非常に使いにくい。
というかそのぶん自由なんだけど。
個人的にはdjangoのurlresolverはなんだかんだいって
使いやすいのでこいつを組み込むといいなあと思ったのでやってみる。
パクリ

reolver.py

import re

class Resolver404(Exception):
    pass

class NoReverseMatch(Exception):
    silent_variable_failure = True

def get_mod_func(callback):
    try:
        dot = callback.rindex('.')
    except ValueError:
        return callback, ''
    return callback[:dot], callback[dot+1:]

class RegexURLPattern(object):
    def __init__(self, regex, callback, default_args=None):
        self.regex = re.compile(regex)
        if callable(callback):
            self._callback = callback
        else:
            self._callback = None
        self._callback_str = callback
        self.default_args = default_args or {}

    def resolve(self, path):
        match = self.regex.search(path)
        print "path ", path, match
        if match:
            kwargs = match.groupdict()
            if kwargs:
                args = ()
            else:
                args = match.groups()
            kwargs.update(self.default_args)
            return self.callback, args, kwargs

    def _get_callback(self):
        if self._callback is not None:
            return self._callback
        mod_name, func_name = get_mod_func(self._callback_str)
        try:
            mod = __import__(mod_name, {}, {}, [''])
            self._callback = getattr(__import__(mod_name, {}, {}, ['']), func_name)
        except ImportError, e:
            raise ViewDoesNotExist, "Could not import %s. Error was: %s" % (mod_name, str(e))
        except AttributeError, e:
            raise ViewDoesNotExist, "Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e))
        return self._callback
    callback = property(_get_callback)


class RegexURLResolver(object):
    def __init__(self, regex, urlconf_name, default_kwargs=None):
        self.regex = re.compile(regex)
        self.urlconf_name = urlconf_name
        self.callback = None
        self.default_kwargs = default_kwargs or {}

    def resolve(self, path):
        tried = []
        match = self.regex.search(path)
        if match:
            new_path = path[match.end():]
            for pattern in self.urlconf_module.urlpatterns:
                try:
                    sub_match = pattern.resolve(new_path)
                except Resolver404, e:
                    tried.extend([(pattern.regex.pattern + '   ' + t) for t in e.args[0]['tried']])
                else:
                    if sub_match:
                        sub_match_dict = dict(self.default_kwargs, **sub_match[2])
                        return sub_match[0], sub_match[1], dict(match.groupdict(), **sub_match_dict)
                    tried.append(pattern.regex.pattern)
            raise Resolver404, {'tried': tried, 'path': new_path}

    def _get_urlconf_module(self):
        try:
            return self._urlconf_module
        except AttributeError:
            try:
                self._urlconf_module = __import__(self.urlconf_name, {}, {}, [''])
            except ValueError, e:
                # Invalid urlconf_name, such as "foo.bar." (note trailing period)
                raise ImproperlyConfigured, "Error while importing URLconf %r: %s" % (self.urlconf_name, e)
            return self._urlconf_module
    urlconf_module = property(_get_urlconf_module)

    def _get_url_patterns(self):
        return self.urlconf_module.urlpatterns
    url_patterns = property(_get_url_patterns)

    def _resolve_special(self, view_type):
        callback = getattr(self.urlconf_module, 'handler%s' % view_type)
        mod_name, func_name = get_mod_func(callback)
        try:
            return getattr(__import__(mod_name, {}, {}, ['']), func_name), {}
        except (ImportError, AttributeError), e:
            raise Exception, "Tried %s. Error was: %s" % (callback, str(e))

    def resolve404(self):
        return self._resolve_special('404')

    def resolve500(self):
        return self._resolve_special('500')

def resolve(path, urlconf=None):
    resolver = RegexURLResolver(r'^/', urlconf)
    return resolver.resolve(path)

include = lambda urlconf_module: [urlconf_module]

def patterns(prefix, *tuples):
    pattern_list = []
    for t in tuples:
        regex, view_or_include = t[:2]
        default_kwargs = t[2:]
        if type(view_or_include) == list:
            pattern_list.append(RegexURLResolver(regex, view_or_include[0], *default_kwargs))
        else:
            pattern_list.append(RegexURLPattern(regex, prefix and (prefix + '.' + view_or_include) or view_or_include, *default_kwargs))
    return pattern_list

まあいつもとおり。
includeも拾えるようにしておく。
でRootPage

root.py

from nevow import inevow, loaders, rend, url, tags as T
from twisted.web import static
import resolver

class RootPage(rend.Page):
    addSlash = True    
    def locateChild(self, ctx, segments):
        request = inevow.IRequest(ctx)
        method = request.method
        path = "/".join(segments)
        try:
            callback, args, kwargs = resolver.resolve("/"+path, "url")
            page = callback(*args, **kwargs)
            if not len(segments):
                return page, ()
            else:
                return page, ()
        except:
            return rend.Page.locateChild(self, ctx, segments)

class BasePage(RootPage):
    docFactory = loaders.htmlfile(templateDir='templates', template='base.html')

    child_css = static.File('css')
    #child_images = static.File('static/images')

    def render_content(self, context, data):
        tag = context.tag.clear()
        if hasattr(self, "contentTemplateFile"):
            tag[loaders.htmlfile(templateDir='templates', template=self.contentTemplateFile)]
            return tag
        return loaders.stan(self.contentStan)

class IndexPage(BasePage):
    addSlash = True
    contentTemplateFile = 'index.html'

URL設定はurl.pyに記述している。
中身はdjangoと同じ。詳しく書かない。
こいつをRootとしてtwistedを起動するのでこんな感じ。

sample.tac

from twisted.application import internet
from twisted.application import service
from nevow import appserver
from core import root

application = service.Application('sample')

appResource = root.BasePage()

site = appserver.NevowSite(appResource)
webServer = internet.TCPServer(8080, site)
webServer.setServiceParent(application)

こうするとURLとPageの組み合わせが自由!
さらにURLの一部をパラメータに使える!
formlessを使うと死ぬのでformalを使う必要アリだけどね。
NevowはPage駆動なのでteedaライクなんだけどそいつについてはあとで書く。
Nevowはtwistedベースなのでテストは・・・・・。ってのもあとで書く。
うくく。