Update mako to 1.1.0

This commit is contained in:
JonnyWong16 2019-11-23 18:57:21 -08:00
commit f2d7beec90
27 changed files with 2424 additions and 1890 deletions

View file

@ -1,5 +1,5 @@
# mako/template.py
# Copyright (C) 2006-2015 the Mako authors and contributors <see AUTHORS file>
# Copyright 2006-2019 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
@ -7,8 +7,7 @@
"""Provides the Template class, a facade for parsing, generating and executing
template strings, as well as template runtime operations."""
from mako.lexer import Lexer
from mako import runtime, util, exceptions, codegen, cache, compat
import json
import os
import re
import shutil
@ -18,9 +17,18 @@ import tempfile
import types
import weakref
from mako import cache
from mako import codegen
from mako import compat
from mako import exceptions
from mako import runtime
from mako import util
from mako.lexer import Lexer
class Template(object):
"""Represents a compiled template.
r"""Represents a compiled template.
:class:`.Template` includes a reference to the original
template source (via the :attr:`.source` attribute)
@ -108,6 +116,11 @@ class Template(object):
completes. Is used to provide custom error-rendering
functions.
.. seealso::
:paramref:`.Template.include_error_handler` - include-specific
error handler function
:param format_exceptions: if ``True``, exceptions which occur during
the render phase of this template will be caught and
formatted into an HTML error page, which then becomes the
@ -128,6 +141,16 @@ class Template(object):
import will not appear as the first executed statement in the generated
code and will therefore not have the desired effect.
:param include_error_handler: An error handler that runs when this template
is included within another one via the ``<%include>`` tag, and raises an
error. Compare to the :paramref:`.Template.error_handler` option.
.. versionadded:: 1.0.6
.. seealso::
:paramref:`.Template.error_handler` - top-level error handler function
:param input_encoding: Encoding of the template's source code. Can
be used in lieu of the coding comment. See
:ref:`usage_unicode` as well as :ref:`unicode_toplevel` for
@ -214,40 +237,43 @@ class Template(object):
lexer_cls = Lexer
def __init__(self,
text=None,
filename=None,
uri=None,
format_exceptions=False,
error_handler=None,
lookup=None,
output_encoding=None,
encoding_errors='strict',
module_directory=None,
cache_args=None,
cache_impl='beaker',
cache_enabled=True,
cache_type=None,
cache_dir=None,
cache_url=None,
module_filename=None,
input_encoding=None,
disable_unicode=False,
module_writer=None,
bytestring_passthrough=False,
default_filters=None,
buffer_filters=(),
strict_undefined=False,
imports=None,
future_imports=None,
enable_loop=True,
preprocessor=None,
lexer_cls=None):
def __init__(
self,
text=None,
filename=None,
uri=None,
format_exceptions=False,
error_handler=None,
lookup=None,
output_encoding=None,
encoding_errors="strict",
module_directory=None,
cache_args=None,
cache_impl="beaker",
cache_enabled=True,
cache_type=None,
cache_dir=None,
cache_url=None,
module_filename=None,
input_encoding=None,
disable_unicode=False,
module_writer=None,
bytestring_passthrough=False,
default_filters=None,
buffer_filters=(),
strict_undefined=False,
imports=None,
future_imports=None,
enable_loop=True,
preprocessor=None,
lexer_cls=None,
include_error_handler=None,
):
if uri:
self.module_id = re.sub(r'\W', "_", uri)
self.module_id = re.sub(r"\W", "_", uri)
self.uri = uri
elif filename:
self.module_id = re.sub(r'\W', "_", filename)
self.module_id = re.sub(r"\W", "_", filename)
drive, path = os.path.splitdrive(filename)
path = os.path.normpath(path).replace(os.path.sep, "/")
self.uri = path
@ -261,9 +287,10 @@ class Template(object):
u_norm = os.path.normpath(u_norm)
if u_norm.startswith(".."):
raise exceptions.TemplateLookupException(
"Template uri \"%s\" is invalid - "
"it cannot be relative outside "
"of the root path." % self.uri)
'Template uri "%s" is invalid - '
"it cannot be relative outside "
"of the root path." % self.uri
)
self.input_encoding = input_encoding
self.output_encoding = output_encoding
@ -276,17 +303,18 @@ class Template(object):
if compat.py3k and disable_unicode:
raise exceptions.UnsupportedError(
"Mako for Python 3 does not "
"support disabling Unicode")
"Mako for Python 3 does not " "support disabling Unicode"
)
elif output_encoding and disable_unicode:
raise exceptions.UnsupportedError(
"output_encoding must be set to "
"None when disable_unicode is used.")
"output_encoding must be set to "
"None when disable_unicode is used."
)
if default_filters is None:
if compat.py3k or self.disable_unicode:
self.default_filters = ['str']
self.default_filters = ["str"]
else:
self.default_filters = ['unicode']
self.default_filters = ["unicode"]
else:
self.default_filters = default_filters
self.buffer_filters = buffer_filters
@ -303,7 +331,7 @@ class Template(object):
(code, module) = _compile_text(self, text, filename)
self._code = code
self._source = text
ModuleInfo(module, None, self, filename, code, text)
ModuleInfo(module, None, self, filename, code, text, uri)
elif filename is not None:
# if template filename and a module directory, load
# a filesystem-based module file, generating if needed
@ -311,43 +339,53 @@ class Template(object):
path = module_filename
elif module_directory is not None:
path = os.path.abspath(
os.path.join(
os.path.normpath(module_directory),
u_norm + ".py"
)
)
os.path.join(
os.path.normpath(module_directory), u_norm + ".py"
)
)
else:
path = None
module = self._compile_from_file(path, filename)
else:
raise exceptions.RuntimeException(
"Template requires text or filename")
"Template requires text or filename"
)
self.module = module
self.filename = filename
self.callable_ = self.module.render_body
self.format_exceptions = format_exceptions
self.error_handler = error_handler
self.include_error_handler = include_error_handler
self.lookup = lookup
self.module_directory = module_directory
self._setup_cache_args(
cache_impl, cache_enabled, cache_args,
cache_type, cache_dir, cache_url
cache_impl,
cache_enabled,
cache_args,
cache_type,
cache_dir,
cache_url,
)
@util.memoized_property
def reserved_names(self):
if self.enable_loop:
return codegen.RESERVED_NAMES
else:
return codegen.RESERVED_NAMES.difference(['loop'])
return codegen.RESERVED_NAMES.difference(["loop"])
def _setup_cache_args(self,
cache_impl, cache_enabled, cache_args,
cache_type, cache_dir, cache_url):
def _setup_cache_args(
self,
cache_impl,
cache_enabled,
cache_args,
cache_type,
cache_dir,
cache_url,
):
self.cache_impl = cache_impl
self.cache_enabled = cache_enabled
if cache_args:
@ -357,49 +395,42 @@ class Template(object):
# transfer deprecated cache_* args
if cache_type:
self.cache_args['type'] = cache_type
self.cache_args["type"] = cache_type
if cache_dir:
self.cache_args['dir'] = cache_dir
self.cache_args["dir"] = cache_dir
if cache_url:
self.cache_args['url'] = cache_url
self.cache_args["url"] = cache_url
def _compile_from_file(self, path, filename):
if path is not None:
util.verify_directory(os.path.dirname(path))
filemtime = os.stat(filename)[stat.ST_MTIME]
if not os.path.exists(path) or \
os.stat(path)[stat.ST_MTIME] < filemtime:
if (
not os.path.exists(path)
or os.stat(path)[stat.ST_MTIME] < filemtime
):
data = util.read_file(filename)
_compile_module_file(
self,
data,
filename,
path,
self.module_writer)
self, data, filename, path, self.module_writer
)
module = compat.load_module(self.module_id, path)
del sys.modules[self.module_id]
if module._magic_number != codegen.MAGIC_NUMBER:
data = util.read_file(filename)
_compile_module_file(
self,
data,
filename,
path,
self.module_writer)
self, data, filename, path, self.module_writer
)
module = compat.load_module(self.module_id, path)
del sys.modules[self.module_id]
ModuleInfo(module, path, self, filename, None, None)
ModuleInfo(module, path, self, filename, None, None, None)
else:
# template filename and no module directory, compile code
# in memory
data = util.read_file(filename)
code, module = _compile_text(
self,
data,
filename)
code, module = _compile_text(self, data, filename)
self._source = None
self._code = code
ModuleInfo(module, None, self, filename, code, None)
ModuleInfo(module, None, self, filename, code, None, None)
return module
@property
@ -420,13 +451,15 @@ class Template(object):
@property
def cache_dir(self):
return self.cache_args['dir']
return self.cache_args["dir"]
@property
def cache_url(self):
return self.cache_args['url']
return self.cache_args["url"]
@property
def cache_type(self):
return self.cache_args['type']
return self.cache_args["type"]
def render(self, *args, **data):
"""Render the output of this template as a string.
@ -445,11 +478,9 @@ class Template(object):
def render_unicode(self, *args, **data):
"""Render the output of this template as a unicode object."""
return runtime._render(self,
self.callable_,
args,
data,
as_unicode=True)
return runtime._render(
self, self.callable_, args, data, as_unicode=True
)
def render_context(self, context, *args, **kwargs):
"""Render this :class:`.Template` with the given context.
@ -457,13 +488,9 @@ class Template(object):
The data is written to the context's buffer.
"""
if getattr(context, '_with_template', None) is None:
if getattr(context, "_with_template", None) is None:
context._set_with_template(self)
runtime._render_context(self,
self.callable_,
context,
*args,
**kwargs)
runtime._render_context(self, self.callable_, context, *args, **kwargs)
def has_def(self, name):
return hasattr(self.module, "render_%s" % name)
@ -473,6 +500,14 @@ class Template(object):
return DefTemplate(self, getattr(self.module, "render_%s" % name))
def list_defs(self):
"""return a list of defs in the template.
.. versionadded:: 1.0.4
"""
return [i[7:] for i in dir(self.module) if i[:7] == "render_"]
def _get_def_callable(self, name):
return getattr(self.module, "render_%s" % name)
@ -480,7 +515,9 @@ class Template(object):
def last_modified(self):
return self.module._modified_time
class ModuleTemplate(Template):
"""A Template which is constructed given an existing Python module.
e.g.::
@ -497,27 +534,30 @@ class ModuleTemplate(Template):
"""
def __init__(self, module,
module_filename=None,
template=None,
template_filename=None,
module_source=None,
template_source=None,
output_encoding=None,
encoding_errors='strict',
disable_unicode=False,
bytestring_passthrough=False,
format_exceptions=False,
error_handler=None,
lookup=None,
cache_args=None,
cache_impl='beaker',
cache_enabled=True,
cache_type=None,
cache_dir=None,
cache_url=None,
def __init__(
self,
module,
module_filename=None,
template=None,
template_filename=None,
module_source=None,
template_source=None,
output_encoding=None,
encoding_errors="strict",
disable_unicode=False,
bytestring_passthrough=False,
format_exceptions=False,
error_handler=None,
lookup=None,
cache_args=None,
cache_impl="beaker",
cache_enabled=True,
cache_type=None,
cache_dir=None,
cache_url=None,
include_error_handler=None,
):
self.module_id = re.sub(r'\W', "_", module._template_uri)
self.module_id = re.sub(r"\W", "_", module._template_uri)
self.uri = module._template_uri
self.input_encoding = module._source_encoding
self.output_encoding = output_encoding
@ -528,32 +568,43 @@ class ModuleTemplate(Template):
if compat.py3k and disable_unicode:
raise exceptions.UnsupportedError(
"Mako for Python 3 does not "
"support disabling Unicode")
"Mako for Python 3 does not " "support disabling Unicode"
)
elif output_encoding and disable_unicode:
raise exceptions.UnsupportedError(
"output_encoding must be set to "
"None when disable_unicode is used.")
"output_encoding must be set to "
"None when disable_unicode is used."
)
self.module = module
self.filename = template_filename
ModuleInfo(module,
module_filename,
self,
template_filename,
module_source,
template_source)
ModuleInfo(
module,
module_filename,
self,
template_filename,
module_source,
template_source,
module._template_uri,
)
self.callable_ = self.module.render_body
self.format_exceptions = format_exceptions
self.error_handler = error_handler
self.include_error_handler = include_error_handler
self.lookup = lookup
self._setup_cache_args(
cache_impl, cache_enabled, cache_args,
cache_type, cache_dir, cache_url
cache_impl,
cache_enabled,
cache_args,
cache_type,
cache_dir,
cache_url,
)
class DefTemplate(Template):
"""A :class:`.Template` which represents a callable def in a parent
template."""
@ -565,6 +616,7 @@ class DefTemplate(Template):
self.encoding_errors = parent.encoding_errors
self.format_exceptions = parent.format_exceptions
self.error_handler = parent.error_handler
self.include_error_handler = parent.include_error_handler
self.enable_loop = parent.enable_loop
self.lookup = parent.lookup
self.bytestring_passthrough = parent.bytestring_passthrough
@ -572,26 +624,33 @@ class DefTemplate(Template):
def get_def(self, name):
return self.parent.get_def(name)
class ModuleInfo(object):
"""Stores information about a module currently loaded into
memory, provides reverse lookups of template source, module
source code based on a module's identifier.
"""
_modules = weakref.WeakValueDictionary()
def __init__(self,
module,
module_filename,
template,
template_filename,
module_source,
template_source):
def __init__(
self,
module,
module_filename,
template,
template_filename,
module_source,
template_source,
template_uri,
):
self.module = module
self.module_filename = module_filename
self.template_filename = template_filename
self.module_source = module_source
self.template_source = template_source
self.template_uri = template_uri
self._modules[module.__name__] = template._mmarker = self
if module_filename:
self._modules[module_filename] = self
@ -599,14 +658,15 @@ class ModuleInfo(object):
@classmethod
def get_module_source_metadata(cls, module_source, full_line_map=False):
source_map = re.search(
r"__M_BEGIN_METADATA(.+?)__M_END_METADATA",
module_source, re.S).group(1)
source_map = compat.json.loads(source_map)
source_map['line_map'] = dict((int(k), int(v))
for k, v in source_map['line_map'].items())
r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S
).group(1)
source_map = json.loads(source_map)
source_map["line_map"] = dict(
(int(k), int(v)) for k, v in source_map["line_map"].items()
)
if full_line_map:
f_line_map = source_map['full_line_map'] = []
line_map = source_map['line_map']
f_line_map = source_map["full_line_map"] = []
line_map = source_map["line_map"]
curr_templ_line = 1
for mod_line in range(1, max(line_map)):
@ -625,10 +685,12 @@ class ModuleInfo(object):
@property
def source(self):
if self.template_source is not None:
if self.module._source_encoding and \
not isinstance(self.template_source, compat.text_type):
if self.module._source_encoding and not isinstance(
self.template_source, compat.text_type
):
return self.template_source.decode(
self.module._source_encoding)
self.module._source_encoding
)
else:
return self.template_source
else:
@ -638,49 +700,61 @@ class ModuleInfo(object):
else:
return data
def _compile(template, text, filename, generate_magic_comment):
lexer = template.lexer_cls(text,
filename,
disable_unicode=template.disable_unicode,
input_encoding=template.input_encoding,
preprocessor=template.preprocessor)
lexer = template.lexer_cls(
text,
filename,
disable_unicode=template.disable_unicode,
input_encoding=template.input_encoding,
preprocessor=template.preprocessor,
)
node = lexer.parse()
source = codegen.compile(node,
template.uri,
filename,
default_filters=template.default_filters,
buffer_filters=template.buffer_filters,
imports=template.imports,
future_imports=template.future_imports,
source_encoding=lexer.encoding,
generate_magic_comment=generate_magic_comment,
disable_unicode=template.disable_unicode,
strict_undefined=template.strict_undefined,
enable_loop=template.enable_loop,
reserved_names=template.reserved_names)
source = codegen.compile(
node,
template.uri,
filename,
default_filters=template.default_filters,
buffer_filters=template.buffer_filters,
imports=template.imports,
future_imports=template.future_imports,
source_encoding=lexer.encoding,
generate_magic_comment=generate_magic_comment,
disable_unicode=template.disable_unicode,
strict_undefined=template.strict_undefined,
enable_loop=template.enable_loop,
reserved_names=template.reserved_names,
)
return source, lexer
def _compile_text(template, text, filename):
identifier = template.module_id
source, lexer = _compile(template, text, filename,
generate_magic_comment=template.disable_unicode)
source, lexer = _compile(
template,
text,
filename,
generate_magic_comment=template.disable_unicode,
)
cid = identifier
if not compat.py3k and isinstance(cid, compat.text_type):
cid = cid.encode()
module = types.ModuleType(cid)
code = compile(source, cid, 'exec')
code = compile(source, cid, "exec")
# this exec() works for 2.4->3.3.
exec(code, module.__dict__, module.__dict__)
return (source, module)
def _compile_module_file(template, text, filename, outputpath, module_writer):
source, lexer = _compile(template, text, filename,
generate_magic_comment=True)
source, lexer = _compile(
template, text, filename, generate_magic_comment=True
)
if isinstance(source, compat.text_type):
source = source.encode(lexer.encoding or 'ascii')
source = source.encode(lexer.encoding or "ascii")
if module_writer:
module_writer(source, outputpath)
@ -694,12 +768,13 @@ def _compile_module_file(template, text, filename, outputpath, module_writer):
os.close(dest)
shutil.move(name, outputpath)
def _get_module_info_from_callable(callable_):
if compat.py3k:
return _get_module_info(callable_.__globals__['__name__'])
return _get_module_info(callable_.__globals__["__name__"])
else:
return _get_module_info(callable_.func_globals['__name__'])
return _get_module_info(callable_.func_globals["__name__"])
def _get_module_info(filename):
return ModuleInfo._modules[filename]