Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# mako/exceptions.py 

2# Copyright 2006-2020 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"""exception classes""" 

8 

9import sys 

10import traceback 

11 

12from mako import compat 

13from mako import util 

14 

15 

16class MakoException(Exception): 

17 pass 

18 

19 

20class RuntimeException(MakoException): 

21 pass 

22 

23 

24def _format_filepos(lineno, pos, filename): 

25 if filename is None: 

26 return " at line: %d char: %d" % (lineno, pos) 

27 else: 

28 return " in file '%s' at line: %d char: %d" % (filename, lineno, pos) 

29 

30 

31class CompileException(MakoException): 

32 def __init__(self, message, source, lineno, pos, filename): 

33 MakoException.__init__( 

34 self, message + _format_filepos(lineno, pos, filename) 

35 ) 

36 self.lineno = lineno 

37 self.pos = pos 

38 self.filename = filename 

39 self.source = source 

40 

41 

42class SyntaxException(MakoException): 

43 def __init__(self, message, source, lineno, pos, filename): 

44 MakoException.__init__( 

45 self, message + _format_filepos(lineno, pos, filename) 

46 ) 

47 self.lineno = lineno 

48 self.pos = pos 

49 self.filename = filename 

50 self.source = source 

51 

52 

53class UnsupportedError(MakoException): 

54 

55 """raised when a retired feature is used.""" 

56 

57 

58class NameConflictError(MakoException): 

59 

60 """raised when a reserved word is used inappropriately""" 

61 

62 

63class TemplateLookupException(MakoException): 

64 pass 

65 

66 

67class TopLevelLookupException(TemplateLookupException): 

68 pass 

69 

70 

71class RichTraceback(object): 

72 

73 """Pull the current exception from the ``sys`` traceback and extracts 

74 Mako-specific template information. 

75 

76 See the usage examples in :ref:`handling_exceptions`. 

77 

78 """ 

79 

80 def __init__(self, error=None, traceback=None): 

81 self.source, self.lineno = "", 0 

82 

83 if error is None or traceback is None: 

84 t, value, tback = sys.exc_info() 

85 

86 if error is None: 

87 error = value or t 

88 

89 if traceback is None: 

90 traceback = tback 

91 

92 self.error = error 

93 self.records = self._init(traceback) 

94 

95 if isinstance(self.error, (CompileException, SyntaxException)): 

96 self.source = self.error.source 

97 self.lineno = self.error.lineno 

98 self._has_source = True 

99 

100 self._init_message() 

101 

102 @property 

103 def errorname(self): 

104 return compat.exception_name(self.error) 

105 

106 def _init_message(self): 

107 """Find a unicode representation of self.error""" 

108 try: 

109 self.message = compat.text_type(self.error) 

110 except UnicodeError: 

111 try: 

112 self.message = str(self.error) 

113 except UnicodeEncodeError: 

114 # Fallback to args as neither unicode nor 

115 # str(Exception(u'\xe6')) work in Python < 2.6 

116 self.message = self.error.args[0] 

117 if not isinstance(self.message, compat.text_type): 

118 self.message = compat.text_type(self.message, "ascii", "replace") 

119 

120 def _get_reformatted_records(self, records): 

121 for rec in records: 

122 if rec[6] is not None: 

123 yield (rec[4], rec[5], rec[2], rec[6]) 

124 else: 

125 yield tuple(rec[0:4]) 

126 

127 @property 

128 def traceback(self): 

129 """Return a list of 4-tuple traceback records (i.e. normal python 

130 format) with template-corresponding lines remapped to the originating 

131 template. 

132 

133 """ 

134 return list(self._get_reformatted_records(self.records)) 

135 

136 @property 

137 def reverse_records(self): 

138 return reversed(self.records) 

139 

140 @property 

141 def reverse_traceback(self): 

142 """Return the same data as traceback, except in reverse order. 

143 """ 

144 

145 return list(self._get_reformatted_records(self.reverse_records)) 

146 

147 def _init(self, trcback): 

148 """format a traceback from sys.exc_info() into 7-item tuples, 

149 containing the regular four traceback tuple items, plus the original 

150 template filename, the line number adjusted relative to the template 

151 source, and code line from that line number of the template.""" 

152 

153 import mako.template 

154 

155 mods = {} 

156 rawrecords = traceback.extract_tb(trcback) 

157 new_trcback = [] 

158 for filename, lineno, function, line in rawrecords: 

159 if not line: 

160 line = "" 

161 try: 

162 (line_map, template_lines, template_filename) = mods[filename] 

163 except KeyError: 

164 try: 

165 info = mako.template._get_module_info(filename) 

166 module_source = info.code 

167 template_source = info.source 

168 template_filename = ( 

169 info.template_filename or info.template_uri or filename 

170 ) 

171 except KeyError: 

172 # A normal .py file (not a Template) 

173 if not compat.py3k: 

174 try: 

175 fp = open(filename, "rb") 

176 encoding = util.parse_encoding(fp) 

177 fp.close() 

178 except IOError: 

179 encoding = None 

180 if encoding: 

181 line = line.decode(encoding) 

182 else: 

183 line = line.decode("ascii", "replace") 

184 new_trcback.append( 

185 ( 

186 filename, 

187 lineno, 

188 function, 

189 line, 

190 None, 

191 None, 

192 None, 

193 None, 

194 ) 

195 ) 

196 continue 

197 

198 template_ln = 1 

199 

200 mtm = mako.template.ModuleInfo 

201 source_map = mtm.get_module_source_metadata( 

202 module_source, full_line_map=True 

203 ) 

204 line_map = source_map["full_line_map"] 

205 

206 template_lines = [ 

207 line_ for line_ in template_source.split("\n") 

208 ] 

209 mods[filename] = (line_map, template_lines, template_filename) 

210 

211 template_ln = line_map[lineno - 1] 

212 

213 if template_ln <= len(template_lines): 

214 template_line = template_lines[template_ln - 1] 

215 else: 

216 template_line = None 

217 new_trcback.append( 

218 ( 

219 filename, 

220 lineno, 

221 function, 

222 line, 

223 template_filename, 

224 template_ln, 

225 template_line, 

226 template_source, 

227 ) 

228 ) 

229 if not self.source: 

230 for l in range(len(new_trcback) - 1, 0, -1): 

231 if new_trcback[l][5]: 

232 self.source = new_trcback[l][7] 

233 self.lineno = new_trcback[l][5] 

234 break 

235 else: 

236 if new_trcback: 

237 try: 

238 # A normal .py file (not a Template) 

239 fp = open(new_trcback[-1][0], "rb") 

240 encoding = util.parse_encoding(fp) 

241 if compat.py3k and not encoding: 

242 encoding = "utf-8" 

243 fp.seek(0) 

244 self.source = fp.read() 

245 fp.close() 

246 if encoding: 

247 self.source = self.source.decode(encoding) 

248 except IOError: 

249 self.source = "" 

250 self.lineno = new_trcback[-1][1] 

251 return new_trcback 

252 

253 

254def text_error_template(lookup=None): 

255 """Provides a template that renders a stack trace in a similar format to 

256 the Python interpreter, substituting source template filenames, line 

257 numbers and code for that of the originating source template, as 

258 applicable. 

259 

260 """ 

261 import mako.template 

262 

263 return mako.template.Template( 

264 r""" 

265<%page args="error=None, traceback=None"/> 

266<%! 

267 from mako.exceptions import RichTraceback 

268%>\ 

269<% 

270 tback = RichTraceback(error=error, traceback=traceback) 

271%>\ 

272Traceback (most recent call last): 

273% for (filename, lineno, function, line) in tback.traceback: 

274 File "${filename}", line ${lineno}, in ${function or '?'} 

275 ${line | trim} 

276% endfor 

277${tback.errorname}: ${tback.message} 

278""" 

279 ) 

280 

281 

282def _install_pygments(): 

283 global syntax_highlight, pygments_html_formatter 

284 from mako.ext.pygmentplugin import syntax_highlight # noqa 

285 from mako.ext.pygmentplugin import pygments_html_formatter # noqa 

286 

287 

288def _install_fallback(): 

289 global syntax_highlight, pygments_html_formatter 

290 from mako.filters import html_escape 

291 

292 pygments_html_formatter = None 

293 

294 def syntax_highlight(filename="", language=None): 

295 return html_escape 

296 

297 

298def _install_highlighting(): 

299 try: 

300 _install_pygments() 

301 except ImportError: 

302 _install_fallback() 

303 

304 

305_install_highlighting() 

306 

307 

308def html_error_template(): 

309 """Provides a template that renders a stack trace in an HTML format, 

310 providing an excerpt of code as well as substituting source template 

311 filenames, line numbers and code for that of the originating source 

312 template, as applicable. 

313 

314 The template's default ``encoding_errors`` value is 

315 ``'htmlentityreplace'``. The template has two options. With the 

316 ``full`` option disabled, only a section of an HTML document is 

317 returned. With the ``css`` option disabled, the default stylesheet 

318 won't be included. 

319 

320 """ 

321 import mako.template 

322 

323 return mako.template.Template( 

324 r""" 

325<%! 

326 from mako.exceptions import RichTraceback, syntax_highlight,\ 

327 pygments_html_formatter 

328%> 

329<%page args="full=True, css=True, error=None, traceback=None"/> 

330% if full: 

331<html> 

332<head> 

333 <title>Mako Runtime Error</title> 

334% endif 

335% if css: 

336 <style> 

337 body { font-family:verdana; margin:10px 30px 10px 30px;} 

338 .stacktrace { margin:5px 5px 5px 5px; } 

339 .highlight { padding:0px 10px 0px 10px; background-color:#9F9FDF; } 

340 .nonhighlight { padding:0px; background-color:#DFDFDF; } 

341 .sample { padding:10px; margin:10px 10px 10px 10px; 

342 font-family:monospace; } 

343 .sampleline { padding:0px 10px 0px 10px; } 

344 .sourceline { margin:5px 5px 10px 5px; font-family:monospace;} 

345 .location { font-size:80%; } 

346 .highlight { white-space:pre; } 

347 .sampleline { white-space:pre; } 

348 

349 % if pygments_html_formatter: 

350 ${pygments_html_formatter.get_style_defs()} 

351 .linenos { min-width: 2.5em; text-align: right; } 

352 pre { margin: 0; } 

353 .syntax-highlighted { padding: 0 10px; } 

354 .syntax-highlightedtable { border-spacing: 1px; } 

355 .nonhighlight { border-top: 1px solid #DFDFDF; 

356 border-bottom: 1px solid #DFDFDF; } 

357 .stacktrace .nonhighlight { margin: 5px 15px 10px; } 

358 .sourceline { margin: 0 0; font-family:monospace; } 

359 .code { background-color: #F8F8F8; width: 100%; } 

360 .error .code { background-color: #FFBDBD; } 

361 .error .syntax-highlighted { background-color: #FFBDBD; } 

362 % endif 

363 

364 </style> 

365% endif 

366% if full: 

367</head> 

368<body> 

369% endif 

370 

371<h2>Error !</h2> 

372<% 

373 tback = RichTraceback(error=error, traceback=traceback) 

374 src = tback.source 

375 line = tback.lineno 

376 if src: 

377 lines = src.split('\n') 

378 else: 

379 lines = None 

380%> 

381<h3>${tback.errorname}: ${tback.message|h}</h3> 

382 

383% if lines: 

384 <div class="sample"> 

385 <div class="nonhighlight"> 

386% for index in range(max(0, line-4),min(len(lines), line+5)): 

387 <% 

388 if pygments_html_formatter: 

389 pygments_html_formatter.linenostart = index + 1 

390 %> 

391 % if index + 1 == line: 

392 <% 

393 if pygments_html_formatter: 

394 old_cssclass = pygments_html_formatter.cssclass 

395 pygments_html_formatter.cssclass = 'error ' + old_cssclass 

396 %> 

397 ${lines[index] | syntax_highlight(language='mako')} 

398 <% 

399 if pygments_html_formatter: 

400 pygments_html_formatter.cssclass = old_cssclass 

401 %> 

402 % else: 

403 ${lines[index] | syntax_highlight(language='mako')} 

404 % endif 

405% endfor 

406 </div> 

407 </div> 

408% endif 

409 

410<div class="stacktrace"> 

411% for (filename, lineno, function, line) in tback.reverse_traceback: 

412 <div class="location">${filename}, line ${lineno}:</div> 

413 <div class="nonhighlight"> 

414 <% 

415 if pygments_html_formatter: 

416 pygments_html_formatter.linenostart = lineno 

417 %> 

418 <div class="sourceline">${line | syntax_highlight(filename)}</div> 

419 </div> 

420% endfor 

421</div> 

422 

423% if full: 

424</body> 

425</html> 

426% endif 

427""", 

428 output_encoding=sys.getdefaultencoding(), 

429 encoding_errors="htmlentityreplace", 

430 )