codekingpro/portable-devtools
114k
1# mako/runtime.py2# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file>3#4# This module is part of Mako and is released under5# the MIT License: http://www.opensource.org/licenses/mit-license.php6 7"""provides runtime services for templates, including Context,8Namespace, and various helper functions."""9 10import builtins11import functools12import sys13 14from mako import compat15from mako import exceptions16from mako import util17 18 19class Context:20 21 """Provides runtime namespace, output buffer, and various22 callstacks for templates.23 24 See :ref:`runtime_toplevel` for detail on the usage of25 :class:`.Context`.26 27 """28 29 def __init__(self, buffer, **data):30 self._buffer_stack = [buffer]31 32 self._data = data33 34 self._kwargs = data.copy()35 self._with_template = None36 self._outputting_as_unicode = None37 self.namespaces = {}38 39 # "capture" function which proxies to the40 # generic "capture" function41 self._data["capture"] = functools.partial(capture, self)42 43 # "caller" stack used by def calls with content44 self.caller_stack = self._data["caller"] = CallerStack()45 46 def _set_with_template(self, t):47 self._with_template = t48 illegal_names = t.reserved_names.intersection(self._data)49 if illegal_names:50 raise exceptions.NameConflictError(51 "Reserved words passed to render(): %s"52 % ", ".join(illegal_names)53 )54 55 @property56 def lookup(self):57 """Return the :class:`.TemplateLookup` associated58 with this :class:`.Context`.59 60 """61 return self._with_template.lookup62 63 @property64 def kwargs(self):65 """Return the dictionary of top level keyword arguments associated66 with this :class:`.Context`.67 68 This dictionary only includes the top-level arguments passed to69 :meth:`.Template.render`. It does not include names produced within70 the template execution such as local variable names or special names71 such as ``self``, ``next``, etc.72 73 The purpose of this dictionary is primarily for the case that74 a :class:`.Template` accepts arguments via its ``<%page>`` tag,75 which are normally expected to be passed via :meth:`.Template.render`,76 except the template is being called in an inheritance context,77 using the ``body()`` method. :attr:`.Context.kwargs` can then be78 used to propagate these arguments to the inheriting template::79 80 ${next.body(**context.kwargs)}81 82 """83 return self._kwargs.copy()84 85 def push_caller(self, caller):86 """Push a ``caller`` callable onto the callstack for87 this :class:`.Context`."""88 89 self.caller_stack.append(caller)90 91 def pop_caller(self):92 """Pop a ``caller`` callable onto the callstack for this93 :class:`.Context`."""94 95 del self.caller_stack[-1]96 97 def keys(self):98 """Return a list of all names established in this :class:`.Context`."""99 100 return list(self._data.keys())101 102 def __getitem__(self, key):103 if key in self._data:104 return self._data[key]105 else:106 return builtins.__dict__[key]107 108 def _push_writer(self):109 """push a capturing buffer onto this Context and return110 the new writer function."""111 112 buf = util.FastEncodingBuffer()113 self._buffer_stack.append(buf)114 return buf.write115 116 def _pop_buffer_and_writer(self):117 """pop the most recent capturing buffer from this Context118 and return the current writer after the pop.119 120 """121 122 buf = self._buffer_stack.pop()123 return buf, self._buffer_stack[-1].write124 125 def _push_buffer(self):126 """push a capturing buffer onto this Context."""127 128 self._push_writer()129 130 def _pop_buffer(self):131 """pop the most recent capturing buffer from this Context."""132 133 return self._buffer_stack.pop()134 135 def get(self, key, default=None):136 """Return a value from this :class:`.Context`."""137 138 return self._data.get(key, builtins.__dict__.get(key, default))139 140 def write(self, string):141 """Write a string to this :class:`.Context` object's142 underlying output buffer."""143 144 self._buffer_stack[-1].write(string)145 146 def writer(self):147 """Return the current writer function."""148 149 return self._buffer_stack[-1].write150 151 def _copy(self):152 c = Context.__new__(Context)153 c._buffer_stack = self._buffer_stack154 c._data = self._data.copy()155 c._kwargs = self._kwargs156 c._with_template = self._with_template157 c._outputting_as_unicode = self._outputting_as_unicode158 c.namespaces = self.namespaces159 c.caller_stack = self.caller_stack160 return c161 162 def _locals(self, d):163 """Create a new :class:`.Context` with a copy of this164 :class:`.Context`'s current state,165 updated with the given dictionary.166 167 The :attr:`.Context.kwargs` collection remains168 unaffected.169 170 171 """172 173 if not d:174 return self175 c = self._copy()176 c._data.update(d)177 return c178 179 def _clean_inheritance_tokens(self):180 """create a new copy of this :class:`.Context`. with181 tokens related to inheritance state removed."""182 183 c = self._copy()184 x = c._data185 x.pop("self", None)186 x.pop("parent", None)187 x.pop("next", None)188 return c189 190 191class CallerStack(list):192 def __init__(self):193 self.nextcaller = None194 195 def __nonzero__(self):196 return self.__bool__()197 198 def __bool__(self):199 return len(self) and self._get_caller() and True or False200 201 def _get_caller(self):202 # this method can be removed once203 # codegen MAGIC_NUMBER moves past 7204 return self[-1]205 206 def __getattr__(self, key):207 return getattr(self._get_caller(), key)208 209 def _push_frame(self):210 frame = self.nextcaller or None211 self.append(frame)212 self.nextcaller = None213 return frame214 215 def _pop_frame(self):216 self.nextcaller = self.pop()217 218 219class Undefined:220 221 """Represents an undefined value in a template.222 223 All template modules have a constant value224 ``UNDEFINED`` present which is an instance of this225 object.226 227 """228 229 def __str__(self):230 raise NameError("Undefined")231 232 def __nonzero__(self):233 return self.__bool__()234 235 def __bool__(self):236 return False237 238 239UNDEFINED = Undefined()240STOP_RENDERING = ""241 242 243class LoopStack:244 245 """a stack for LoopContexts that implements the context manager protocol246 to automatically pop off the top of the stack on context exit247 """248 249 def __init__(self):250 self.stack = []251 252 def _enter(self, iterable):253 self._push(iterable)254 return self._top255 256 def _exit(self):257 self._pop()258 return self._top259 260 @property261 def _top(self):262 if self.stack:263 return self.stack[-1]264 else:265 return self266 267 def _pop(self):268 return self.stack.pop()269 270 def _push(self, iterable):271 new = LoopContext(iterable)272 if self.stack:273 new.parent = self.stack[-1]274 return self.stack.append(new)275 276 def __getattr__(self, key):277 raise exceptions.RuntimeException("No loop context is established")278 279 def __iter__(self):280 return iter(self._top)281 282 283class LoopContext:284 285 """A magic loop variable.286 Automatically accessible in any ``% for`` block.287 288 See the section :ref:`loop_context` for usage289 notes.290 291 :attr:`parent` -> :class:`.LoopContext` or ``None``292 The parent loop, if one exists.293 :attr:`index` -> `int`294 The 0-based iteration count.295 :attr:`reverse_index` -> `int`296 The number of iterations remaining.297 :attr:`first` -> `bool`298 ``True`` on the first iteration, ``False`` otherwise.299 :attr:`last` -> `bool`300 ``True`` on the last iteration, ``False`` otherwise.301 :attr:`even` -> `bool`302 ``True`` when ``index`` is even.303 :attr:`odd` -> `bool`304 ``True`` when ``index`` is odd.305 """306 307 def __init__(self, iterable):308 self._iterable = iterable309 self.index = 0310 self.parent = None311 312 def __iter__(self):313 for i in self._iterable:314 yield i315 self.index += 1316 317 @util.memoized_instancemethod318 def __len__(self):319 return len(self._iterable)320 321 @property322 def reverse_index(self):323 return len(self) - self.index - 1324 325 @property326 def first(self):327 return self.index == 0328 329 @property330 def last(self):331 return self.index == len(self) - 1332 333 @property334 def even(self):335 return not self.odd336 337 @property338 def odd(self):339 return bool(self.index % 2)340 341 def cycle(self, *values):342 """Cycle through values as the loop progresses."""343 if not values:344 raise ValueError("You must provide values to cycle through")345 return values[self.index % len(values)]346 347 348class _NSAttr:349 def __init__(self, parent):350 self.__parent = parent351 352 def __getattr__(self, key):353 ns = self.__parent354 while ns:355 if hasattr(ns.module, key):356 return getattr(ns.module, key)357 else:358 ns = ns.inherits359 raise AttributeError(key)360 361 362class Namespace:363 364 """Provides access to collections of rendering methods, which365 can be local, from other templates, or from imported modules.366 367 To access a particular rendering method referenced by a368 :class:`.Namespace`, use plain attribute access:369 370 .. sourcecode:: mako371 372 ${some_namespace.foo(x, y, z)}373 374 :class:`.Namespace` also contains several built-in attributes375 described here.376 377 """378 379 def __init__(380 self,381 name,382 context,383 callables=None,384 inherits=None,385 populate_self=True,386 calling_uri=None,387 ):388 self.name = name389 self.context = context390 self.inherits = inherits391 if callables is not None:392 self.callables = {c.__name__: c for c in callables}393 394 callables = ()395 396 module = None397 """The Python module referenced by this :class:`.Namespace`.398 399 If the namespace references a :class:`.Template`, then400 this module is the equivalent of ``template.module``,401 i.e. the generated module for the template.402 403 """404 405 template = None406 """The :class:`.Template` object referenced by this407 :class:`.Namespace`, if any.408 409 """410 411 context = None412 """The :class:`.Context` object for this :class:`.Namespace`.413 414 Namespaces are often created with copies of contexts that415 contain slightly different data, particularly in inheritance416 scenarios. Using the :class:`.Context` off of a :class:`.Namespace` one417 can traverse an entire chain of templates that inherit from418 one-another.419 420 """421 422 filename = None423 """The path of the filesystem file used for this424 :class:`.Namespace`'s module or template.425 426 If this is a pure module-based427 :class:`.Namespace`, this evaluates to ``module.__file__``. If a428 template-based namespace, it evaluates to the original429 template file location.430 431 """432 433 uri = None434 """The URI for this :class:`.Namespace`'s template.435 436 I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`.437 438 This is the equivalent of :attr:`.Template.uri`.439 440 """441 442 _templateuri = None443 444 @util.memoized_property445 def attr(self):446 """Access module level attributes by name.447 448 This accessor allows templates to supply "scalar"449 attributes which are particularly handy in inheritance450 relationships.451 452 .. seealso::453 454 :ref:`inheritance_attr`455 456 :ref:`namespace_attr_for_includes`457 458 """459 return _NSAttr(self)460 461 def get_namespace(self, uri):462 """Return a :class:`.Namespace` corresponding to the given ``uri``.463 464 If the given ``uri`` is a relative URI (i.e. it does not465 contain a leading slash ``/``), the ``uri`` is adjusted to466 be relative to the ``uri`` of the namespace itself. This467 method is therefore mostly useful off of the built-in468 ``local`` namespace, described in :ref:`namespace_local`.469 470 In471 most cases, a template wouldn't need this function, and472 should instead use the ``<%namespace>`` tag to load473 namespaces. However, since all ``<%namespace>`` tags are474 evaluated before the body of a template ever runs,475 this method can be used to locate namespaces using476 expressions that were generated within the body code of477 the template, or to conditionally use a particular478 namespace.479 480 """481 key = (self, uri)482 if key in self.context.namespaces:483 return self.context.namespaces[key]484 ns = TemplateNamespace(485 uri,486 self.context._copy(),487 templateuri=uri,488 calling_uri=self._templateuri,489 )490 self.context.namespaces[key] = ns491 return ns492 493 def get_template(self, uri):494 """Return a :class:`.Template` from the given ``uri``.495 496 The ``uri`` resolution is relative to the ``uri`` of this497 :class:`.Namespace` object's :class:`.Template`.498 499 """500 return _lookup_template(self.context, uri, self._templateuri)501 502 def get_cached(self, key, **kwargs):503 """Return a value from the :class:`.Cache` referenced by this504 :class:`.Namespace` object's :class:`.Template`.505 506 The advantage to this method versus direct access to the507 :class:`.Cache` is that the configuration parameters508 declared in ``<%page>`` take effect here, thereby calling509 up the same configured backend as that configured510 by ``<%page>``.511 512 """513 514 return self.cache.get(key, **kwargs)515 516 @property517 def cache(self):518 """Return the :class:`.Cache` object referenced519 by this :class:`.Namespace` object's520 :class:`.Template`.521 522 """523 return self.template.cache524 525 def include_file(self, uri, **kwargs):526 """Include a file at the given ``uri``."""527 528 _include_file(self.context, uri, self._templateuri, **kwargs)529 530 def _populate(self, d, l):531 for ident in l:532 if ident == "*":533 for k, v in self._get_star():534 d[k] = v535 else:536 d[ident] = getattr(self, ident)537 538 def _get_star(self):539 if self.callables:540 for key in self.callables:541 yield (key, self.callables[key])542 543 def __getattr__(self, key):544 if key in self.callables:545 val = self.callables[key]546 elif self.inherits:547 val = getattr(self.inherits, key)548 else:549 raise AttributeError(550 "Namespace '%s' has no member '%s'" % (self.name, key)551 )552 setattr(self, key, val)553 return val554 555 556class TemplateNamespace(Namespace):557 558 """A :class:`.Namespace` specific to a :class:`.Template` instance."""559 560 def __init__(561 self,562 name,563 context,564 template=None,565 templateuri=None,566 callables=None,567 inherits=None,568 populate_self=True,569 calling_uri=None,570 ):571 self.name = name572 self.context = context573 self.inherits = inherits574 if callables is not None:575 self.callables = {c.__name__: c for c in callables}576 577 if templateuri is not None:578 self.template = _lookup_template(context, templateuri, calling_uri)579 self._templateuri = self.template.module._template_uri580 elif template is not None:581 self.template = template582 self._templateuri = template.module._template_uri583 else:584 raise TypeError("'template' argument is required.")585 586 if populate_self:587 lclcallable, lclcontext = _populate_self_namespace(588 context, self.template, self_ns=self589 )590 591 @property592 def module(self):593 """The Python module referenced by this :class:`.Namespace`.594 595 If the namespace references a :class:`.Template`, then596 this module is the equivalent of ``template.module``,597 i.e. the generated module for the template.598 599 """600 return self.template.module601 602 @property603 def filename(self):604 """The path of the filesystem file used for this605 :class:`.Namespace`'s module or template.606 """607 return self.template.filename608 609 @property610 def uri(self):611 """The URI for this :class:`.Namespace`'s template.612 613 I.e. whatever was sent to :meth:`.TemplateLookup.get_template()`.614 615 This is the equivalent of :attr:`.Template.uri`.616 617 """618 return self.template.uri619 620 def _get_star(self):621 if self.callables:622 for key in self.callables:623 yield (key, self.callables[key])624 625 def get(key):626 callable_ = self.template._get_def_callable(key)627 return functools.partial(callable_, self.context)628 629 for k in self.template.module._exports:630 yield (k, get(k))631 632 def __getattr__(self, key):633 if key in self.callables:634 val = self.callables[key]635 elif self.template.has_def(key):636 callable_ = self.template._get_def_callable(key)637 val = functools.partial(callable_, self.context)638 elif self.inherits:639 val = getattr(self.inherits, key)640 641 else:642 raise AttributeError(643 "Namespace '%s' has no member '%s'" % (self.name, key)644 )645 setattr(self, key, val)646 return val647 648 649class ModuleNamespace(Namespace):650 651 """A :class:`.Namespace` specific to a Python module instance."""652 653 def __init__(654 self,655 name,656 context,657 module,658 callables=None,659 inherits=None,660 populate_self=True,661 calling_uri=None,662 ):663 self.name = name664 self.context = context665 self.inherits = inherits666 if callables is not None:667 self.callables = {c.__name__: c for c in callables}668 669 mod = __import__(module)670 for token in module.split(".")[1:]:671 mod = getattr(mod, token)672 self.module = mod673 674 @property675 def filename(self):676 """The path of the filesystem file used for this677 :class:`.Namespace`'s module or template.678 """679 return self.module.__file__680 681 def _get_star(self):682 if self.callables:683 for key in self.callables:684 yield (key, self.callables[key])685 for key in dir(self.module):686 if key[0] != "_":687 callable_ = getattr(self.module, key)688 if callable(callable_):689 yield key, functools.partial(callable_, self.context)690 691 def __getattr__(self, key):692 if key in self.callables:693 val = self.callables[key]694 elif hasattr(self.module, key):695 callable_ = getattr(self.module, key)696 val = functools.partial(callable_, self.context)697 elif self.inherits:698 val = getattr(self.inherits, key)699 else:700 raise AttributeError(701 "Namespace '%s' has no member '%s'" % (self.name, key)702 )703 setattr(self, key, val)704 return val705 706 707def supports_caller(func):708 """Apply a caller_stack compatibility decorator to a plain709 Python function.710 711 See the example in :ref:`namespaces_python_modules`.712 713 """714 715 def wrap_stackframe(context, *args, **kwargs):716 context.caller_stack._push_frame()717 try:718 return func(context, *args, **kwargs)719 finally:720 context.caller_stack._pop_frame()721 722 return wrap_stackframe723 724 725def capture(context, callable_, *args, **kwargs):726 """Execute the given template def, capturing the output into727 a buffer.728 729 See the example in :ref:`namespaces_python_modules`.730 731 """732 733 if not callable(callable_):734 raise exceptions.RuntimeException(735 "capture() function expects a callable as "736 "its argument (i.e. capture(func, *args, **kwargs))"737 )738 context._push_buffer()739 try:740 callable_(*args, **kwargs)741 finally:742 buf = context._pop_buffer()743 return buf.getvalue()744 745 746def _decorate_toplevel(fn):747 def decorate_render(render_fn):748 def go(context, *args, **kw):749 def y(*args, **kw):750 return render_fn(context, *args, **kw)751 752 try:753 y.__name__ = render_fn.__name__[7:]754 except TypeError:755 # < Python 2.4756 pass757 return fn(y)(context, *args, **kw)758 759 return go760 761 return decorate_render762 763 764def _decorate_inline(context, fn):765 def decorate_render(render_fn):766 dec = fn(render_fn)767 768 def go(*args, **kw):769 return dec(context, *args, **kw)770 771 return go772 773 return decorate_render774 775 776def _include_file(context, uri, calling_uri, **kwargs):777 """locate the template from the given uri and include it in778 the current output."""779 780 template = _lookup_template(context, uri, calling_uri)781 (callable_, ctx) = _populate_self_namespace(782 context._clean_inheritance_tokens(), template783 )784 kwargs = _kwargs_for_include(callable_, context._data, **kwargs)785 if template.include_error_handler:786 try:787 callable_(ctx, **kwargs)788 except Exception:789 result = template.include_error_handler(ctx, compat.exception_as())790 if not result:791 raise792 else:793 callable_(ctx, **kwargs)794 795 796def _inherit_from(context, uri, calling_uri):797 """called by the _inherit method in template modules to set798 up the inheritance chain at the start of a template's799 execution."""800 801 if uri is None:802 return None803 template = _lookup_template(context, uri, calling_uri)804 self_ns = context["self"]805 ih = self_ns806 while ih.inherits is not None:807 ih = ih.inherits808 lclcontext = context._locals({"next": ih})809 ih.inherits = TemplateNamespace(810 "self:%s" % template.uri,811 lclcontext,812 template=template,813 populate_self=False,814 )815 context._data["parent"] = lclcontext._data["local"] = ih.inherits816 callable_ = getattr(template.module, "_mako_inherit", None)817 if callable_ is not None:818 ret = callable_(template, lclcontext)819 if ret:820 return ret821 822 gen_ns = getattr(template.module, "_mako_generate_namespaces", None)823 if gen_ns is not None:824 gen_ns(context)825 return (template.callable_, lclcontext)826 827 828def _lookup_template(context, uri, relativeto):829 lookup = context._with_template.lookup830 if lookup is None:831 raise exceptions.TemplateLookupException(832 "Template '%s' has no TemplateLookup associated"833 % context._with_template.uri834 )835 uri = lookup.adjust_uri(uri, relativeto)836 try:837 return lookup.get_template(uri)838 except exceptions.TopLevelLookupException as e:839 raise exceptions.TemplateLookupException(840 str(compat.exception_as())841 ) from e842 843 844def _populate_self_namespace(context, template, self_ns=None):845 if self_ns is None:846 self_ns = TemplateNamespace(847 "self:%s" % template.uri,848 context,849 template=template,850 populate_self=False,851 )852 context._data["self"] = context._data["local"] = self_ns853 if hasattr(template.module, "_mako_inherit"):854 ret = template.module._mako_inherit(template, context)855 if ret:856 return ret857 return (template.callable_, context)858 859 860def _render(template, callable_, args, data, as_unicode=False):861 """create a Context and return the string862 output of the given template and template callable."""863 864 if as_unicode:865 buf = util.FastEncodingBuffer()866 else:867 buf = util.FastEncodingBuffer(868 encoding=template.output_encoding, errors=template.encoding_errors869 )870 context = Context(buf, **data)871 context._outputting_as_unicode = as_unicode872 context._set_with_template(template)873 874 _render_context(875 template,876 callable_,877 context,878 *args,879 **_kwargs_for_callable(callable_, data),880 )881 return context._pop_buffer().getvalue()882 883 884def _kwargs_for_callable(callable_, data):885 argspec = compat.inspect_getargspec(callable_)886 # for normal pages, **pageargs is usually present887 if argspec[2]:888 return data889 890 # for rendering defs from the top level, figure out the args891 namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]892 kwargs = {}893 for arg in namedargs:894 if arg != "context" and arg in data and arg not in kwargs:895 kwargs[arg] = data[arg]896 return kwargs897 898 899def _kwargs_for_include(callable_, data, **kwargs):900 argspec = compat.inspect_getargspec(callable_)901 namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None]902 for arg in namedargs:903 if arg != "context" and arg in data and arg not in kwargs:904 kwargs[arg] = data[arg]905 return kwargs906 907 908def _render_context(tmpl, callable_, context, *args, **kwargs):909 import mako.template as template910 911 # create polymorphic 'self' namespace for this912 # template with possibly updated context913 if not isinstance(tmpl, template.DefTemplate):914 # if main render method, call from the base of the inheritance stack915 (inherit, lclcontext) = _populate_self_namespace(context, tmpl)916 _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)917 else:918 # otherwise, call the actual rendering method specified919 (inherit, lclcontext) = _populate_self_namespace(context, tmpl.parent)920 _exec_template(callable_, context, args=args, kwargs=kwargs)921 922 923def _exec_template(callable_, context, args=None, kwargs=None):924 """execute a rendering callable given the callable, a925 Context, and optional explicit arguments926 927 the contextual Template will be located if it exists, and928 the error handling options specified on that Template will929 be interpreted here.930 """931 template = context._with_template932 if template is not None and (933 template.format_exceptions or template.error_handler934 ):935 try:936 callable_(context, *args, **kwargs)937 except Exception:938 _render_error(template, context, compat.exception_as())939 except:940 e = sys.exc_info()[0]941 _render_error(template, context, e)942 else:943 callable_(context, *args, **kwargs)944 945 946def _render_error(template, context, error):947 if template.error_handler:948 result = template.error_handler(context, error)949 if not result:950 tp, value, tb = sys.exc_info()951 if value and tb:952 raise value.with_traceback(tb)953 else:954 raise error955 else:956 error_template = exceptions.html_error_template()957 if context._outputting_as_unicode:958 context._buffer_stack[:] = [util.FastEncodingBuffer()]959 else:960 context._buffer_stack[:] = [961 util.FastEncodingBuffer(962 error_template.output_encoding,963 error_template.encoding_errors,964 )965 ]966 967 context._set_with_template(error_template)968 error_template.render_context(context, error=error)969 