You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
969 lines
27 KiB
969 lines
27 KiB
1 year ago
|
# mako/runtime.py
|
||
|
# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file>
|
||
|
#
|
||
|
# This module is part of Mako and is released under
|
||
|
# the MIT License: http://www.opensource.org/licenses/mit-license.php
|
||
|
|
||
|
"""provides runtime services for templates, including Context,
|
||
|
Namespace, and various helper functions."""
|
||
|
|
||
|
import builtins
|
||
|
import functools
|
||
|
import sys
|
||
|
|
||
|
from mako import compat
|
||
|
from mako import exceptions
|
||
|
from mako import util
|
||
|
|
||
|
|
||
|
class Context:
|
||
|
|
||
|
"""Provides runtime namespace, output buffer, and various
|
||
|
callstacks for templates.
|
||
|
|
||
|
See :ref:`runtime_toplevel` for detail on the usage of
|
||
|
:class:`.Context`.
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(self, buffer, **data):
|
||
|
self._buffer_stack = [buffer]
|
||
|
|
||
|
self._data = data
|
||
|
|
||
|
self._kwargs = data.copy()
|
||
|
self._with_template = None
|
||
|
self._outputting_as_unicode = None
|
||
|
self.namespaces = {}
|
||
|
|
||
|
# "capture" function which proxies to the
|
||
|
# generic "capture" function
|
||
|
self._data["capture"] = functools.partial(capture, self)
|
||
|
|
||
|
# "caller" stack used by def calls with content
|
||
|
self.caller_stack = self._data["caller"] = CallerStack()
|
||
|
|
||
|
def _set_with_template(self, t):
|
||
|
self._with_template = t
|
||
|
illegal_names = t.reserved_names.intersection(self._data)
|
||
|
if illegal_names:
|
||
|
raise exceptions.NameConflictError(
|
||
|
"Reserved words passed to render(): %s"
|
||
|
% ", ".join(illegal_names)
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def lookup(self):
|
||
|
"""Return the :class:`.TemplateLookup` associated
|
||
|
with this :class:`.Context`.
|
||
|
|
||
|
"""
|
||
|
return self._with_template.lookup
|
||
|
|
||
|
@property
|
||
|
def kwargs(self):
|
||
|
"""Return the dictionary of top level keyword arguments associated
|
||
|
with this :class:`.Context`.
|
||
|
|
||
|
This dictionary only includes the top-level arguments passed to
|
||
|
:meth:`.Template.render`. It does not include names produced within
|
||
|
the template execution such as local variable names or special names
|
||
|
such as ``self``, ``next``, etc.
|
||
|
|
||
|
The purpose of this dictionary is primarily for the case that
|
||
|
a :class:`.Template` accepts arguments via its ``<%page>`` tag,
|
||
|
which are normally expected to be passed via :meth:`.Template.render`,
|
||
|
except the template is being called in an inheritance context,
|
||
|
using the ``body()`` method. :attr:`.Context.kwargs` can then be
|
||
|
used to propagate these arguments to the inheriting template::
|
||
|
|
||
|
${next.body(**context.kwargs)}
|
||
|
|
||
|
"""
|
||
|
return self._kwargs.copy()
|
||
|
|
||
|
def push_caller(self, caller):
|
||
|
"""Push a ``caller`` callable onto the callstack for
|
||
|
this :class:`.Context`."""
|
||
|
|
||
|
self.caller_stack.append(caller)
|
||
|
|
||
|
def pop_caller(self):
|
||
|
"""Pop a ``caller`` callable onto the callstack for this
|
||
|
:class:`.Context`."""
|
||
|
|
||
|
del self.caller_stack[-1]
|
||
|
|
||
|
def keys(self):
|
||
|
"""Return a list of all names established in this :class:`.Context`."""
|
||
|
|
||
|
return list(self._data.keys())
|
||
|
|
||
|
def __getitem__(self, key):
|
||
|
if key in self._data:
|
||
|
return self._data[key]
|
||
|
else:
|
||
|
return builtins.__dict__[key]
|
||
|
|
||
|
def _push_writer(self):
|
||
|
"""push a capturing buffer onto this Context and return
|
||
|
the new writer function."""
|
||
|
|
||
|
buf = util.FastEncodingBuffer()
|
||
|
self._buffer_stack.append(buf)
|
||
|
return buf.write
|
||
|
|
||
|
def _pop_buffer_and_writer(self):
|
||
|
"""pop the most recent capturing buffer from this Context
|
||
|
and return the current writer after the pop.
|
||
|
|
||
|
"""
|
||
|
|
||
|
buf = self._buffer_stack.pop()
|
||
|
return buf, self._buffer_stack[-1].write
|
||
|
|
||
|
def _push_buffer(self):
|
||
|
"""push a capturing buffer onto this Context."""
|
||
|
|
||
|
self._push_writer()
|
||
|
|
||
|
def _pop_buffer(self):
|
||
|
"""pop the most recent capturing buffer from this Context."""
|
||
|
|
||
|
return self._buffer_stack.pop()
|
||
|
|
||
|
def get(self, key, default=None):
|
||
|
"""Return a value from this :class:`.Context`."""
|
||
|
|
||
|
return self._data.get(key, builtins.__dict__.get(key, default))
|
||
|
|
||
|
def write(self, string):
|
||
|
"""Write a string to this :class:`.Context` object's
|
||
|
underlying output buffer."""
|
||
|
|
||
|
self._buffer_stack[-1].write(string)
|
||
|
|
||
|
def writer(self):
|
||
|
"""Return the current writer function."""
|
||
|
|
||
|
return self._buffer_stack[-1].write
|
||
|
|
||
|
def _copy(self):
|
||
|
c = Context.__new__(Context)
|
||
|
c._buffer_stack = self._buffer_stack
|
||
|
c._data = self._data.copy()
|
||
|
c._kwargs = self._kwargs
|
||
|
c._with_template = self._with_template
|
||
|
c._outputting_as_unicode = self._outputting_as_unicode
|
||
|
c.namespaces = self.namespaces
|
||
|
c.caller_stack = self.caller_stack
|
||
|
return c
|
||
|
|
||
|
def _locals(self, d):
|
||
|
"""Create a new :class:`.Context` with a copy of this
|
||
|
:class:`.Context`'s current state,
|
||
|
updated with the given dictionary.
|
||
|
|
||
|
The :attr:`.Context.kwargs` collection remains
|
||
|
unaffected.
|
||
|
|
||
|
|
||
|
"""
|
||
|
|
||
|
if not d:
|
||
|
return self
|
||
|
c = self._copy()
|
||
|
c._data.update(d)
|
||
|
return c
|
||
|
|
||
|
def _clean_inheritance_tokens(self):
|
||
|
"""create a new copy of this :class:`.Context`. with
|
||
|
tokens related to inheritance state removed."""
|
||
|
|
||
|
c = self._copy()
|
||
|
x = c._data
|
||
|
x.pop("self", None)
|
||
|
x.pop("parent", None)
|
||
|
x.pop("next", None)
|
||
|
return c
|
||
|
|
||
|
|
||
|
class CallerStack(list):
|
||
|
def __init__(self):
|
||
|
self.nextcaller = None
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return self.__bool__()
|
||
|
|
||
|
def __bool__(self):
|
||
|
return len(self) and self._get_caller() and True or False
|
||
|
|
||
|
def _get_caller(self):
|
||
|
# this method can be removed once
|
||
|
# codegen MAGIC_NUMBER moves past 7
|
||
|
return self[-1]
|
||
|
|
||
|
def __getattr__(self, key):
|
||
|
return getattr(self._get_caller(), key)
|
||
|
|
||
|
def _push_frame(self):
|
||
|
frame = self.nextcaller or None
|
||
|
self.append(frame)
|
||
|
self.nextcaller = None
|
||
|
return frame
|
||
|
|
||
|
def _pop_frame(self):
|
||
|
self.nextcaller = self.pop()
|
||
|
|
||
|
|
||
|
class Undefined:
|
||
|
|
||
|
"""Represents an undefined value in a template.
|
||
|
|
||
|
All template modules have a constant value
|
||
|
``UNDEFINED`` present which is an instance of this
|
||
|
object.
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __str__(self):
|
||
|
raise NameError("Undefined")
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return self.__bool__()
|
||
|
|
||
|
def __bool__(self):
|
||
|
return False
|
||
|
|
||
|
|
||
|
UNDEFINED = Undefined()
|
||
|
STOP_RENDERING = ""
|
||
|
|
||
|
|
||
|
class LoopStack:
|
||
|
|
||
|
"""a stack for LoopContexts that implements the context manager protocol
|
||
|
to automatically pop off the top of the stack on context exit
|
||
|
"""
|
||
|
|
||
|
def __init__(self):
|
||
|
self.stack = []
|
||
|
|
||
|
def _enter(self, iterable):
|
||
|
self._push(iterable)
|
||
|
return self._top
|
||
|
|
||
|
def _exit(self):
|
||
|
self._pop()
|
||
|
return self._top
|
||
|
|
||
|
@property
|
||
|
def _top(self):
|
||
|
if self.stack:
|
||
|
return self.stack[-1]
|
||
|
else:
|
||
|
return self
|
||
|
|
||
|
def _pop(self):
|
||
|
return self.stack.pop()
|
||
|
|
||
|
def _push(self, iterable):
|
||
|
new = LoopContext(iterable)
|
||
|
if self.stack:
|
||
|
new.parent = self.stack[-1]
|
||
|
return self.stack.append(new)
|
||
|
|
||
|
def __getattr__(self, key):
|
||
|
raise exceptions.RuntimeException("No loop context is established")
|
||
|
|
||
|
def __iter__(self):
|
||
|
return iter(self._top)
|
||
|
|
||
|
|
||
|
class LoopContext:
|
||
|
|
||
|
"""A magic loop variable.
|
||
|
Automatically accessible in any ``% for`` block.
|
||
|
|
||
|
See the section :ref:`loop_context` for usage
|
||
|
notes.
|
||
|
|
||
|
:attr:`parent` -> :class:`.LoopContext` or ``None``
|
||
|
The parent loop, if one exists.
|
||
|
:attr:`index` -> `int`
|
||
|
The 0-based iteration count.
|
||
|
:attr:`reverse_index` -> `int`
|
||
|
The number of iterations remaining.
|
||
|
:attr:`first` -> `bool`
|
||
|
``True`` on the first iteration, ``False`` otherwise.
|
||
|
:attr:`last` -> `bool`
|
||
|
``True`` on the last iteration, ``False`` otherwise.
|
||
|
:attr:`even` -> `bool`
|
||
|
``True`` when ``index`` is even.
|
||
|
:attr:`odd` -> `bool`
|
||
|
``True`` when ``index`` is odd.
|
||
|
"""
|
||
|
|
||
|
def __init__(self, iterable):
|
||
|
self._iterable = iterable
|
||
|
self.index = 0
|
||
|
self.parent = None
|
||
|
|
||
|
def __iter__(self):
|
||
|
for i in self._iterable:
|
||
|
yield i
|
||
|
self.index += 1
|
||
|
|
||
|
@util.memoized_instancemethod
|
||
|
def __len__(self):
|
||
|
return len(self._iterable)
|
||
|
|
||
|
@property
|
||
|
def reverse_index(self):
|
||
|
return len(self) - self.index - 1
|
||
|
|
||
|
@property
|
||
|
def first(self):
|
||
|
return self.index == 0
|
||
|
|
||
|
@property
|
||
|
def last(self):
|
||
|
return self.index == len(self) - 1
|
||
|
|
||
|
@property
|
||
|
def even(self):
|
||
|
return not self.odd
|
||
|
|
||
|
@property
|
||
|
def odd(self):
|
||
|
return bool(self.index % 2)
|
||
|
|
||
|
def cycle(self, *values):
|
||
|
"""Cycle through values as the loop progresses."""
|
||
|
if not values:
|
||
|
raise ValueError("You must provide values to cycle through")
|
||
|
return values[self.index % len(values)]
|
||
|
|
||
|
|
||
|
class _NSAttr:
|
||
|
def __init__(self, parent):
|
||
|
self.__parent = parent
|
||
|
|
||
|
def __getattr__(self, key):
|
||
|
ns = self.__parent
|
||
|
while ns:
|
||
|
if hasattr(ns.module, key):
|
||
|
return getattr(ns.module, key)
|
||
|
else:
|
||
|
ns = ns.inherits
|
||
|
raise AttributeError(key)
|
||
|
|
||
|
|
||
|
class Namespace:
|
||
|
|
||
|
"""Provides access to collections of rendering methods, which
|
||
|
can be local, from other templates, or from imported modules.
|
||
|
|
||
|
To access a particular rendering method referenced by a
|
||
|
:class:`.Namespace`, use plain attribute access:
|
||
|
|
||
|
.. sourcecode:: mako
|
||
|
|
||
|
${some_namespace.foo(x, y, z)}
|
||
|
|
||
|
:class:`.Namespace` also contains several built-in attributes
|
||
|
described here.
|
||
|
|
||
|
"""
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
name,
|
||
|
context,
|
||
|
callables=None,
|
||
|
inherits=None,
|
||
|
populate_self=True,
|
||
|
calling_uri=None,
|
||
|
):
|
||
|
self.name = name
|
||
|
self.context = context
|
||
|
self.inherits = inherits
|
||
|
if callables is not None:
|
||
|
self.callables = {c.__name__: c for c in callables}
|
||
|
|
||
|
callables = ()
|
||
|
|
||
|
module = None
|
||
|
"""The Python module referenced by this :class:`.Namespace`.
|
||
|
|
||
|
If the namespace references a :class:`.Template`, then
|
||
|
this module is the equivalent of ``template.module``,
|
||
|
i.e. the generated module for the template.
|
||
|
|
||
|
"""
|
||
|
|
||
|
template = None
|
||
|
"""The :class:`.Template` object referenced by this
|
||
|
:class:`.Namespace`, if any.
|
||
|
|
||
|
"""
|
||
|
|
||
|
context = None
|
||
|
"""The :class:`.Context` object for this :class:`.Namespace`.
|
||
|
|
||
|
Namespaces are often created with copies of contexts that
|
||
|
contain slightly different data, particularly in inheritance
|
||
|
scenarios. Using the :class:`.Context` off of a :class:`.Namespace` one
|
||
|
can traverse an entire chain of templates that inherit from
|
||
|
one-another.
|
||
|
|
||
|
"""
|
||
|
|
||
|
filename = None
|
||
|
"""The path of the filesystem file used for this
|
||
|
:class:`.Namespace`'s module or template.
|
||
|
|
||
|
If this is a pure module-based
|
||
|
:class:`.Namespace`, this evaluates to ``module.__file__``. If a
|
||
|
template-based namespace, it evaluates to the original
|
||
|
template file location.
|
||
|
|
||
|
"""
|
||
|
|
||
|
uri = None
|
||
|
"""The URI for this :class:`.Namespace`'s template.
|
||
|
|
||
|
I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`.
|
||
|
|
||
|
This is the equivalent of :attr:`.Template.uri`.
|
||
|
|
||
|
"""
|
||
|
|
||
|
_templateuri = None
|
||
|
|
||
|
@util.memoized_property
|
||
|
def attr(self):
|
||
|
"""Access module level attributes by name.
|
||
|
|
||
|
This accessor allows templates to supply "scalar"
|
||
|
attributes which are particularly handy in inheritance
|
||
|
relationships.
|
||
|
|
||
|
.. seealso::
|
||
|
|
||
|
:ref:`inheritance_attr`
|
||
|
|
||
|
:ref:`namespace_attr_for_includes`
|
||
|
|
||
|
"""
|
||
|
return _NSAttr(self)
|
||
|
|
||
|
def get_namespace(self, uri):
|
||
|
"""Return a :class:`.Namespace` corresponding to the given ``uri``.
|
||
|
|
||
|
If the given ``uri`` is a relative URI (i.e. it does not
|
||
|
contain a leading slash ``/``), the ``uri`` is adjusted to
|
||
|
be relative to the ``uri`` of the namespace itself. This
|
||
|
method is therefore mostly useful off of the built-in
|
||
|
``local`` namespace, described in :ref:`namespace_local`.
|
||
|
|
||
|
In
|
||
|
most cases, a template wouldn't need this function, and
|
||
|
should instead use the ``<%namespace>`` tag to load
|
||
|
namespaces. However, since all ``<%namespace>`` tags are
|
||
|
evaluated before the body of a template ever runs,
|
||
|
this method can be used to locate namespaces using
|
||
|
expressions that were generated within the body code of
|
||
|
the template, or to conditionally use a particular
|
||
|
namespace.
|
||
|
|
||
|
"""
|
||
|
key = (self, uri)
|
||
|
if key in self.context.namespaces:
|
||
|
return self.context.namespaces[key]
|
||
|
ns = TemplateNamespace(
|
||
|
uri,
|
||
|
self.context._copy(),
|
||
|
templateuri=uri,
|
||
|
calling_uri=self._templateuri,
|
||
|
)
|
||
|
self.context.namespaces[key] = ns
|
||
|
return ns
|
||
|
|
||
|
def get_template(self, uri):
|
||
|
"""Return a :class:`.Template` from the given ``uri``.
|
||
|
|
||
|
The ``uri`` resolution is relative to the ``uri`` of this
|
||
|
:class:`.Namespace` object's :class:`.Template`.
|
||
|
|
||
|
"""
|
||
|
return _lookup_template(self.context, uri, self._templateuri)
|
||
|
|
||
|
def get_cached(self, key, **kwargs):
|
||
|
"""Return a value from the :class:`.Cache` referenced by this
|
||
|
:class:`.Namespace` object's :class:`.Template`.
|
||
|
|
||
|
The advantage to this method versus direct access to the
|
||
|
:class:`.Cache` is that the configuration parameters
|
||
|
declared in ``<%page>`` take effect here, thereby calling
|
||
|
up the same configured backend as that configured
|
||
|
by ``<%page>``.
|
||
|
|
||
|
"""
|
||
|
|
||
|
return self.cache.get(key, **kwargs)
|
||
|
|
||
|
@property
|
||
|
def cache(self):
|
||
|
"""Return the :class:`.Cache` object referenced
|
||
|
by this :class:`.Namespace` object's
|
||
|
:class:`.Template`.
|
||
|
|
||
|
"""
|
||
|
return self.template.cache
|
||
|
|
||
|
def include_file(self, uri, **kwargs):
|
||
|
"""Include a file at the given ``uri``."""
|
||
|
|
||
|
_include_file(self.context, uri, self._templateuri, **kwargs)
|
||
|
|
||
|
def _populate(self, d, l):
|
||
|
for ident in l:
|
||
|
if ident == "*":
|
||
|
for (k, v) in self._get_star():
|
||
|
d[k] = v
|
||
|
else:
|
||
|
d[ident] = getattr(self, ident)
|
||
|
|
||
|
def _get_star(self):
|
||
|
if self.callables:
|
||
|
for key in self.callables:
|
||
|
yield (key, self.callables[key])
|
||
|
|
||
|
def __getattr__(self, key):
|
||
|
if key in self.callables:
|
||
|
val = self.callables[key]
|
||
|
elif self.inherits:
|
||
|
val = getattr(self.inherits, key)
|
||
|
else:
|
||
|
raise AttributeError(
|
||
|
"Namespace '%s' has no member '%s'" % (self.name, key)
|
||
|
)
|
||
|
setattr(self, key, val)
|
||
|
return val
|
||
|
|
||
|
|
||
|
class TemplateNamespace(Namespace):
|
||
|
|
||
|
"""A :class:`.Namespace` specific to a :class:`.Template` instance."""
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
name,
|
||
|
context,
|
||
|
template=None,
|
||
|
templateuri=None,
|
||
|
callables=None,
|
||
|
inherits=None,
|
||
|
populate_self=True,
|
||
|
calling_uri=None,
|
||
|
):
|
||
|
self.name = name
|
||
|
self.context = context
|
||
|
self.inherits = inherits
|
||
|
if callables is not None:
|
||
|
self.callables = {c.__name__: c for c in callables}
|
||
|
|
||
|
if templateuri is not None:
|
||
|
self.template = _lookup_template(context, templateuri, calling_uri)
|
||
|
self._templateuri = self.template.module._template_uri
|
||
|
elif template is not None:
|
||
|
self.template = template
|
||
|
self._templateuri = template.module._template_uri
|
||
|
else:
|
||
|
raise TypeError("'template' argument is required.")
|
||
|
|
||
|
if populate_self:
|
||
|
lclcallable, lclcontext = _populate_self_namespace(
|
||
|
context, self.template, self_ns=self
|
||
|
)
|
||
|
|
||
|
@property
|
||
|
def module(self):
|
||
|
"""The Python module referenced by this :class:`.Namespace`.
|
||
|
|
||
|
If the namespace references a :class:`.Template`, then
|
||
|
this module is the equivalent of ``template.module``,
|
||
|
i.e. the generated module for the template.
|
||
|
|
||
|
"""
|
||
|
return self.template.module
|
||
|
|
||
|
@property
|
||
|
def filename(self):
|
||
|
"""The path of the filesystem file used for this
|
||
|
:class:`.Namespace`'s module or template.
|
||
|
"""
|
||
|
return self.template.filename
|
||
|
|
||
|
@property
|
||
|
def uri(self):
|
||
|
"""The URI for this :class:`.Namespace`'s template.
|
||
|
|
||
|
I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`.
|
||
|
|
||
|
This is the equivalent of :attr:`.Template.uri`.
|
||
|
|
||
|
"""
|
||
|
return self.template.uri
|
||
|
|
||
|
def _get_star(self):
|
||
|
if self.callables:
|
||
|
for key in self.callables:
|
||
|
yield (key, self.callables[key])
|
||
|
|
||
|
def get(key):
|
||
|
callable_ = self.template._get_def_callable(key)
|
||
|
return functools.partial(callable_, self.context)
|
||
|
|
||
|
for k in self.template.module._exports:
|
||
|
yield (k, get(k))
|
||
|
|
||
|
def __getattr__(self, key):
|
||
|
if key in self.callables:
|
||
|
val = self.callables[key]
|
||
|
elif self.template.has_def(key):
|
||
|
callable_ = self.template._get_def_callable(key)
|
||
|
val = functools.partial(callable_, self.context)
|
||
|
elif self.inherits:
|
||
|
val = getattr(self.inherits, key)
|
||
|
|
||
|
else:
|
||
|
raise AttributeError(
|
||
|
"Namespace '%s' has no member '%s'" % (self.name, key)
|
||
|
)
|
||
|
setattr(self, key, val)
|
||
|
return val
|
||
|
|
||
|
|
||
|
class ModuleNamespace(Namespace):
|
||
|
|
||
|
"""A :class:`.Namespace` specific to a Python module instance."""
|
||
|
|
||
|
def __init__(
|
||
|
self,
|
||
|
name,
|
||
|
context,
|
||
|
module,
|
||
|
callables=None,
|
||
|
inherits=None,
|
||
|
populate_self=True,
|
||
|
calling_uri=None,
|
||
|
):
|
||
|
self.name = name
|
||
|
self.context = context
|
||
|
self.inherits = inherits
|
||
|
if callables is not None:
|
||
|
self.callables = {c.__name__: c for c in callables}
|
||
|
|
||
|
mod = __import__(module)
|
||
|
for token in module.split(".")[1:]:
|
||
|
mod = getattr(mod, token)
|
||
|
self.module = mod
|
||
|
|
||
|
@property
|
||
|
def filename(self):
|
||
|
"""The path of the filesystem file used for this
|
||
|
:class:`.Namespace`'s module or template.
|
||
|
"""
|
||
|
return self.module.__file__
|
||
|
|
||
|
def _get_star(self):
|
||
|
if self.callables:
|
||
|
for key in self.callables:
|
||
|
yield (key, self.callables[key])
|
||
|
for key in dir(self.module):
|
||
|
if key[0] != "_":
|
||
|
callable_ = getattr(self.module, key)
|
||
|
if callable(callable_):
|
||
|
yield key, functools.partial(callable_, self.context)
|
||
|
|
||
|
def __getattr__(self, key):
|
||
|
if key in self.callables:
|
||
|
val = self.callables[key]
|
||
|
elif hasattr(self.module, key):
|
||
|
callable_ = getattr(self.module, key)
|
||
|
val = functools.partial(callable_, self.context)
|
||
|
elif self.inherits:
|
||
|
val = getattr(self.inherits, key)
|
||
|
else:
|
||
|
raise AttributeError(
|
||
|
"Namespace '%s' has no member '%s'" % (self.name, key)
|
||
|
)
|
||
|
setattr(self, key, val)
|
||
|
return val
|
||
|
|
||
|
|
||
|
def supports_caller(func):
|
||
|
"""Apply a caller_stack compatibility decorator to a plain
|
||
|
Python function.
|
||
|
|
||
|
See the example in :ref:`namespaces_python_modules`.
|
||
|
|
||
|
"""
|
||
|
|
||
|
def wrap_stackframe(context, *args, **kwargs):
|
||
|
context.caller_stack._push_frame()
|
||
|
try:
|
||
|
return func(context, *args, **kwargs)
|
||
|
finally:
|
||
|
context.caller_stack._pop_frame()
|
||
|
|
||
|
return wrap_stackframe
|
||
|
|
||
|
|
||
|
def capture(context, callable_, *args, **kwargs):
|
||
|
"""Execute the given template def, capturing the output into
|
||
|
a buffer.
|
||
|
|
||
|
See the example in :ref:`namespaces_python_modules`.
|
||
|
|
||
|
"""
|
||
|
|
||
|
if not callable(callable_):
|
||
|
raise exceptions.RuntimeException(
|
||
|
"capture() function expects a callable as "
|
||
|
"its argument (i.e. capture(func, *args, **kwargs))"
|
||
|
)
|
||
|
context._push_buffer()
|
||
|
try:
|
||
|
callable_(*args, **kwargs)
|
||
|
finally:
|
||
|
buf = context._pop_buffer()
|
||
|
return buf.getvalue()
|
||
|
|
||
|
|
||
|
def _decorate_toplevel(fn):
|
||
|
def decorate_render(render_fn):
|
||
|
def go(context, *args, **kw):
|
||
|
def y(*args, **kw):
|
||
|
return render_fn(context, *args, **kw)
|
||
|
|
||
|
try:
|
||
|
y.__name__ = render_fn.__name__[7:]
|
||
|
except TypeError:
|
||
|
# < Python 2.4
|
||
|
pass
|
||
|
return fn(y)(context, *args, **kw)
|
||
|
|
||
|
return go
|
||
|
|
||
|
return decorate_render
|
||
|
|
||
|
|
||
|
def _decorate_inline(context, fn):
|
||
|
def decorate_render(render_fn):
|
||
|
dec = fn(render_fn)
|
||
|
|
||
|
def go(*args, **kw):
|
||
|
return dec(context, *args, **kw)
|
||
|
|
||
|
return go
|
||
|
|
||
|
return decorate_render
|
||
|
|
||
|
|
||
|
def _include_file(context, uri, calling_uri, **kwargs):
|
||
|
"""locate the template from the given uri and include it in
|
||
|
the current output."""
|
||
|
|
||
|
template = _lookup_template(context, uri, calling_uri)
|
||
|
(callable_, ctx) = _populate_self_namespace(
|
||
|
context._clean_inheritance_tokens(), template
|
||
|
)
|
||
|
kwargs = _kwargs_for_include(callable_, context._data, **kwargs)
|
||
|
if template.include_error_handler:
|
||
|
try:
|
||
|
callable_(ctx, **kwargs)
|
||
|
except Exception:
|
||
|
result = template.include_error_handler(ctx, compat.exception_as())
|
||
|
if not result:
|
||
|
raise
|
||
|
else:
|
||
|
callable_(ctx, **kwargs)
|
||
|
|
||
|
|
||
|
def _inherit_from(context, uri, calling_uri):
|
||
|
"""called by the _inherit method in template modules to set
|
||
|
up the inheritance chain at the start of a template's
|
||
|
execution."""
|
||
|
|
||
|
if uri is None:
|
||
|
return None
|
||
|
template = _lookup_template(context, uri, calling_uri)
|
||
|
self_ns = context["self"]
|
||
|
ih = self_ns
|
||
|
while ih.inherits is not None:
|
||
|
ih = ih.inherits
|
||
|
lclcontext = context._locals({"next": ih})
|
||
|
ih.inherits = TemplateNamespace(
|
||
|
"self:%s" % template.uri,
|
||
|
lclcontext,
|
||
|
template=template,
|
||
|
populate_self=False,
|
||
|
)
|
||
|
context._data["parent"] = lclcontext._data["local"] = ih.inherits
|
||
|
callable_ = getattr(template.module, "_mako_inherit", None)
|
||
|
if callable_ is not None:
|
||
|
ret = callable_(template, lclcontext)
|
||
|
if ret:
|
||
|
return ret
|
||
|
|
||
|
gen_ns = getattr(template.module, "_mako_generate_namespaces", None)
|
||
|
if gen_ns is not None:
|
||
|
gen_ns(context)
|
||
|
return (template.callable_, lclcontext)
|
||
|
|
||
|
|
||
|
def _lookup_template(context, uri, relativeto):
|
||
|
lookup = context._with_template.lookup
|
||
|
if lookup is None:
|
||
|
raise exceptions.TemplateLookupException(
|
||
|
"Template '%s' has no TemplateLookup associated"
|
||
|
% context._with_template.uri
|
||
|
)
|
||
|
uri = lookup.adjust_uri(uri, relativeto)
|
||
|
try:
|
||
|
return lookup.get_template(uri)
|
||
|
except exceptions.TopLevelLookupException as e:
|
||
|
raise exceptions.TemplateLookupException(
|
||
|
str(compat.exception_as())
|
||
|
) from e
|
||
|
|
||
|
|
||
|
def _populate_self_namespace(context, template, self_ns=None):
|
||
|
if self_ns is None:
|
||
|
self_ns = TemplateNamespace(
|
||
|
"self:%s" % template.uri,
|
||
|
context,
|
||
|
template=template,
|
||
|
populate_self=False,
|
||
|
)
|
||
|
context._data["self"] = context._data["local"] = self_ns
|
||
|
if hasattr(template.module, "_mako_inherit"):
|
||
|
ret = template.module._mako_inherit(template, context)
|
||
|
if ret:
|
||
|
return ret
|
||
|
return (template.callable_, context)
|
||
|
|
||
|
|
||
|
def _render(template, callable_, args, data, as_unicode=False):
|
||
|
"""create a Context and return the string
|
||
|
output of the given template and template callable."""
|
||
|
|
||
|
if as_unicode:
|
||
|
buf = util.FastEncodingBuffer()
|
||
|
else:
|
||
|
buf = util.FastEncodingBuffer(
|
||
|
encoding=template.output_encoding, errors=template.encoding_errors
|
||
|
)
|
||
|
context = Context(buf, **data)
|
||
|
context._outputting_as_unicode = as_unicode
|
||
|
context._set_with_template(template)
|
||
|
|
||
|
_render_context(
|
||
|
template,
|
||
|
callable_,
|
||
|
context,
|
||
|
*args,
|
||
|
**_kwargs_for_callable(callable_, data),
|
||
|
)
|
||
|
return context._pop_buffer().getvalue()
|
||
|
|
||
|
|
||
|
def _kwargs_for_callable(callable_, data):
|
||
|
argspec = compat.inspect_getargspec(callable_)
|
||
|
# for normal pages, **pageargs is usually present
|
||
|
if argspec[2]:
|
||
|
return data
|
||
|
|
||
|
# for rendering defs from the top level, figure out the args
|
||
|
namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]
|
||
|
kwargs = {}
|
||
|
for arg in namedargs:
|
||
|
if arg != "context" and arg in data and arg not in kwargs:
|
||
|
kwargs[arg] = data[arg]
|
||
|
return kwargs
|
||
|
|
||
|
|
||
|
def _kwargs_for_include(callable_, data, **kwargs):
|
||
|
argspec = compat.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 and arg not in kwargs:
|
||
|
kwargs[arg] = data[arg]
|
||
|
return kwargs
|
||
|
|
||
|
|
||
|
def _render_context(tmpl, callable_, context, *args, **kwargs):
|
||
|
import mako.template as template
|
||
|
|
||
|
# create polymorphic 'self' namespace for this
|
||
|
# template with possibly updated context
|
||
|
if not isinstance(tmpl, template.DefTemplate):
|
||
|
# if main render method, call from the base of the inheritance stack
|
||
|
(inherit, lclcontext) = _populate_self_namespace(context, tmpl)
|
||
|
_exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
|
||
|
else:
|
||
|
# otherwise, call the actual rendering method specified
|
||
|
(inherit, lclcontext) = _populate_self_namespace(context, tmpl.parent)
|
||
|
_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
|
||
|
):
|
||
|
try:
|
||
|
callable_(context, *args, **kwargs)
|
||
|
except Exception:
|
||
|
_render_error(template, context, compat.exception_as())
|
||
|
except:
|
||
|
e = sys.exc_info()[0]
|
||
|
_render_error(template, context, e)
|
||
|
else:
|
||
|
callable_(context, *args, **kwargs)
|
||
|
|
||
|
|
||
|
def _render_error(template, context, error):
|
||
|
if template.error_handler:
|
||
|
result = template.error_handler(context, error)
|
||
|
if not result:
|
||
|
tp, value, tb = sys.exc_info()
|
||
|
if value and tb:
|
||
|
raise value.with_traceback(tb)
|
||
|
else:
|
||
|
raise error
|
||
|
else:
|
||
|
error_template = exceptions.html_error_template()
|
||
|
if context._outputting_as_unicode:
|
||
|
context._buffer_stack[:] = [util.FastEncodingBuffer()]
|
||
|
else:
|
||
|
context._buffer_stack[:] = [
|
||
|
util.FastEncodingBuffer(
|
||
|
error_template.output_encoding,
|
||
|
error_template.encoding_errors,
|
||
|
)
|
||
|
]
|
||
|
|
||
|
context._set_with_template(error_template)
|
||
|
error_template.render_context(context, error=error)
|