# mako/runtime.py # Copyright (C) 2006-2016 the Mako authors and contributors # # 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.""" from mako import exceptions, util, compat from mako.compat import compat_builtins import sys class Context(object): """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'] = compat.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 compat_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, compat_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(object): """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(object): """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(object): """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(object): 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(object): """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 = dict([(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] else: 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 = dict([(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 compat.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 = compat.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 = dict([(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 compat.callable(callable_): yield key, compat.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 = compat.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 compat.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: compat.reraise(*sys.exc_info()) 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: raise exceptions.TemplateLookupException(str(compat.exception_as())) 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(as_unicode=True) elif template.bytestring_passthrough: buf = compat.StringIO() else: buf = util.FastEncodingBuffer( as_unicode=as_unicode, 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_func_args(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_func_args(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: compat.reraise(*sys.exc_info()) else: error_template = exceptions.html_error_template() if context._outputting_as_unicode: context._buffer_stack[:] = [ util.FastEncodingBuffer(as_unicode=True)] 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)