Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mako/template.py: 25%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# mako/template.py
2# Copyright 2006-2026 the Mako authors and contributors <see AUTHORS file>
3#
4# This module is part of Mako and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
7"""Provides the Template class, a facade for parsing, generating and executing
8template strings, as well as template runtime operations."""
10import contextlib
11from importlib import abc
12from importlib import machinery
13import json
14import os
15import re
16import shutil
17import stat
18import tempfile
19import types
20import warnings
21import weakref
23from mako import cache
24from mako import codegen
25from mako import compat
26from mako import exceptions
27from mako import pyparser
28from mako import runtime
29from mako import util
30from mako.lexer import Lexer
33class Template:
34 r"""Represents a compiled template.
36 :class:`.Template` includes a reference to the original
37 template source (via the :attr:`.source` attribute)
38 as well as the source code of the
39 generated Python module (i.e. the :attr:`.code` attribute),
40 as well as a reference to an actual Python module.
42 :class:`.Template` is constructed using either a literal string
43 representing the template text, or a filename representing a filesystem
44 path to a source file.
46 :param text: textual template source. This argument is mutually
47 exclusive versus the ``filename`` parameter.
49 :param filename: filename of the source template. This argument is
50 mutually exclusive versus the ``text`` parameter.
52 :param buffer_filters: string list of filters to be applied
53 to the output of ``%def``\ s which are buffered, cached, or otherwise
54 filtered, after all filters
55 defined with the ``%def`` itself have been applied. Allows the
56 creation of default expression filters that let the output
57 of return-valued ``%def``\ s "opt out" of that filtering via
58 passing special attributes or objects.
60 :param cache_args: Dictionary of cache configuration arguments that
61 will be passed to the :class:`.CacheImpl`. See :ref:`caching_toplevel`.
63 :param cache_dir:
65 .. deprecated:: 0.6
66 Use the ``'dir'`` argument in the ``cache_args`` dictionary.
67 See :ref:`caching_toplevel`.
69 :param cache_enabled: Boolean flag which enables caching of this
70 template. See :ref:`caching_toplevel`.
72 :param cache_impl: String name of a :class:`.CacheImpl` caching
73 implementation to use. Defaults to ``'beaker'``.
75 :param cache_type:
77 .. deprecated:: 0.6
78 Use the ``'type'`` argument in the ``cache_args`` dictionary.
79 See :ref:`caching_toplevel`.
81 :param cache_url:
83 .. deprecated:: 0.6
84 Use the ``'url'`` argument in the ``cache_args`` dictionary.
85 See :ref:`caching_toplevel`.
87 :param default_filters: List of string filter names that will
88 be applied to all expressions. See :ref:`filtering_default_filters`.
90 :param enable_loop: When ``True``, enable the ``loop`` context variable.
91 This can be set to ``False`` to support templates that may
92 be making usage of the name "``loop``". Individual templates can
93 re-enable the "loop" context by placing the directive
94 ``enable_loop="True"`` inside the ``<%page>`` tag -- see
95 :ref:`migrating_loop`.
97 :param encoding_errors: Error parameter passed to ``encode()`` when
98 string encoding is performed. See :ref:`usage_unicode`.
100 :param error_handler: Python callable which is called whenever
101 compile or runtime exceptions occur. The callable is passed
102 the current context as well as the exception. If the
103 callable returns ``True``, the exception is considered to
104 be handled, else it is re-raised after the function
105 completes. Is used to provide custom error-rendering
106 functions.
108 .. seealso::
110 :paramref:`.Template.include_error_handler` - include-specific
111 error handler function
113 :param format_exceptions: if ``True``, exceptions which occur during
114 the render phase of this template will be caught and
115 formatted into an HTML error page, which then becomes the
116 rendered result of the :meth:`.render` call. Otherwise,
117 runtime exceptions are propagated outwards.
119 :param imports: String list of Python statements, typically individual
120 "import" lines, which will be placed into the module level
121 preamble of all generated Python modules. See the example
122 in :ref:`filtering_default_filters`.
124 :param future_imports: String list of names to import from `__future__`.
125 These will be concatenated into a comma-separated string and inserted
126 into the beginning of the template, e.g. ``futures_imports=['FOO',
127 'BAR']`` results in ``from __future__ import FOO, BAR``.
129 :param include_error_handler: An error handler that runs when this template
130 is included within another one via the ``<%include>`` tag, and raises an
131 error. Compare to the :paramref:`.Template.error_handler` option.
133 .. versionadded:: 1.0.6
135 .. seealso::
137 :paramref:`.Template.error_handler` - top-level error handler function
139 :param input_encoding: Encoding of the template's source code. Can
140 be used in lieu of the coding comment. See
141 :ref:`usage_unicode` as well as :ref:`unicode_toplevel` for
142 details on source encoding.
144 :param lookup: a :class:`.TemplateLookup` instance that will be used
145 for all file lookups via the ``<%namespace>``,
146 ``<%include>``, and ``<%inherit>`` tags. See
147 :ref:`usage_templatelookup`.
149 :param module_directory: Filesystem location where generated
150 Python module files will be placed.
152 :param module_filename: Overrides the filename of the generated
153 Python module file. For advanced usage only.
155 :param module_writer: A callable which overrides how the Python
156 module is written entirely. The callable is passed the
157 encoded source content of the module and the destination
158 path to be written to. The default behavior of module writing
159 uses a tempfile in conjunction with a file move in order
160 to make the operation atomic. So a user-defined module
161 writing function that mimics the default behavior would be:
163 .. sourcecode:: python
165 import tempfile
166 import os
167 import shutil
169 def module_writer(source, outputpath):
170 (dest, name) = \\
171 tempfile.mkstemp(
172 dir=os.path.dirname(outputpath)
173 )
175 os.write(dest, source)
176 os.close(dest)
177 shutil.move(name, outputpath)
179 from mako.template import Template
180 mytemplate = Template(
181 filename="index.html",
182 module_directory="/path/to/modules",
183 module_writer=module_writer
184 )
186 The function is provided for unusual configurations where
187 certain platform-specific permissions or other special
188 steps are needed.
190 :param output_encoding: The encoding to use when :meth:`.render`
191 is called.
192 See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`.
194 :param preprocessor: Python callable which will be passed
195 the full template source before it is parsed. The return
196 result of the callable will be used as the template source
197 code.
199 :param lexer_cls: A :class:`.Lexer` class used to parse
200 the template. The :class:`.Lexer` class is used by
201 default.
203 .. versionadded:: 0.7.4
205 :param strict_undefined: Replaces the automatic usage of
206 ``UNDEFINED`` for any undeclared variables not located in
207 the :class:`.Context` with an immediate raise of
208 ``NameError``. The advantage is immediate reporting of
209 missing variables which include the name.
211 .. versionadded:: 0.3.6
213 :param uri: string URI or other identifier for this template.
214 If not provided, the ``uri`` is generated from the filesystem
215 path, or from the in-memory identity of a non-file-based
216 template. The primary usage of the ``uri`` is to provide a key
217 within :class:`.TemplateLookup`, as well as to generate the
218 file path of the generated Python module file, if
219 ``module_directory`` is specified.
221 """
223 lexer_cls = Lexer
225 def __init__(
226 self,
227 text=None,
228 filename=None,
229 uri=None,
230 format_exceptions=False,
231 error_handler=None,
232 lookup=None,
233 output_encoding=None,
234 encoding_errors="strict",
235 module_directory=None,
236 cache_args=None,
237 cache_impl="beaker",
238 cache_enabled=True,
239 cache_type=None,
240 cache_dir=None,
241 cache_url=None,
242 module_filename=None,
243 input_encoding=None,
244 module_writer=None,
245 default_filters=None,
246 buffer_filters=(),
247 strict_undefined=False,
248 imports=None,
249 future_imports=None,
250 enable_loop=True,
251 preprocessor=None,
252 lexer_cls=None,
253 include_error_handler=None,
254 ):
255 if uri:
256 self.module_id = re.sub(r"\W", "_", uri)
257 self.uri = uri
258 elif filename:
259 self.module_id = re.sub(r"\W", "_", filename)
260 drive, path = os.path.splitdrive(filename)
261 path = os.path.normpath(path).replace(os.path.sep, "/")
262 self.uri = path
263 else:
264 self.module_id = "memory:" + hex(id(self))
265 self.uri = self.module_id
267 u_norm = self.uri.replace("\\", "/").lstrip("/")
268 u_norm = os.path.normpath(u_norm)
269 if u_norm.startswith(".."):
270 raise exceptions.TemplateLookupException(
271 'Template uri "%s" is invalid - '
272 "it cannot be relative outside "
273 "of the root path." % self.uri
274 )
276 self.input_encoding = input_encoding
277 self.output_encoding = output_encoding
278 self.encoding_errors = encoding_errors
279 self.enable_loop = enable_loop
280 self.strict_undefined = strict_undefined
281 self.module_writer = module_writer
283 if default_filters is None:
284 self.default_filters = ["str"]
285 else:
286 self.default_filters = default_filters
287 self.buffer_filters = buffer_filters
289 self.imports = imports
290 self.future_imports = future_imports
291 self.preprocessor = preprocessor
293 if lexer_cls is not None:
294 self.lexer_cls = lexer_cls
296 # if plain text, compile code in memory only
297 if text is not None:
298 code, module = _compile_text(self, text, filename)
299 self._code = code
300 self._source = text
301 ModuleInfo(module, None, self, filename, code, text, uri)
302 elif filename is not None:
303 # if template filename and a module directory, load
304 # a filesystem-based module file, generating if needed
305 if module_filename is not None:
306 path = module_filename
307 elif module_directory is not None:
308 path = os.path.abspath(
309 os.path.join(
310 os.path.normpath(module_directory), u_norm + ".py"
311 )
312 )
313 else:
314 path = None
315 module = self._compile_from_file(path, filename)
316 else:
317 raise exceptions.RuntimeException(
318 "Template requires text or filename"
319 )
321 self.module = module
322 self.filename = filename
323 self.callable_ = self.module.render_body
324 self.format_exceptions = format_exceptions
325 self.error_handler = error_handler
326 self.include_error_handler = include_error_handler
327 self.lookup = lookup
329 self.module_directory = module_directory
331 self._setup_cache_args(
332 cache_impl,
333 cache_enabled,
334 cache_args,
335 cache_type,
336 cache_dir,
337 cache_url,
338 )
340 @util.memoized_property
341 def reserved_names(self):
342 if self.enable_loop:
343 return codegen.RESERVED_NAMES
344 else:
345 return codegen.RESERVED_NAMES.difference(["loop"])
347 def _setup_cache_args(
348 self,
349 cache_impl,
350 cache_enabled,
351 cache_args,
352 cache_type,
353 cache_dir,
354 cache_url,
355 ):
356 self.cache_impl = cache_impl
357 self.cache_enabled = cache_enabled
358 self.cache_args = cache_args or {}
359 # transfer deprecated cache_* args
360 if cache_type:
361 self.cache_args["type"] = cache_type
362 if cache_dir:
363 self.cache_args["dir"] = cache_dir
364 if cache_url:
365 self.cache_args["url"] = cache_url
367 def _compile_from_file(self, path, filename):
368 if path is not None:
369 util.verify_directory(os.path.dirname(path))
370 filemtime = os.stat(filename)[stat.ST_MTIME]
371 with _translate_module_warnings(
372 lambda: util.read_python_file(path), path, filename
373 ):
374 if (
375 not os.path.exists(path)
376 or os.stat(path)[stat.ST_MTIME] < filemtime
377 ):
378 data = util.read_file(filename)
379 with _drop_expression_warnings():
380 _compile_module_file(
381 self, data, filename, path, self.module_writer
382 )
383 module = compat.load_module(self.module_id, path)
384 if module._magic_number != codegen.MAGIC_NUMBER:
385 data = util.read_file(filename)
386 with _drop_expression_warnings():
387 _compile_module_file(
388 self, data, filename, path, self.module_writer
389 )
390 module = compat.load_module(self.module_id, path)
392 ModuleInfo(module, path, self, filename, None, None, None)
393 else:
394 # template filename and no module directory, compile code
395 # in memory
396 data = util.read_file(filename)
397 code, module = _compile_text(self, data, filename)
398 self._source = None
399 self._code = code
400 ModuleInfo(module, None, self, filename, code, None, None)
401 return module
403 @property
404 def source(self):
405 """Return the template source code for this :class:`.Template`."""
407 return _get_module_info_from_callable(self.callable_).source
409 @property
410 def code(self):
411 """Return the module source code for this :class:`.Template`."""
413 return _get_module_info_from_callable(self.callable_).code
415 @util.memoized_property
416 def cache(self):
417 return cache.Cache(self)
419 @property
420 def cache_dir(self):
421 return self.cache_args["dir"]
423 @property
424 def cache_url(self):
425 return self.cache_args["url"]
427 @property
428 def cache_type(self):
429 return self.cache_args["type"]
431 def render(self, *args, **data):
432 """Render the output of this template as a string.
434 If the template specifies an output encoding, the string
435 will be encoded accordingly, else the output is raw (raw
436 output uses `StringIO` and can't handle multibyte
437 characters). A :class:`.Context` object is created corresponding
438 to the given data. Arguments that are explicitly declared
439 by this template's internal rendering method are also
440 pulled from the given ``*args``, ``**data`` members.
442 """
443 return runtime._render(self, self.callable_, args, data)
445 def render_unicode(self, *args, **data):
446 """Render the output of this template as a unicode object."""
448 return runtime._render(
449 self, self.callable_, args, data, as_unicode=True
450 )
452 def render_context(self, context, *args, **kwargs):
453 """Render this :class:`.Template` with the given context.
455 The data is written to the context's buffer.
457 """
458 if getattr(context, "_with_template", None) is None:
459 context._set_with_template(self)
460 runtime._render_context(self, self.callable_, context, *args, **kwargs)
462 def has_def(self, name):
463 return hasattr(self.module, "render_%s" % name)
465 def get_def(self, name):
466 """Return a def of this template as a :class:`.DefTemplate`."""
468 return DefTemplate(self, getattr(self.module, "render_%s" % name))
470 def list_defs(self):
471 """return a list of defs in the template.
473 .. versionadded:: 1.0.4
475 """
476 return [i[7:] for i in dir(self.module) if i[:7] == "render_"]
478 def _get_def_callable(self, name):
479 return getattr(self.module, "render_%s" % name)
481 @property
482 def last_modified(self):
483 return self.module._modified_time
486class ModuleTemplate(Template):
487 """A Template which is constructed given an existing Python module.
489 e.g.::
491 t = Template("this is a template")
492 f = file("mymodule.py", "w")
493 f.write(t.code)
494 f.close()
496 import mymodule
498 t = ModuleTemplate(mymodule)
499 print(t.render())
501 """
503 def __init__(
504 self,
505 module,
506 module_filename=None,
507 template=None,
508 template_filename=None,
509 module_source=None,
510 template_source=None,
511 output_encoding=None,
512 encoding_errors="strict",
513 format_exceptions=False,
514 error_handler=None,
515 lookup=None,
516 cache_args=None,
517 cache_impl="beaker",
518 cache_enabled=True,
519 cache_type=None,
520 cache_dir=None,
521 cache_url=None,
522 include_error_handler=None,
523 ):
524 self.module_id = re.sub(r"\W", "_", module._template_uri)
525 self.uri = module._template_uri
526 self.input_encoding = module._source_encoding
527 self.output_encoding = output_encoding
528 self.encoding_errors = encoding_errors
529 self.enable_loop = module._enable_loop
531 self.module = module
532 self.filename = template_filename
533 ModuleInfo(
534 module,
535 module_filename,
536 self,
537 template_filename,
538 module_source,
539 template_source,
540 module._template_uri,
541 )
543 self.callable_ = self.module.render_body
544 self.format_exceptions = format_exceptions
545 self.error_handler = error_handler
546 self.include_error_handler = include_error_handler
547 self.lookup = lookup
548 self._setup_cache_args(
549 cache_impl,
550 cache_enabled,
551 cache_args,
552 cache_type,
553 cache_dir,
554 cache_url,
555 )
558class DefTemplate(Template):
559 """A :class:`.Template` which represents a callable def in a parent
560 template."""
562 def __init__(self, parent, callable_):
563 self.parent = parent
564 self.callable_ = callable_
565 self.output_encoding = parent.output_encoding
566 self.module = parent.module
567 self.encoding_errors = parent.encoding_errors
568 self.format_exceptions = parent.format_exceptions
569 self.error_handler = parent.error_handler
570 self.include_error_handler = parent.include_error_handler
571 self.enable_loop = parent.enable_loop
572 self.lookup = parent.lookup
574 def get_def(self, name):
575 return self.parent.get_def(name)
578class ModuleInfo:
579 """Stores information about a module currently loaded into
580 memory, provides reverse lookups of template source, module
581 source code based on a module's identifier.
583 """
585 _modules = weakref.WeakValueDictionary()
587 def __init__(
588 self,
589 module,
590 module_filename,
591 template,
592 template_filename,
593 module_source,
594 template_source,
595 template_uri,
596 ):
597 self.module = module
598 self.module_filename = module_filename
599 self.template_filename = template_filename
600 self.module_source = module_source
601 self.template_source = template_source
602 self.template_uri = template_uri
603 self._modules[module.__name__] = template._mmarker = self
604 if module_filename:
605 self._modules[module_filename] = self
607 @classmethod
608 def get_module_source_metadata(cls, module_source, full_line_map=False):
609 source_map = re.search(
610 r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S
611 ).group(1)
612 source_map = json.loads(source_map)
613 source_map["line_map"] = {
614 int(k): int(v) for k, v in source_map["line_map"].items()
615 }
616 if full_line_map:
617 f_line_map = source_map["full_line_map"] = []
618 line_map = source_map["line_map"]
620 curr_templ_line = 1
621 for mod_line in range(1, max(line_map)):
622 if mod_line in line_map:
623 curr_templ_line = line_map[mod_line]
624 f_line_map.append(curr_templ_line)
625 return source_map
627 @property
628 def code(self):
629 if self.module_source is not None:
630 return self.module_source
631 else:
632 return util.read_python_file(self.module_filename)
634 @property
635 def source(self):
636 if self.template_source is None:
637 data = util.read_file(self.template_filename)
638 if self.module._source_encoding:
639 return data.decode(self.module._source_encoding)
640 else:
641 return data
643 elif self.module._source_encoding and not isinstance(
644 self.template_source, str
645 ):
646 return self.template_source.decode(self.module._source_encoding)
647 else:
648 return self.template_source
651def _compile(template, text, filename, generate_magic_comment):
652 lexer = template.lexer_cls(
653 text,
654 filename,
655 input_encoding=template.input_encoding,
656 preprocessor=template.preprocessor,
657 )
658 node = lexer.parse()
659 source = codegen.compile(
660 node,
661 template.uri,
662 filename,
663 default_filters=template.default_filters,
664 buffer_filters=template.buffer_filters,
665 imports=template.imports,
666 future_imports=template.future_imports,
667 source_encoding=lexer.encoding,
668 generate_magic_comment=generate_magic_comment,
669 strict_undefined=template.strict_undefined,
670 enable_loop=template.enable_loop,
671 reserved_names=template.reserved_names,
672 )
673 return source, lexer
676class _ModuleSourceLoader(abc.Loader):
677 """Provide the generated source of an in-memory template module.
679 A module created directly from :class:`types.ModuleType` has ``__spec__``
680 and ``__loader__`` present in its namespace but set to ``None``. The
681 :mod:`linecache` module consults these while a traceback is being built,
682 and as of Python 3.15 emits a :class:`DeprecationWarning` when it finds a
683 ``__spec__`` that has no loader; a module lacking a real loader also has
684 no source lines available to show for its frames. Supplying a loader
685 that can produce the generated module source addresses both.
687 """
689 def __init__(self, name, source):
690 self.name = name
691 self._source = source
693 def get_source(self, name):
694 return self._source
696 def is_package(self, name):
697 return False
700@contextlib.contextmanager
701def _show_warnings_as(locate):
702 """Replace the warnings display hook for the duration of the block.
704 ``locate`` is passed the message, category, filename and line number of
705 each warning, and returns the filename and line number it should be
706 shown as, or ``None`` for a warning that should not be shown at all.
708 The display hook is replaced, rather than the warnings being recorded
709 and raised a second time, so that each warning passes through the
710 warnings filters exactly once. A filter with a stateful action such as
711 "once" would otherwise suppress the second occurrence, and the warning
712 would be lost entirely.
714 """
716 show_warning = warnings.showwarning
718 def _show(message, category, filename, lineno, file=None, line=None):
719 location = locate(message, category, filename, lineno)
720 if location is None:
721 return
723 if location != (filename, lineno):
724 # the source line, if given, is that of the original location;
725 # allow it to be looked up again for the new one
726 line = None
727 filename, lineno = location
729 show_warning(message, category, filename, lineno, file, line)
731 warnings.showwarning = _show
732 try:
733 yield
734 finally:
735 warnings.showwarning = show_warning
738def _drop_expression_warnings():
739 """Drop warnings raised while individual expressions are parsed.
741 These carry no location that can be related back to the template, as the
742 expression is parsed on its own, and the same warning is raised again
743 when the module as a whole is compiled, where the location can be
744 translated.
746 """
748 def _locate(message, category, filename, lineno):
749 if filename != pyparser.EXPRESSION_FILENAME:
750 return filename, lineno
752 # a "once" filter records the warning globally as it is passed
753 # over, so remove that record, else the occurrence that can be
754 # translated would be suppressed
755 warnings.onceregistry.pop((str(message), category), None)
756 return None
758 return _show_warnings_as(_locate)
761def _translate_module_warnings(get_source, module_id, filename):
762 """Report warnings raised for a generated module against the template
763 it was generated from, translating the line number through the module's
764 line map.
766 Any other warning, such as one raised by a module imported from a
767 ``<%! %>`` block as the template module executes, is shown unchanged, as
768 is one whose line cannot be translated.
770 """
772 line_map = None
774 def _locate(message, category, warning_filename, lineno):
775 nonlocal line_map
777 if warning_filename != module_id:
778 return warning_filename, lineno
780 if line_map is None:
781 try:
782 line_map = ModuleInfo.get_module_source_metadata(
783 get_source(), full_line_map=True
784 )["full_line_map"]
785 except Exception:
786 # a module file that carries no usable metadata, having
787 # been written by some other means or damaged. the warning
788 # is worth more than the translation is
789 line_map = []
791 try:
792 translated = line_map[lineno - 1]
793 except IndexError:
794 return warning_filename, lineno
795 else:
796 return filename, translated
798 return _show_warnings_as(_locate)
801def _compile_text(template, text, filename):
802 identifier = template.module_id
804 cid = identifier
805 module = types.ModuleType(cid)
807 with _drop_expression_warnings():
808 source, lexer = _compile(
809 template, text, filename, generate_magic_comment=False
810 )
812 loader = _ModuleSourceLoader(cid, source)
813 module.__loader__ = loader
814 module.__spec__ = machinery.ModuleSpec(cid, loader, origin=cid)
816 with _translate_module_warnings(
817 lambda: source, cid, filename or template.uri
818 ):
819 code = compile(source, cid, "exec")
821 # the module body, which is the code of any <%! %> blocks, is
822 # executed within the same block, so that a warning it raises is
823 # reported the same way here as it is when the template is loaded
824 # from a module file, where the import system compiles and executes
825 # the module as one step
826 exec(code, module.__dict__, module.__dict__)
828 return (source, module)
831def _compile_module_file(template, text, filename, outputpath, module_writer):
832 source, lexer = _compile(
833 template, text, filename, generate_magic_comment=True
834 )
836 if isinstance(source, str):
837 source = source.encode(lexer.encoding or "ascii")
839 if module_writer:
840 module_writer(source, outputpath)
841 else:
842 # make tempfiles in the same location as the ultimate
843 # location. this ensures they're on the same filesystem,
844 # avoiding synchronization issues.
845 dest, name = tempfile.mkstemp(dir=os.path.dirname(outputpath))
847 os.write(dest, source)
848 os.close(dest)
849 shutil.move(name, outputpath)
852def _get_module_info_from_callable(callable_):
853 return _get_module_info(callable_.__globals__["__name__"])
856def _get_module_info(filename):
857 return ModuleInfo._modules[filename]