template.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. # mako/template.py
  2. # Copyright 2006-2025 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
  6. """Provides the Template class, a facade for parsing, generating and executing
  7. template strings, as well as template runtime operations."""
  8. import json
  9. import os
  10. import re
  11. import shutil
  12. import stat
  13. import tempfile
  14. import types
  15. import weakref
  16. from mako import cache
  17. from mako import codegen
  18. from mako import compat
  19. from mako import exceptions
  20. from mako import runtime
  21. from mako import util
  22. from mako.lexer import Lexer
  23. class Template:
  24. r"""Represents a compiled template.
  25. :class:`.Template` includes a reference to the original
  26. template source (via the :attr:`.source` attribute)
  27. as well as the source code of the
  28. generated Python module (i.e. the :attr:`.code` attribute),
  29. as well as a reference to an actual Python module.
  30. :class:`.Template` is constructed using either a literal string
  31. representing the template text, or a filename representing a filesystem
  32. path to a source file.
  33. :param text: textual template source. This argument is mutually
  34. exclusive versus the ``filename`` parameter.
  35. :param filename: filename of the source template. This argument is
  36. mutually exclusive versus the ``text`` parameter.
  37. :param buffer_filters: string list of filters to be applied
  38. to the output of ``%def``\ s which are buffered, cached, or otherwise
  39. filtered, after all filters
  40. defined with the ``%def`` itself have been applied. Allows the
  41. creation of default expression filters that let the output
  42. of return-valued ``%def``\ s "opt out" of that filtering via
  43. passing special attributes or objects.
  44. :param cache_args: Dictionary of cache configuration arguments that
  45. will be passed to the :class:`.CacheImpl`. See :ref:`caching_toplevel`.
  46. :param cache_dir:
  47. .. deprecated:: 0.6
  48. Use the ``'dir'`` argument in the ``cache_args`` dictionary.
  49. See :ref:`caching_toplevel`.
  50. :param cache_enabled: Boolean flag which enables caching of this
  51. template. See :ref:`caching_toplevel`.
  52. :param cache_impl: String name of a :class:`.CacheImpl` caching
  53. implementation to use. Defaults to ``'beaker'``.
  54. :param cache_type:
  55. .. deprecated:: 0.6
  56. Use the ``'type'`` argument in the ``cache_args`` dictionary.
  57. See :ref:`caching_toplevel`.
  58. :param cache_url:
  59. .. deprecated:: 0.6
  60. Use the ``'url'`` argument in the ``cache_args`` dictionary.
  61. See :ref:`caching_toplevel`.
  62. :param default_filters: List of string filter names that will
  63. be applied to all expressions. See :ref:`filtering_default_filters`.
  64. :param enable_loop: When ``True``, enable the ``loop`` context variable.
  65. This can be set to ``False`` to support templates that may
  66. be making usage of the name "``loop``". Individual templates can
  67. re-enable the "loop" context by placing the directive
  68. ``enable_loop="True"`` inside the ``<%page>`` tag -- see
  69. :ref:`migrating_loop`.
  70. :param encoding_errors: Error parameter passed to ``encode()`` when
  71. string encoding is performed. See :ref:`usage_unicode`.
  72. :param error_handler: Python callable which is called whenever
  73. compile or runtime exceptions occur. The callable is passed
  74. the current context as well as the exception. If the
  75. callable returns ``True``, the exception is considered to
  76. be handled, else it is re-raised after the function
  77. completes. Is used to provide custom error-rendering
  78. functions.
  79. .. seealso::
  80. :paramref:`.Template.include_error_handler` - include-specific
  81. error handler function
  82. :param format_exceptions: if ``True``, exceptions which occur during
  83. the render phase of this template will be caught and
  84. formatted into an HTML error page, which then becomes the
  85. rendered result of the :meth:`.render` call. Otherwise,
  86. runtime exceptions are propagated outwards.
  87. :param imports: String list of Python statements, typically individual
  88. "import" lines, which will be placed into the module level
  89. preamble of all generated Python modules. See the example
  90. in :ref:`filtering_default_filters`.
  91. :param future_imports: String list of names to import from `__future__`.
  92. These will be concatenated into a comma-separated string and inserted
  93. into the beginning of the template, e.g. ``futures_imports=['FOO',
  94. 'BAR']`` results in ``from __future__ import FOO, BAR``.
  95. :param include_error_handler: An error handler that runs when this template
  96. is included within another one via the ``<%include>`` tag, and raises an
  97. error. Compare to the :paramref:`.Template.error_handler` option.
  98. .. versionadded:: 1.0.6
  99. .. seealso::
  100. :paramref:`.Template.error_handler` - top-level error handler function
  101. :param input_encoding: Encoding of the template's source code. Can
  102. be used in lieu of the coding comment. See
  103. :ref:`usage_unicode` as well as :ref:`unicode_toplevel` for
  104. details on source encoding.
  105. :param lookup: a :class:`.TemplateLookup` instance that will be used
  106. for all file lookups via the ``<%namespace>``,
  107. ``<%include>``, and ``<%inherit>`` tags. See
  108. :ref:`usage_templatelookup`.
  109. :param module_directory: Filesystem location where generated
  110. Python module files will be placed.
  111. :param module_filename: Overrides the filename of the generated
  112. Python module file. For advanced usage only.
  113. :param module_writer: A callable which overrides how the Python
  114. module is written entirely. The callable is passed the
  115. encoded source content of the module and the destination
  116. path to be written to. The default behavior of module writing
  117. uses a tempfile in conjunction with a file move in order
  118. to make the operation atomic. So a user-defined module
  119. writing function that mimics the default behavior would be:
  120. .. sourcecode:: python
  121. import tempfile
  122. import os
  123. import shutil
  124. def module_writer(source, outputpath):
  125. (dest, name) = \\
  126. tempfile.mkstemp(
  127. dir=os.path.dirname(outputpath)
  128. )
  129. os.write(dest, source)
  130. os.close(dest)
  131. shutil.move(name, outputpath)
  132. from mako.template import Template
  133. mytemplate = Template(
  134. filename="index.html",
  135. module_directory="/path/to/modules",
  136. module_writer=module_writer
  137. )
  138. The function is provided for unusual configurations where
  139. certain platform-specific permissions or other special
  140. steps are needed.
  141. :param output_encoding: The encoding to use when :meth:`.render`
  142. is called.
  143. See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`.
  144. :param preprocessor: Python callable which will be passed
  145. the full template source before it is parsed. The return
  146. result of the callable will be used as the template source
  147. code.
  148. :param lexer_cls: A :class:`.Lexer` class used to parse
  149. the template. The :class:`.Lexer` class is used by
  150. default.
  151. .. versionadded:: 0.7.4
  152. :param strict_undefined: Replaces the automatic usage of
  153. ``UNDEFINED`` for any undeclared variables not located in
  154. the :class:`.Context` with an immediate raise of
  155. ``NameError``. The advantage is immediate reporting of
  156. missing variables which include the name.
  157. .. versionadded:: 0.3.6
  158. :param uri: string URI or other identifier for this template.
  159. If not provided, the ``uri`` is generated from the filesystem
  160. path, or from the in-memory identity of a non-file-based
  161. template. The primary usage of the ``uri`` is to provide a key
  162. within :class:`.TemplateLookup`, as well as to generate the
  163. file path of the generated Python module file, if
  164. ``module_directory`` is specified.
  165. """
  166. lexer_cls = Lexer
  167. def __init__(
  168. self,
  169. text=None,
  170. filename=None,
  171. uri=None,
  172. format_exceptions=False,
  173. error_handler=None,
  174. lookup=None,
  175. output_encoding=None,
  176. encoding_errors="strict",
  177. module_directory=None,
  178. cache_args=None,
  179. cache_impl="beaker",
  180. cache_enabled=True,
  181. cache_type=None,
  182. cache_dir=None,
  183. cache_url=None,
  184. module_filename=None,
  185. input_encoding=None,
  186. module_writer=None,
  187. default_filters=None,
  188. buffer_filters=(),
  189. strict_undefined=False,
  190. imports=None,
  191. future_imports=None,
  192. enable_loop=True,
  193. preprocessor=None,
  194. lexer_cls=None,
  195. include_error_handler=None,
  196. ):
  197. if uri:
  198. self.module_id = re.sub(r"\W", "_", uri)
  199. self.uri = uri
  200. elif filename:
  201. self.module_id = re.sub(r"\W", "_", filename)
  202. drive, path = os.path.splitdrive(filename)
  203. path = os.path.normpath(path).replace(os.path.sep, "/")
  204. self.uri = path
  205. else:
  206. self.module_id = "memory:" + hex(id(self))
  207. self.uri = self.module_id
  208. u_norm = self.uri
  209. if u_norm.startswith("/"):
  210. u_norm = u_norm[1:]
  211. u_norm = os.path.normpath(u_norm)
  212. if u_norm.startswith(".."):
  213. raise exceptions.TemplateLookupException(
  214. 'Template uri "%s" is invalid - '
  215. "it cannot be relative outside "
  216. "of the root path." % self.uri
  217. )
  218. self.input_encoding = input_encoding
  219. self.output_encoding = output_encoding
  220. self.encoding_errors = encoding_errors
  221. self.enable_loop = enable_loop
  222. self.strict_undefined = strict_undefined
  223. self.module_writer = module_writer
  224. if default_filters is None:
  225. self.default_filters = ["str"]
  226. else:
  227. self.default_filters = default_filters
  228. self.buffer_filters = buffer_filters
  229. self.imports = imports
  230. self.future_imports = future_imports
  231. self.preprocessor = preprocessor
  232. if lexer_cls is not None:
  233. self.lexer_cls = lexer_cls
  234. # if plain text, compile code in memory only
  235. if text is not None:
  236. (code, module) = _compile_text(self, text, filename)
  237. self._code = code
  238. self._source = text
  239. ModuleInfo(module, None, self, filename, code, text, uri)
  240. elif filename is not None:
  241. # if template filename and a module directory, load
  242. # a filesystem-based module file, generating if needed
  243. if module_filename is not None:
  244. path = module_filename
  245. elif module_directory is not None:
  246. path = os.path.abspath(
  247. os.path.join(
  248. os.path.normpath(module_directory), u_norm + ".py"
  249. )
  250. )
  251. else:
  252. path = None
  253. module = self._compile_from_file(path, filename)
  254. else:
  255. raise exceptions.RuntimeException(
  256. "Template requires text or filename"
  257. )
  258. self.module = module
  259. self.filename = filename
  260. self.callable_ = self.module.render_body
  261. self.format_exceptions = format_exceptions
  262. self.error_handler = error_handler
  263. self.include_error_handler = include_error_handler
  264. self.lookup = lookup
  265. self.module_directory = module_directory
  266. self._setup_cache_args(
  267. cache_impl,
  268. cache_enabled,
  269. cache_args,
  270. cache_type,
  271. cache_dir,
  272. cache_url,
  273. )
  274. @util.memoized_property
  275. def reserved_names(self):
  276. if self.enable_loop:
  277. return codegen.RESERVED_NAMES
  278. else:
  279. return codegen.RESERVED_NAMES.difference(["loop"])
  280. def _setup_cache_args(
  281. self,
  282. cache_impl,
  283. cache_enabled,
  284. cache_args,
  285. cache_type,
  286. cache_dir,
  287. cache_url,
  288. ):
  289. self.cache_impl = cache_impl
  290. self.cache_enabled = cache_enabled
  291. self.cache_args = cache_args or {}
  292. # transfer deprecated cache_* args
  293. if cache_type:
  294. self.cache_args["type"] = cache_type
  295. if cache_dir:
  296. self.cache_args["dir"] = cache_dir
  297. if cache_url:
  298. self.cache_args["url"] = cache_url
  299. def _compile_from_file(self, path, filename):
  300. if path is not None:
  301. util.verify_directory(os.path.dirname(path))
  302. filemtime = os.stat(filename)[stat.ST_MTIME]
  303. if (
  304. not os.path.exists(path)
  305. or os.stat(path)[stat.ST_MTIME] < filemtime
  306. ):
  307. data = util.read_file(filename)
  308. _compile_module_file(
  309. self, data, filename, path, self.module_writer
  310. )
  311. module = compat.load_module(self.module_id, path)
  312. if module._magic_number != codegen.MAGIC_NUMBER:
  313. data = util.read_file(filename)
  314. _compile_module_file(
  315. self, data, filename, path, self.module_writer
  316. )
  317. module = compat.load_module(self.module_id, path)
  318. ModuleInfo(module, path, self, filename, None, None, None)
  319. else:
  320. # template filename and no module directory, compile code
  321. # in memory
  322. data = util.read_file(filename)
  323. code, module = _compile_text(self, data, filename)
  324. self._source = None
  325. self._code = code
  326. ModuleInfo(module, None, self, filename, code, None, None)
  327. return module
  328. @property
  329. def source(self):
  330. """Return the template source code for this :class:`.Template`."""
  331. return _get_module_info_from_callable(self.callable_).source
  332. @property
  333. def code(self):
  334. """Return the module source code for this :class:`.Template`."""
  335. return _get_module_info_from_callable(self.callable_).code
  336. @util.memoized_property
  337. def cache(self):
  338. return cache.Cache(self)
  339. @property
  340. def cache_dir(self):
  341. return self.cache_args["dir"]
  342. @property
  343. def cache_url(self):
  344. return self.cache_args["url"]
  345. @property
  346. def cache_type(self):
  347. return self.cache_args["type"]
  348. def render(self, *args, **data):
  349. """Render the output of this template as a string.
  350. If the template specifies an output encoding, the string
  351. will be encoded accordingly, else the output is raw (raw
  352. output uses `StringIO` and can't handle multibyte
  353. characters). A :class:`.Context` object is created corresponding
  354. to the given data. Arguments that are explicitly declared
  355. by this template's internal rendering method are also
  356. pulled from the given ``*args``, ``**data`` members.
  357. """
  358. return runtime._render(self, self.callable_, args, data)
  359. def render_unicode(self, *args, **data):
  360. """Render the output of this template as a unicode object."""
  361. return runtime._render(
  362. self, self.callable_, args, data, as_unicode=True
  363. )
  364. def render_context(self, context, *args, **kwargs):
  365. """Render this :class:`.Template` with the given context.
  366. The data is written to the context's buffer.
  367. """
  368. if getattr(context, "_with_template", None) is None:
  369. context._set_with_template(self)
  370. runtime._render_context(self, self.callable_, context, *args, **kwargs)
  371. def has_def(self, name):
  372. return hasattr(self.module, "render_%s" % name)
  373. def get_def(self, name):
  374. """Return a def of this template as a :class:`.DefTemplate`."""
  375. return DefTemplate(self, getattr(self.module, "render_%s" % name))
  376. def list_defs(self):
  377. """return a list of defs in the template.
  378. .. versionadded:: 1.0.4
  379. """
  380. return [i[7:] for i in dir(self.module) if i[:7] == "render_"]
  381. def _get_def_callable(self, name):
  382. return getattr(self.module, "render_%s" % name)
  383. @property
  384. def last_modified(self):
  385. return self.module._modified_time
  386. class ModuleTemplate(Template):
  387. """A Template which is constructed given an existing Python module.
  388. e.g.::
  389. t = Template("this is a template")
  390. f = file("mymodule.py", "w")
  391. f.write(t.code)
  392. f.close()
  393. import mymodule
  394. t = ModuleTemplate(mymodule)
  395. print(t.render())
  396. """
  397. def __init__(
  398. self,
  399. module,
  400. module_filename=None,
  401. template=None,
  402. template_filename=None,
  403. module_source=None,
  404. template_source=None,
  405. output_encoding=None,
  406. encoding_errors="strict",
  407. format_exceptions=False,
  408. error_handler=None,
  409. lookup=None,
  410. cache_args=None,
  411. cache_impl="beaker",
  412. cache_enabled=True,
  413. cache_type=None,
  414. cache_dir=None,
  415. cache_url=None,
  416. include_error_handler=None,
  417. ):
  418. self.module_id = re.sub(r"\W", "_", module._template_uri)
  419. self.uri = module._template_uri
  420. self.input_encoding = module._source_encoding
  421. self.output_encoding = output_encoding
  422. self.encoding_errors = encoding_errors
  423. self.enable_loop = module._enable_loop
  424. self.module = module
  425. self.filename = template_filename
  426. ModuleInfo(
  427. module,
  428. module_filename,
  429. self,
  430. template_filename,
  431. module_source,
  432. template_source,
  433. module._template_uri,
  434. )
  435. self.callable_ = self.module.render_body
  436. self.format_exceptions = format_exceptions
  437. self.error_handler = error_handler
  438. self.include_error_handler = include_error_handler
  439. self.lookup = lookup
  440. self._setup_cache_args(
  441. cache_impl,
  442. cache_enabled,
  443. cache_args,
  444. cache_type,
  445. cache_dir,
  446. cache_url,
  447. )
  448. class DefTemplate(Template):
  449. """A :class:`.Template` which represents a callable def in a parent
  450. template."""
  451. def __init__(self, parent, callable_):
  452. self.parent = parent
  453. self.callable_ = callable_
  454. self.output_encoding = parent.output_encoding
  455. self.module = parent.module
  456. self.encoding_errors = parent.encoding_errors
  457. self.format_exceptions = parent.format_exceptions
  458. self.error_handler = parent.error_handler
  459. self.include_error_handler = parent.include_error_handler
  460. self.enable_loop = parent.enable_loop
  461. self.lookup = parent.lookup
  462. def get_def(self, name):
  463. return self.parent.get_def(name)
  464. class ModuleInfo:
  465. """Stores information about a module currently loaded into
  466. memory, provides reverse lookups of template source, module
  467. source code based on a module's identifier.
  468. """
  469. _modules = weakref.WeakValueDictionary()
  470. def __init__(
  471. self,
  472. module,
  473. module_filename,
  474. template,
  475. template_filename,
  476. module_source,
  477. template_source,
  478. template_uri,
  479. ):
  480. self.module = module
  481. self.module_filename = module_filename
  482. self.template_filename = template_filename
  483. self.module_source = module_source
  484. self.template_source = template_source
  485. self.template_uri = template_uri
  486. self._modules[module.__name__] = template._mmarker = self
  487. if module_filename:
  488. self._modules[module_filename] = self
  489. @classmethod
  490. def get_module_source_metadata(cls, module_source, full_line_map=False):
  491. source_map = re.search(
  492. r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S
  493. ).group(1)
  494. source_map = json.loads(source_map)
  495. source_map["line_map"] = {
  496. int(k): int(v) for k, v in source_map["line_map"].items()
  497. }
  498. if full_line_map:
  499. f_line_map = source_map["full_line_map"] = []
  500. line_map = source_map["line_map"]
  501. curr_templ_line = 1
  502. for mod_line in range(1, max(line_map)):
  503. if mod_line in line_map:
  504. curr_templ_line = line_map[mod_line]
  505. f_line_map.append(curr_templ_line)
  506. return source_map
  507. @property
  508. def code(self):
  509. if self.module_source is not None:
  510. return self.module_source
  511. else:
  512. return util.read_python_file(self.module_filename)
  513. @property
  514. def source(self):
  515. if self.template_source is None:
  516. data = util.read_file(self.template_filename)
  517. if self.module._source_encoding:
  518. return data.decode(self.module._source_encoding)
  519. else:
  520. return data
  521. elif self.module._source_encoding and not isinstance(
  522. self.template_source, str
  523. ):
  524. return self.template_source.decode(self.module._source_encoding)
  525. else:
  526. return self.template_source
  527. def _compile(template, text, filename, generate_magic_comment):
  528. lexer = template.lexer_cls(
  529. text,
  530. filename,
  531. input_encoding=template.input_encoding,
  532. preprocessor=template.preprocessor,
  533. )
  534. node = lexer.parse()
  535. source = codegen.compile(
  536. node,
  537. template.uri,
  538. filename,
  539. default_filters=template.default_filters,
  540. buffer_filters=template.buffer_filters,
  541. imports=template.imports,
  542. future_imports=template.future_imports,
  543. source_encoding=lexer.encoding,
  544. generate_magic_comment=generate_magic_comment,
  545. strict_undefined=template.strict_undefined,
  546. enable_loop=template.enable_loop,
  547. reserved_names=template.reserved_names,
  548. )
  549. return source, lexer
  550. def _compile_text(template, text, filename):
  551. identifier = template.module_id
  552. source, lexer = _compile(
  553. template, text, filename, generate_magic_comment=False
  554. )
  555. cid = identifier
  556. module = types.ModuleType(cid)
  557. code = compile(source, cid, "exec")
  558. # this exec() works for 2.4->3.3.
  559. exec(code, module.__dict__, module.__dict__)
  560. return (source, module)
  561. def _compile_module_file(template, text, filename, outputpath, module_writer):
  562. source, lexer = _compile(
  563. template, text, filename, generate_magic_comment=True
  564. )
  565. if isinstance(source, str):
  566. source = source.encode(lexer.encoding or "ascii")
  567. if module_writer:
  568. module_writer(source, outputpath)
  569. else:
  570. # make tempfiles in the same location as the ultimate
  571. # location. this ensures they're on the same filesystem,
  572. # avoiding synchronization issues.
  573. (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath))
  574. os.write(dest, source)
  575. os.close(dest)
  576. shutil.move(name, outputpath)
  577. def _get_module_info_from_callable(callable_):
  578. return _get_module_info(callable_.__globals__["__name__"])
  579. def _get_module_info(filename):
  580. return ModuleInfo._modules[filename]