template側で定義したmoduleをnamespaceから取り出セルようにする

template側で定義したmoduleをnamespaceから取り出セルようにする(wip)

namespaceの取り出し

通常namespaceは2通りの方法でimportできる - file経由のimport - module経由のimport

file経由のimportは以下の様なもの

<%namespace name="mf" file="<abtstract>.mako"/>

module経由のimportは以下もの

<%namespace name="mf" module="foo.bar.boo"/>

テンプレート上に<%def>で定義したファイルについては、file経由のimportを使うのが普通。mako.lookup.LookupTemplateのdirectoriesに入っていれば相対パスで呼べるが。例えばライブラリで提供するものについてはmodule経由でimportしたい。

%defで定義したmakoモジュールをpythonモジュールから使えるようにする。

以下の様にするとcompileされた後のコードが手に入る。

# -*- coding:utf-8 -*-
import os.path
from mako.template import Template

template = Template("""
<%def name="tag(tag)">
${tag}${caller.body()}${tag}
</%def>

<%def name="hello()">
<%self:tag tag="hello">
${caller.body()}
</%self:tag>
</%def>

${hello()}
<%self:hello>
hai
</%self:hello>
""")

code = template.code
print(code.replace(os.path.abspath(os.path.dirname(__file__)), "./"))

以下の様な出力を得る。

from mako import runtime, filters, cache
UNDEFINED = runtime.UNDEFINED
__M_dict_builtin = dict
__M_locals_builtin = locals
_magic_number = 10
_modified_time = 1427816076.348679
_enable_loop = True
_template_filename = None
_template_uri = 'memory:0x102e9ae10'
_source_encoding = 'ascii'
_exports = ['hello', 'tag']


def render_body(context,**pageargs):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_locals = __M_dict_builtin(pageargs=pageargs)
        def hello():
            return render_hello(context._locals(__M_locals))
        self = context.get('self', UNDEFINED)
        __M_writer = context.writer()
        __M_writer('\n')
        __M_writer('\n\n')
        __M_writer('\n\n')
        __M_writer(str(hello()))
        __M_writer('\n')
        def ccall(caller):
            def body():
                __M_writer = context.writer()
                __M_writer('\nhai\n')
                return ''
            return [body]
        context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(__M_caller))
        try:
            __M_writer(str(self.hello()))
        finally:
            context.caller_stack.nextcaller = None
        __M_writer('\n')
        return ''
    finally:
        context.caller_stack._pop_frame()


def render_hello(context):
    __M_caller = context.caller_stack._push_frame()
    try:
        self = context.get('self', UNDEFINED)
        __M_writer = context.writer()
        __M_writer('\n')
        def ccall(caller):
            def body():
                __M_writer = context.writer()
                __M_writer('\n')
                __M_writer(str(caller.body()))
                __M_writer('\n')
                return ''
            return [body]
        context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(__M_caller))
        try:
            __M_writer(str(self.tag(tag='hello')))
        finally:
            context.caller_stack.nextcaller = None
        __M_writer('\n')
        return ''
    finally:
        context.caller_stack._pop_frame()


def render_tag(context,tag):
    __M_caller = context.caller_stack._push_frame()
    try:
        caller = context.get('caller', UNDEFINED)
        __M_writer = context.writer()
        __M_writer('\n')
        __M_writer(str(tag))
        __M_writer(str(caller.body()))
        __M_writer(str(tag))
        __M_writer('\n')
        return ''
    finally:
        context.caller_stack._pop_frame()


"""
__M_BEGIN_METADATA
{"filename": null, "uri": "memory:0x102e9ae10", "source_encoding": "ascii", "line_map": {"65": 2, "34": 12, "70": 2, "71": 3, "40": 5, "73": 3, "74": 3, "45": 5, "14": 0, "80": 74, "49": 6, "50": 7, "51": 7, "20": 1, "21": 4, "22": 9, "56": 6, "72": 3, "26": 10, "59": 8, "31": 10}}
__M_END_METADATA
"""
  • 全てrenderが前置された関数に変換される
  • template全体自体はrender_bodyに変換される。
  • ${hello()}render_hello()を使った形式に変更される

このままだとまともに使えそうにないのでコードを見る

  • mako.parsetree:NamespaceTagが<%namespace .../>に対応する
  • ここからNamespace objectが作られる

2つは本質的に違うもの。

# <%namespace module="foo.boo" name="boo"/>

def _mako_get_namespace(context, name):
    try:
        return context.namespaces[(__name__, name)]
    except KeyError:
        _mako_generate_namespaces(context)
        return context.namespaces[(__name__, name)]
def _mako_generate_namespaces(context):
    ns = runtime.ModuleNamespace('boo', context._clean_inheritance_tokens(), callables=None, calling_uri=_template_uri, module='foo.boo')
    context.namespaces[(__name__, 'boo')] = ns

file経由の場合はmako.runtime.TemplateNamespaceが使われる

# <%namespace file="foo.boo" name="boo"/>

def _mako_get_namespace(context, name):
    try:
        return context.namespaces[(__name__, name)]
    except KeyError:
        _mako_generate_namespaces(context)
        return context.namespaces[(__name__, name)]
def _mako_generate_namespaces(context):
    ns = runtime.TemplateNamespace('boo', context._clean_inheritance_tokens(), templateuri='foo.boo', callables=None,  calling_uri=_template_uri)
    context.namespaces[(__name__, 'boo')] = ns

なんか無理そう。

追記

現状は以下のようにlookupのdirectoriesに追加できるだけ

from mako.lookup import TemplateLookup
from mako.template import Template
import os.path
import foo

lookup = TemplateLookup(directories=[os.path.dirname(foo.__file__)])
template = Template("""
<%namespace file="foo.mako" name="foo"/>

<%foo:hello>
hai
</%foo:hello>
""", lookup=lookup)

print(template.render())