Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mako/template.py: 26%

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

243 statements  

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 

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``. 

123 

124 :param include_error_handler: An error handler that runs when this template 

125 is included within another one via the ``<%include>`` tag, and raises an 

126 error. Compare to the :paramref:`.Template.error_handler` option. 

127 

128 .. versionadded:: 1.0.6 

129 

130 .. seealso:: 

131 

132 :paramref:`.Template.error_handler` - top-level error handler function 

133 

134 :param input_encoding: Encoding of the template's source code. Can 

135 be used in lieu of the coding comment. See 

136 :ref:`usage_unicode` as well as :ref:`unicode_toplevel` for 

137 details on source encoding. 

138 

139 :param lookup: a :class:`.TemplateLookup` instance that will be used 

140 for all file lookups via the ``<%namespace>``, 

141 ``<%include>``, and ``<%inherit>`` tags. See 

142 :ref:`usage_templatelookup`. 

143 

144 :param module_directory: Filesystem location where generated 

145 Python module files will be placed. 

146 

147 :param module_filename: Overrides the filename of the generated 

148 Python module file. For advanced usage only. 

149 

150 :param module_writer: A callable which overrides how the Python 

151 module is written entirely. The callable is passed the 

152 encoded source content of the module and the destination 

153 path to be written to. The default behavior of module writing 

154 uses a tempfile in conjunction with a file move in order 

155 to make the operation atomic. So a user-defined module 

156 writing function that mimics the default behavior would be: 

157 

158 .. sourcecode:: python 

159 

160 import tempfile 

161 import os 

162 import shutil 

163 

164 def module_writer(source, outputpath): 

165 (dest, name) = \\ 

166 tempfile.mkstemp( 

167 dir=os.path.dirname(outputpath) 

168 ) 

169 

170 os.write(dest, source) 

171 os.close(dest) 

172 shutil.move(name, outputpath) 

173 

174 from mako.template import Template 

175 mytemplate = Template( 

176 filename="index.html", 

177 module_directory="/path/to/modules", 

178 module_writer=module_writer 

179 ) 

180 

181 The function is provided for unusual configurations where 

182 certain platform-specific permissions or other special 

183 steps are needed. 

184 

185 :param output_encoding: The encoding to use when :meth:`.render` 

186 is called. 

187 See :ref:`usage_unicode` as well as :ref:`unicode_toplevel`. 

188 

189 :param preprocessor: Python callable which will be passed 

190 the full template source before it is parsed. The return 

191 result of the callable will be used as the template source 

192 code. 

193 

194 :param lexer_cls: A :class:`.Lexer` class used to parse 

195 the template. The :class:`.Lexer` class is used by 

196 default. 

197 

198 .. versionadded:: 0.7.4 

199 

200 :param strict_undefined: Replaces the automatic usage of 

201 ``UNDEFINED`` for any undeclared variables not located in 

202 the :class:`.Context` with an immediate raise of 

203 ``NameError``. The advantage is immediate reporting of 

204 missing variables which include the name. 

205 

206 .. versionadded:: 0.3.6 

207 

208 :param uri: string URI or other identifier for this template. 

209 If not provided, the ``uri`` is generated from the filesystem 

210 path, or from the in-memory identity of a non-file-based 

211 template. The primary usage of the ``uri`` is to provide a key 

212 within :class:`.TemplateLookup`, as well as to generate the 

213 file path of the generated Python module file, if 

214 ``module_directory`` is specified. 

215 

216 """ 

217 

218 lexer_cls = Lexer 

219 

220 def __init__( 

221 self, 

222 text=None, 

223 filename=None, 

224 uri=None, 

225 format_exceptions=False, 

226 error_handler=None, 

227 lookup=None, 

228 output_encoding=None, 

229 encoding_errors="strict", 

230 module_directory=None, 

231 cache_args=None, 

232 cache_impl="beaker", 

233 cache_enabled=True, 

234 cache_type=None, 

235 cache_dir=None, 

236 cache_url=None, 

237 module_filename=None, 

238 input_encoding=None, 

239 module_writer=None, 

240 default_filters=None, 

241 buffer_filters=(), 

242 strict_undefined=False, 

243 imports=None, 

244 future_imports=None, 

245 enable_loop=True, 

246 preprocessor=None, 

247 lexer_cls=None, 

248 include_error_handler=None, 

249 ): 

250 if uri: 

251 self.module_id = re.sub(r"\W", "_", uri) 

252 self.uri = uri 

253 elif filename: 

254 self.module_id = re.sub(r"\W", "_", filename) 

255 drive, path = os.path.splitdrive(filename) 

256 path = os.path.normpath(path).replace(os.path.sep, "/") 

257 self.uri = path 

258 else: 

259 self.module_id = "memory:" + hex(id(self)) 

260 self.uri = self.module_id 

261 

262 u_norm = self.uri 

263 if u_norm.startswith("/"): 

264 u_norm = u_norm[1:] 

265 u_norm = os.path.normpath(u_norm) 

266 if u_norm.startswith(".."): 

267 raise exceptions.TemplateLookupException( 

268 'Template uri "%s" is invalid - ' 

269 "it cannot be relative outside " 

270 "of the root path." % self.uri 

271 ) 

272 

273 self.input_encoding = input_encoding 

274 self.output_encoding = output_encoding 

275 self.encoding_errors = encoding_errors 

276 self.enable_loop = enable_loop 

277 self.strict_undefined = strict_undefined 

278 self.module_writer = module_writer 

279 

280 if default_filters is None: 

281 self.default_filters = ["str"] 

282 else: 

283 self.default_filters = default_filters 

284 self.buffer_filters = buffer_filters 

285 

286 self.imports = imports 

287 self.future_imports = future_imports 

288 self.preprocessor = preprocessor 

289 

290 if lexer_cls is not None: 

291 self.lexer_cls = lexer_cls 

292 

293 # if plain text, compile code in memory only 

294 if text is not None: 

295 (code, module) = _compile_text(self, text, filename) 

296 self._code = code 

297 self._source = text 

298 ModuleInfo(module, None, self, filename, code, text, uri) 

299 elif filename is not None: 

300 # if template filename and a module directory, load 

301 # a filesystem-based module file, generating if needed 

302 if module_filename is not None: 

303 path = module_filename 

304 elif module_directory is not None: 

305 path = os.path.abspath( 

306 os.path.join( 

307 os.path.normpath(module_directory), u_norm + ".py" 

308 ) 

309 ) 

310 else: 

311 path = None 

312 module = self._compile_from_file(path, filename) 

313 else: 

314 raise exceptions.RuntimeException( 

315 "Template requires text or filename" 

316 ) 

317 

318 self.module = module 

319 self.filename = filename 

320 self.callable_ = self.module.render_body 

321 self.format_exceptions = format_exceptions 

322 self.error_handler = error_handler 

323 self.include_error_handler = include_error_handler 

324 self.lookup = lookup 

325 

326 self.module_directory = module_directory 

327 

328 self._setup_cache_args( 

329 cache_impl, 

330 cache_enabled, 

331 cache_args, 

332 cache_type, 

333 cache_dir, 

334 cache_url, 

335 ) 

336 

337 @util.memoized_property 

338 def reserved_names(self): 

339 if self.enable_loop: 

340 return codegen.RESERVED_NAMES 

341 else: 

342 return codegen.RESERVED_NAMES.difference(["loop"]) 

343 

344 def _setup_cache_args( 

345 self, 

346 cache_impl, 

347 cache_enabled, 

348 cache_args, 

349 cache_type, 

350 cache_dir, 

351 cache_url, 

352 ): 

353 self.cache_impl = cache_impl 

354 self.cache_enabled = cache_enabled 

355 self.cache_args = cache_args or {} 

356 # transfer deprecated cache_* args 

357 if cache_type: 

358 self.cache_args["type"] = cache_type 

359 if cache_dir: 

360 self.cache_args["dir"] = cache_dir 

361 if cache_url: 

362 self.cache_args["url"] = cache_url 

363 

364 def _compile_from_file(self, path, filename): 

365 if path is not None: 

366 util.verify_directory(os.path.dirname(path)) 

367 filemtime = os.stat(filename)[stat.ST_MTIME] 

368 if ( 

369 not os.path.exists(path) 

370 or os.stat(path)[stat.ST_MTIME] < filemtime 

371 ): 

372 data = util.read_file(filename) 

373 _compile_module_file( 

374 self, data, filename, path, self.module_writer 

375 ) 

376 module = compat.load_module(self.module_id, path) 

377 if module._magic_number != codegen.MAGIC_NUMBER: 

378 data = util.read_file(filename) 

379 _compile_module_file( 

380 self, data, filename, path, self.module_writer 

381 ) 

382 module = compat.load_module(self.module_id, path) 

383 ModuleInfo(module, path, self, filename, None, None, None) 

384 else: 

385 # template filename and no module directory, compile code 

386 # in memory 

387 data = util.read_file(filename) 

388 code, module = _compile_text(self, data, filename) 

389 self._source = None 

390 self._code = code 

391 ModuleInfo(module, None, self, filename, code, None, None) 

392 return module 

393 

394 @property 

395 def source(self): 

396 """Return the template source code for this :class:`.Template`.""" 

397 

398 return _get_module_info_from_callable(self.callable_).source 

399 

400 @property 

401 def code(self): 

402 """Return the module source code for this :class:`.Template`.""" 

403 

404 return _get_module_info_from_callable(self.callable_).code 

405 

406 @util.memoized_property 

407 def cache(self): 

408 return cache.Cache(self) 

409 

410 @property 

411 def cache_dir(self): 

412 return self.cache_args["dir"] 

413 

414 @property 

415 def cache_url(self): 

416 return self.cache_args["url"] 

417 

418 @property 

419 def cache_type(self): 

420 return self.cache_args["type"] 

421 

422 def render(self, *args, **data): 

423 """Render the output of this template as a string. 

424 

425 If the template specifies an output encoding, the string 

426 will be encoded accordingly, else the output is raw (raw 

427 output uses `StringIO` and can't handle multibyte 

428 characters). A :class:`.Context` object is created corresponding 

429 to the given data. Arguments that are explicitly declared 

430 by this template's internal rendering method are also 

431 pulled from the given ``*args``, ``**data`` members. 

432 

433 """ 

434 return runtime._render(self, self.callable_, args, data) 

435 

436 def render_unicode(self, *args, **data): 

437 """Render the output of this template as a unicode object.""" 

438 

439 return runtime._render( 

440 self, self.callable_, args, data, as_unicode=True 

441 ) 

442 

443 def render_context(self, context, *args, **kwargs): 

444 """Render this :class:`.Template` with the given context. 

445 

446 The data is written to the context's buffer. 

447 

448 """ 

449 if getattr(context, "_with_template", None) is None: 

450 context._set_with_template(self) 

451 runtime._render_context(self, self.callable_, context, *args, **kwargs) 

452 

453 def has_def(self, name): 

454 return hasattr(self.module, "render_%s" % name) 

455 

456 def get_def(self, name): 

457 """Return a def of this template as a :class:`.DefTemplate`.""" 

458 

459 return DefTemplate(self, getattr(self.module, "render_%s" % name)) 

460 

461 def list_defs(self): 

462 """return a list of defs in the template. 

463 

464 .. versionadded:: 1.0.4 

465 

466 """ 

467 return [i[7:] for i in dir(self.module) if i[:7] == "render_"] 

468 

469 def _get_def_callable(self, name): 

470 return getattr(self.module, "render_%s" % name) 

471 

472 @property 

473 def last_modified(self): 

474 return self.module._modified_time 

475 

476 

477class ModuleTemplate(Template): 

478 

479 """A Template which is constructed given an existing Python module. 

480 

481 e.g.:: 

482 

483 t = Template("this is a template") 

484 f = file("mymodule.py", "w") 

485 f.write(t.code) 

486 f.close() 

487 

488 import mymodule 

489 

490 t = ModuleTemplate(mymodule) 

491 print(t.render()) 

492 

493 """ 

494 

495 def __init__( 

496 self, 

497 module, 

498 module_filename=None, 

499 template=None, 

500 template_filename=None, 

501 module_source=None, 

502 template_source=None, 

503 output_encoding=None, 

504 encoding_errors="strict", 

505 format_exceptions=False, 

506 error_handler=None, 

507 lookup=None, 

508 cache_args=None, 

509 cache_impl="beaker", 

510 cache_enabled=True, 

511 cache_type=None, 

512 cache_dir=None, 

513 cache_url=None, 

514 include_error_handler=None, 

515 ): 

516 self.module_id = re.sub(r"\W", "_", module._template_uri) 

517 self.uri = module._template_uri 

518 self.input_encoding = module._source_encoding 

519 self.output_encoding = output_encoding 

520 self.encoding_errors = encoding_errors 

521 self.enable_loop = module._enable_loop 

522 

523 self.module = module 

524 self.filename = template_filename 

525 ModuleInfo( 

526 module, 

527 module_filename, 

528 self, 

529 template_filename, 

530 module_source, 

531 template_source, 

532 module._template_uri, 

533 ) 

534 

535 self.callable_ = self.module.render_body 

536 self.format_exceptions = format_exceptions 

537 self.error_handler = error_handler 

538 self.include_error_handler = include_error_handler 

539 self.lookup = lookup 

540 self._setup_cache_args( 

541 cache_impl, 

542 cache_enabled, 

543 cache_args, 

544 cache_type, 

545 cache_dir, 

546 cache_url, 

547 ) 

548 

549 

550class DefTemplate(Template): 

551 

552 """A :class:`.Template` which represents a callable def in a parent 

553 template.""" 

554 

555 def __init__(self, parent, callable_): 

556 self.parent = parent 

557 self.callable_ = callable_ 

558 self.output_encoding = parent.output_encoding 

559 self.module = parent.module 

560 self.encoding_errors = parent.encoding_errors 

561 self.format_exceptions = parent.format_exceptions 

562 self.error_handler = parent.error_handler 

563 self.include_error_handler = parent.include_error_handler 

564 self.enable_loop = parent.enable_loop 

565 self.lookup = parent.lookup 

566 

567 def get_def(self, name): 

568 return self.parent.get_def(name) 

569 

570 

571class ModuleInfo: 

572 

573 """Stores information about a module currently loaded into 

574 memory, provides reverse lookups of template source, module 

575 source code based on a module's identifier. 

576 

577 """ 

578 

579 _modules = weakref.WeakValueDictionary() 

580 

581 def __init__( 

582 self, 

583 module, 

584 module_filename, 

585 template, 

586 template_filename, 

587 module_source, 

588 template_source, 

589 template_uri, 

590 ): 

591 self.module = module 

592 self.module_filename = module_filename 

593 self.template_filename = template_filename 

594 self.module_source = module_source 

595 self.template_source = template_source 

596 self.template_uri = template_uri 

597 self._modules[module.__name__] = template._mmarker = self 

598 if module_filename: 

599 self._modules[module_filename] = self 

600 

601 @classmethod 

602 def get_module_source_metadata(cls, module_source, full_line_map=False): 

603 source_map = re.search( 

604 r"__M_BEGIN_METADATA(.+?)__M_END_METADATA", module_source, re.S 

605 ).group(1) 

606 source_map = json.loads(source_map) 

607 source_map["line_map"] = { 

608 int(k): int(v) for k, v in source_map["line_map"].items() 

609 } 

610 if full_line_map: 

611 f_line_map = source_map["full_line_map"] = [] 

612 line_map = source_map["line_map"] 

613 

614 curr_templ_line = 1 

615 for mod_line in range(1, max(line_map)): 

616 if mod_line in line_map: 

617 curr_templ_line = line_map[mod_line] 

618 f_line_map.append(curr_templ_line) 

619 return source_map 

620 

621 @property 

622 def code(self): 

623 if self.module_source is not None: 

624 return self.module_source 

625 else: 

626 return util.read_python_file(self.module_filename) 

627 

628 @property 

629 def source(self): 

630 if self.template_source is None: 

631 data = util.read_file(self.template_filename) 

632 if self.module._source_encoding: 

633 return data.decode(self.module._source_encoding) 

634 else: 

635 return data 

636 

637 elif self.module._source_encoding and not isinstance( 

638 self.template_source, str 

639 ): 

640 return self.template_source.decode(self.module._source_encoding) 

641 else: 

642 return self.template_source 

643 

644 

645def _compile(template, text, filename, generate_magic_comment): 

646 lexer = template.lexer_cls( 

647 text, 

648 filename, 

649 input_encoding=template.input_encoding, 

650 preprocessor=template.preprocessor, 

651 ) 

652 node = lexer.parse() 

653 source = codegen.compile( 

654 node, 

655 template.uri, 

656 filename, 

657 default_filters=template.default_filters, 

658 buffer_filters=template.buffer_filters, 

659 imports=template.imports, 

660 future_imports=template.future_imports, 

661 source_encoding=lexer.encoding, 

662 generate_magic_comment=generate_magic_comment, 

663 strict_undefined=template.strict_undefined, 

664 enable_loop=template.enable_loop, 

665 reserved_names=template.reserved_names, 

666 ) 

667 return source, lexer 

668 

669 

670def _compile_text(template, text, filename): 

671 identifier = template.module_id 

672 source, lexer = _compile( 

673 template, text, filename, generate_magic_comment=False 

674 ) 

675 

676 cid = identifier 

677 module = types.ModuleType(cid) 

678 code = compile(source, cid, "exec") 

679 

680 # this exec() works for 2.4->3.3. 

681 exec(code, module.__dict__, module.__dict__) 

682 return (source, module) 

683 

684 

685def _compile_module_file(template, text, filename, outputpath, module_writer): 

686 source, lexer = _compile( 

687 template, text, filename, generate_magic_comment=True 

688 ) 

689 

690 if isinstance(source, str): 

691 source = source.encode(lexer.encoding or "ascii") 

692 

693 if module_writer: 

694 module_writer(source, outputpath) 

695 else: 

696 # make tempfiles in the same location as the ultimate 

697 # location. this ensures they're on the same filesystem, 

698 # avoiding synchronization issues. 

699 (dest, name) = tempfile.mkstemp(dir=os.path.dirname(outputpath)) 

700 

701 os.write(dest, source) 

702 os.close(dest) 

703 shutil.move(name, outputpath) 

704 

705 

706def _get_module_info_from_callable(callable_): 

707 return _get_module_info(callable_.__globals__["__name__"]) 

708 

709 

710def _get_module_info(filename): 

711 return ModuleInfo._modules[filename]