# mako/runtime.py # Copyright 2006-2020 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.""" 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)