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