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

241 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.replace("\\", "/").lstrip("/") 

263 u_norm = os.path.normpath(u_norm) 

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

265 raise exceptions.TemplateLookupException( 

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

267 "it cannot be relative outside " 

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

269 ) 

270 

271 self.input_encoding = input_encoding 

272 self.output_encoding = output_encoding 

273 self.encoding_errors = encoding_errors 

274 self.enable_loop = enable_loop 

275 self.strict_undefined = strict_undefined 

276 self.module_writer = module_writer 

277 

278 if default_filters is None: 

279 self.default_filters = ["str"] 

280 else: 

281 self.default_filters = default_filters 

282 self.buffer_filters = buffer_filters 

283 

284 self.imports = imports 

285 self.future_imports = future_imports 

286 self.preprocessor = preprocessor 

287 

288 if lexer_cls is not None: 

289 self.lexer_cls = lexer_cls 

290 

291 # if plain text, compile code in memory only 

292 if text is not None: 

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

294 self._code = code 

295 self._source = text 

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

297 elif filename is not None: 

298 # if template filename and a module directory, load 

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

300 if module_filename is not None: 

301 path = module_filename 

302 elif module_directory is not None: 

303 path = os.path.abspath( 

304 os.path.join( 

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

306 ) 

307 ) 

308 else: 

309 path = None 

310 module = self._compile_from_file(path, filename) 

311 else: 

312 raise exceptions.RuntimeException( 

313 "Template requires text or filename" 

314 ) 

315 

316 self.module = module 

317 self.filename = filename 

318 self.callable_ = self.module.render_body 

319 self.format_exceptions = format_exceptions 

320 self.error_handler = error_handler 

321 self.include_error_handler = include_error_handler 

322 self.lookup = lookup 

323 

324 self.module_directory = module_directory 

325 

326 self._setup_cache_args( 

327 cache_impl, 

328 cache_enabled, 

329 cache_args, 

330 cache_type, 

331 cache_dir, 

332 cache_url, 

333 ) 

334 

335 @util.memoized_property 

336 def reserved_names(self): 

337 if self.enable_loop: 

338 return codegen.RESERVED_NAMES 

339 else: 

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

341 

342 def _setup_cache_args( 

343 self, 

344 cache_impl, 

345 cache_enabled, 

346 cache_args, 

347 cache_type, 

348 cache_dir, 

349 cache_url, 

350 ): 

351 self.cache_impl = cache_impl 

352 self.cache_enabled = cache_enabled 

353 self.cache_args = cache_args or {} 

354 # transfer deprecated cache_* args 

355 if cache_type: 

356 self.cache_args["type"] = cache_type 

357 if cache_dir: 

358 self.cache_args["dir"] = cache_dir 

359 if cache_url: 

360 self.cache_args["url"] = cache_url 

361 

362 def _compile_from_file(self, path, filename): 

363 if path is not None: 

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

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

366 if ( 

367 not os.path.exists(path) 

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

369 ): 

370 data = util.read_file(filename) 

371 _compile_module_file( 

372 self, data, filename, path, self.module_writer 

373 ) 

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

375 if module._magic_number != codegen.MAGIC_NUMBER: 

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 ModuleInfo(module, path, self, filename, None, None, None) 

382 else: 

383 # template filename and no module directory, compile code 

384 # in memory 

385 data = util.read_file(filename) 

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

387 self._source = None 

388 self._code = code 

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

390 return module 

391 

392 @property 

393 def source(self): 

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

395 

396 return _get_module_info_from_callable(self.callable_).source 

397 

398 @property 

399 def code(self): 

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

401 

402 return _get_module_info_from_callable(self.callable_).code 

403 

404 @util.memoized_property 

405 def cache(self): 

406 return cache.Cache(self) 

407 

408 @property 

409 def cache_dir(self): 

410 return self.cache_args["dir"] 

411 

412 @property 

413 def cache_url(self): 

414 return self.cache_args["url"] 

415 

416 @property 

417 def cache_type(self): 

418 return self.cache_args["type"] 

419 

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

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

422 

423 If the template specifies an output encoding, the string 

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

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

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

427 to the given data. Arguments that are explicitly declared 

428 by this template's internal rendering method are also 

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

430 

431 """ 

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

433 

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

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

436 

437 return runtime._render( 

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

439 ) 

440 

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

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

443 

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

445 

446 """ 

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

448 context._set_with_template(self) 

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

450 

451 def has_def(self, name): 

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

453 

454 def get_def(self, name): 

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

456 

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

458 

459 def list_defs(self): 

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

461 

462 .. versionadded:: 1.0.4 

463 

464 """ 

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

466 

467 def _get_def_callable(self, name): 

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

469 

470 @property 

471 def last_modified(self): 

472 return self.module._modified_time 

473 

474 

475class ModuleTemplate(Template): 

476 

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

478 

479 e.g.:: 

480 

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

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

483 f.write(t.code) 

484 f.close() 

485 

486 import mymodule 

487 

488 t = ModuleTemplate(mymodule) 

489 print(t.render()) 

490 

491 """ 

492 

493 def __init__( 

494 self, 

495 module, 

496 module_filename=None, 

497 template=None, 

498 template_filename=None, 

499 module_source=None, 

500 template_source=None, 

501 output_encoding=None, 

502 encoding_errors="strict", 

503 format_exceptions=False, 

504 error_handler=None, 

505 lookup=None, 

506 cache_args=None, 

507 cache_impl="beaker", 

508 cache_enabled=True, 

509 cache_type=None, 

510 cache_dir=None, 

511 cache_url=None, 

512 include_error_handler=None, 

513 ): 

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

515 self.uri = module._template_uri 

516 self.input_encoding = module._source_encoding 

517 self.output_encoding = output_encoding 

518 self.encoding_errors = encoding_errors 

519 self.enable_loop = module._enable_loop 

520 

521 self.module = module 

522 self.filename = template_filename 

523 ModuleInfo( 

524 module, 

525 module_filename, 

526 self, 

527 template_filename, 

528 module_source, 

529 template_source, 

530 module._template_uri, 

531 ) 

532 

533 self.callable_ = self.module.render_body 

534 self.format_exceptions = format_exceptions 

535 self.error_handler = error_handler 

536 self.include_error_handler = include_error_handler 

537 self.lookup = lookup 

538 self._setup_cache_args( 

539 cache_impl, 

540 cache_enabled, 

541 cache_args, 

542 cache_type, 

543 cache_dir, 

544 cache_url, 

545 ) 

546 

547 

548class DefTemplate(Template): 

549 

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

551 template.""" 

552 

553 def __init__(self, parent, callable_): 

554 self.parent = parent 

555 self.callable_ = callable_ 

556 self.output_encoding = parent.output_encoding 

557 self.module = parent.module 

558 self.encoding_errors = parent.encoding_errors 

559 self.format_exceptions = parent.format_exceptions 

560 self.error_handler = parent.error_handler 

561 self.include_error_handler = parent.include_error_handler 

562 self.enable_loop = parent.enable_loop 

563 self.lookup = parent.lookup 

564 

565 def get_def(self, name): 

566 return self.parent.get_def(name) 

567 

568 

569class ModuleInfo: 

570 

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

572 memory, provides reverse lookups of template source, module 

573 source code based on a module's identifier. 

574 

575 """ 

576 

577 _modules = weakref.WeakValueDictionary() 

578 

579 def __init__( 

580 self, 

581 module, 

582 module_filename, 

583 template, 

584 template_filename, 

585 module_source, 

586 template_source, 

587 template_uri, 

588 ): 

589 self.module = module 

590 self.module_filename = module_filename 

591 self.template_filename = template_filename 

592 self.module_source = module_source 

593 self.template_source = template_source 

594 self.template_uri = template_uri 

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

596 if module_filename: 

597 self._modules[module_filename] = self 

598 

599 @classmethod 

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

601 source_map = re.search( 

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

603 ).group(1) 

604 source_map = json.loads(source_map) 

605 source_map["line_map"] = { 

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

607 } 

608 if full_line_map: 

609 f_line_map = source_map["full_line_map"] = [] 

610 line_map = source_map["line_map"] 

611 

612 curr_templ_line = 1 

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

614 if mod_line in line_map: 

615 curr_templ_line = line_map[mod_line] 

616 f_line_map.append(curr_templ_line) 

617 return source_map 

618 

619 @property 

620 def code(self): 

621 if self.module_source is not None: 

622 return self.module_source 

623 else: 

624 return util.read_python_file(self.module_filename) 

625 

626 @property 

627 def source(self): 

628 if self.template_source is None: 

629 data = util.read_file(self.template_filename) 

630 if self.module._source_encoding: 

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

632 else: 

633 return data 

634 

635 elif self.module._source_encoding and not isinstance( 

636 self.template_source, str 

637 ): 

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

639 else: 

640 return self.template_source 

641 

642 

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

644 lexer = template.lexer_cls( 

645 text, 

646 filename, 

647 input_encoding=template.input_encoding, 

648 preprocessor=template.preprocessor, 

649 ) 

650 node = lexer.parse() 

651 source = codegen.compile( 

652 node, 

653 template.uri, 

654 filename, 

655 default_filters=template.default_filters, 

656 buffer_filters=template.buffer_filters, 

657 imports=template.imports, 

658 future_imports=template.future_imports, 

659 source_encoding=lexer.encoding, 

660 generate_magic_comment=generate_magic_comment, 

661 strict_undefined=template.strict_undefined, 

662 enable_loop=template.enable_loop, 

663 reserved_names=template.reserved_names, 

664 ) 

665 return source, lexer 

666 

667 

668def _compile_text(template, text, filename): 

669 identifier = template.module_id 

670 source, lexer = _compile( 

671 template, text, filename, generate_magic_comment=False 

672 ) 

673 

674 cid = identifier 

675 module = types.ModuleType(cid) 

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

677 

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

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

680 return (source, module) 

681 

682 

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

684 source, lexer = _compile( 

685 template, text, filename, generate_magic_comment=True 

686 ) 

687 

688 if isinstance(source, str): 

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

690 

691 if module_writer: 

692 module_writer(source, outputpath) 

693 else: 

694 # make tempfiles in the same location as the ultimate 

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

696 # avoiding synchronization issues. 

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

698 

699 os.write(dest, source) 

700 os.close(dest) 

701 shutil.move(name, outputpath) 

702 

703 

704def _get_module_info_from_callable(callable_): 

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

706 

707 

708def _get_module_info(filename): 

709 return ModuleInfo._modules[filename]