Newer
Older
"""provides the Template class, a facade for parsing, generating and executing template strings,
as well as template runtime operations."""
from mako.lexer import Lexer
from mako.codegen import Compiler
from mako.runtime import Context
_modules = weakref.WeakValueDictionary()
_inmemory_templates = weakref.WeakValueDictionary()
class _ModuleMarker(object):
"""enables weak-referencing to module instances"""
def __init__(self, module):
self.module = module
def __init__(self, text=None, module=None, identifier=None, filename=None, format_exceptions=True, error_handler=None):
"""construct a new Template instance using either literal template text, or a previously loaded template module
text - textual template source, or None if a module is to be provided
module - a Python module, such as loaded via __import__ or similar. the module should contain at least one
function render(context) that renders with the given context.
identifier - the "id" of this template. defaults to the identifier of the given module, or for text
the hex string of this Template's object id
filename - filename of the source template, compiled into the module.
format_exceptions - if caught exceptions should be formatted as template output, including a stack
trace adjusted to the source template
self.identifier = identifier or "memory:" + hex(id(self))
(code, module) = _compile_text(text, self.identifier, filename)
self.callable_ = self.module.render
self.format_exceptions = format_exceptions
self.error_handler = error_handler
_modules[module.__name__] = _ModuleMarker(module)
source = property(lambda self:_get_template_source(self.callable_), doc="""return the template source code for this Template.""")
code = property(lambda self:_get_module_source(self.callable_), doc="""return the module source code for this Template""")
def render(self, *args, **data):
"""render the output of this template as a string.
a Context object is created corresponding to the given data. Arguments that are explictly
declared by this template's internal rendering method are also pulled from the given *args, **data
members."""
return _render(self, self.callable_, *args, **data)
def render_context(self, context, *args, **kwargs):
"""render this Template with the given context.
the data is written to the context's buffer."""
_render_context(self, self.callable_, context, *args, **kwargs)
def get_component(self, name):
"""return a component of this template as an individual Template of its own."""
return ComponentTemplate(self, getattr(self.module, "render_%s" % name))
class ComponentTemplate(Template):
"""a Template which represents a callable component in a parent template."""
def __init__(self, parent, callable_):
self.parent = parent
def get_component(self, name):
return self.parent.get_component(name)
def _compile_text(text, identifier, filename):
node = Lexer(text).parse()
source = Compiler(node).render()
def _render(template, callable_, *args, **data):
"""given a Template and a callable_ from that template, create a Context and return the string output."""
buf = StringIO()
context = Context(buf, **data)
kwargs = {}
argspec = inspect.getargspec(callable_)
namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]
for arg in namedargs:
if arg != 'context' and arg in data:
kwargs[arg] = data[arg]
_render_context(template, callable_, context, *args, **kwargs)
def _render_context(template, callable_, context, *args, **kwargs):
context.with_template = template
_exec_template(callable_, context, args=args, kwargs=kwargs)
def _exec_template(callable_, context, args=None, kwargs=None):
"""execute a rendering callable given the callable, a Context, and optional explicit arguments
the contextual Template will be located if it exists, and the error handling options specified
on that Template will be interpreted here.
"""
template = context.with_template
if template is not None and (template.format_exceptions or template.error_handler):
error = None
try:
callable_(context, *args, **kwargs)
except Exception, e:
error = e
except:
e = sys.exc_info()[0]
error = e
if error:
if template.error_handler:
result = template.error_handler(context, error)
if not result:
raise error
else:
# TODO
source = _get_template_source(callable_)
raise error
else:
callable_(context, *args, **kwargs)
def _get_template_source(callable_):
"""return the source code for the template that produced the given rendering callable"""
name = callable_.func_globals['__name__']
try:
template = _inmemory_templates[name]
except KeyError:
module = _modules[name].module
filename = module._template_filename
if filename is None:
if not filename:
raise exceptions.RuntimeException("Cant get source code or template filename for template: %s" % name)
return file(filename).read()
def _get_module_source(callable_):
name = callable_.func_globals['__name__']
try:
template = _inmemory_templates[name]
return template._code
except KeyError:
module = _modules[name].module
filename = module.__file__
if filename is None:
if not filename:
raise exceptions.RuntimeException("Cant get module source code or module filename for template: %s" % name)