backlash 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- backlash/__init__.py +3 -0
- backlash/_compat.py +78 -0
- backlash/console.py +213 -0
- backlash/debug.py +193 -0
- backlash/frtools.py +27 -0
- backlash/repr.py +305 -0
- backlash/statics/FONT_LICENSE +202 -0
- backlash/statics/console.png +0 -0
- backlash/statics/debugger.js +202 -0
- backlash/statics/jquery.js +167 -0
- backlash/statics/less.png +0 -0
- backlash/statics/more.png +0 -0
- backlash/statics/opensans.ttf +0 -0
- backlash/statics/source.png +0 -0
- backlash/statics/style.css +113 -0
- backlash/tbtools.py +526 -0
- backlash/trace_errors/__init__.py +8 -0
- backlash/trace_errors/sentry.py +6 -0
- backlash/tracing/__init__.py +1 -0
- backlash/tracing/errors/__init__.py +1 -0
- backlash/tracing/errors/middleware.py +85 -0
- backlash/tracing/reporters/__init__.py +1 -0
- backlash/tracing/reporters/mail.py +126 -0
- backlash/tracing/reporters/sentry.py +40 -0
- backlash/tracing/slowrequests/__init__.py +1 -0
- backlash/tracing/slowrequests/middleware.py +113 -0
- backlash/tracing/slowrequests/timer.py +107 -0
- backlash/utils.py +42 -0
- backlash-0.4.0.dist-info/METADATA +135 -0
- backlash-0.4.0.dist-info/RECORD +34 -0
- backlash-0.4.0.dist-info/WHEEL +5 -0
- backlash-0.4.0.dist-info/licenses/AUTHORS +15 -0
- backlash-0.4.0.dist-info/licenses/LICENSE +7 -0
- backlash-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
@font-face {
|
|
2
|
+
font-family: 'Open Sans';
|
|
3
|
+
font-style: normal;
|
|
4
|
+
font-weight: normal;
|
|
5
|
+
src: local('Open Sans'), local('OpenSans-Regular'),
|
|
6
|
+
url('?__debugger__=yes&cmd=resource&f=opensans.ttf') format('truetype');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body, input { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
|
10
|
+
'Verdana', sans-serif; color: #000; text-align: center;
|
|
11
|
+
margin: 1em; padding: 0; font-size: 15px; }
|
|
12
|
+
h1, h2, h3 { font-family: 'Open Sans', 'Lucida Grande', 'Lucida Sans Unicode',
|
|
13
|
+
'Geneva', 'Verdana', sans-serif; font-weight: normal; }
|
|
14
|
+
|
|
15
|
+
input { background-color: #fff; margin: 0; text-align: left;
|
|
16
|
+
outline: none !important; }
|
|
17
|
+
a { color: #11557C; }
|
|
18
|
+
a:hover { color: #177199; }
|
|
19
|
+
pre, code, table.source,
|
|
20
|
+
textarea { font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans Mono',
|
|
21
|
+
monospace; font-size: 14px; }
|
|
22
|
+
|
|
23
|
+
div.debugger { text-align: left; padding: 12px; margin: auto;
|
|
24
|
+
background-color: white; }
|
|
25
|
+
h1 { font-size: 36px; margin: 0 0 0.3em 0; }
|
|
26
|
+
div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap; }
|
|
27
|
+
div.explanation { margin: 20px 13px; font-size: 15px; color: #555; }
|
|
28
|
+
div.footer { font-size: 13px; text-align: right; margin: 30px 0;
|
|
29
|
+
color: #86989B; }
|
|
30
|
+
|
|
31
|
+
h2 { font-size: 16px; margin: 1.3em 0 0.0 0; padding: 9px;
|
|
32
|
+
background-color: #11557C; color: white; }
|
|
33
|
+
h2 em, h3 em { font-style: normal; color: #A5D6D9; font-weight: normal; }
|
|
34
|
+
|
|
35
|
+
div.traceback, div.plain { border: 1px solid #ddd; margin: 0 0 1em 0; padding: 10px; }
|
|
36
|
+
div.plain p { margin: 0; }
|
|
37
|
+
div.plain textarea,
|
|
38
|
+
div.plain pre { margin: 10px 0 0 0; padding: 4px;
|
|
39
|
+
background-color: #E8EFF0; border: 1px solid #D3E7E9; }
|
|
40
|
+
div.plain textarea { width: 99%; height: 300px; }
|
|
41
|
+
div.traceback h3 { font-size: 1em; margin: 0 0 0.8em 0; }
|
|
42
|
+
div.traceback ul { list-style: none; margin: 0; padding: 0 0 0 1em; }
|
|
43
|
+
div.traceback h4 { font-size: 13px; font-weight: normal; margin: 0.7em 0 0.1em 0; }
|
|
44
|
+
div.traceback pre { margin: 0; padding: 5px 0 3px 15px;
|
|
45
|
+
background-color: #E8EFF0; border: 1px solid #D3E7E9; }
|
|
46
|
+
div.traceback pre,
|
|
47
|
+
div.box table.source { white-space: pre-wrap; /* css-3 should we be so lucky... */
|
|
48
|
+
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
|
49
|
+
white-space: -pre-wrap; /* Opera 4-6 ?? */
|
|
50
|
+
white-space: -o-pre-wrap; /* Opera 7 ?? */
|
|
51
|
+
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
|
52
|
+
_white-space: pre; /* IE only hack to re-specify in
|
|
53
|
+
addition to word-wrap */ }
|
|
54
|
+
div.traceback pre:hover { background-color: #DDECEE; color: black; cursor: pointer; }
|
|
55
|
+
div.traceback blockquote { margin: 1em 0 0 0; padding: 0; }
|
|
56
|
+
div.traceback img { float: right; padding: 2px; margin: -3px 2px 0 0; display: none; }
|
|
57
|
+
div.traceback img:hover { background-color: #ddd; cursor: pointer;
|
|
58
|
+
border-color: #BFDDE0; }
|
|
59
|
+
div.traceback pre:hover img { display: block; }
|
|
60
|
+
div.traceback cite.filename { font-style: normal; color: #3B666B; }
|
|
61
|
+
|
|
62
|
+
pre.console { border: 1px solid #ccc; background: white!important;
|
|
63
|
+
color: black; padding: 5px!important;
|
|
64
|
+
margin: 3px 0 0 0!important; cursor: default!important;
|
|
65
|
+
max-height: 400px; overflow: auto; }
|
|
66
|
+
pre.console form { color: #555; }
|
|
67
|
+
pre.console input { background-color: transparent; color: #555;
|
|
68
|
+
width: 90%; font-family: 'Consolas', 'Deja Vu Sans Mono',
|
|
69
|
+
'Bitstream Vera Sans Mono', monospace; font-size: 14px;
|
|
70
|
+
border: none!important; }
|
|
71
|
+
|
|
72
|
+
span.string { color: #30799B; }
|
|
73
|
+
span.number { color: #9C1A1C; }
|
|
74
|
+
span.help { color: #3A7734; }
|
|
75
|
+
span.object { color: #485F6E; }
|
|
76
|
+
span.extended { opacity: 0.5; }
|
|
77
|
+
span.extended:hover { opacity: 1; }
|
|
78
|
+
a.toggle { text-decoration: none; background-repeat: no-repeat;
|
|
79
|
+
background-position: center center;
|
|
80
|
+
background-image: url(?__debugger__=yes&cmd=resource&f=more.png); }
|
|
81
|
+
a.toggle:hover { background-color: #444; }
|
|
82
|
+
a.open { background-image: url(?__debugger__=yes&cmd=resource&f=less.png); }
|
|
83
|
+
|
|
84
|
+
pre.console div.traceback,
|
|
85
|
+
pre.console div.box { margin: 5px 10px; white-space: normal;
|
|
86
|
+
border: 1px solid #11557C; padding: 10px;
|
|
87
|
+
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
|
88
|
+
'Verdana', sans-serif; }
|
|
89
|
+
pre.console div.box h3,
|
|
90
|
+
pre.console div.traceback h3 { margin: -10px -10px 10px -10px; padding: 5px;
|
|
91
|
+
background: #11557C; color: white; }
|
|
92
|
+
|
|
93
|
+
pre.console div.traceback pre:hover { cursor: default; background: #E8EFF0; }
|
|
94
|
+
pre.console div.traceback pre.syntaxerror { background: inherit; border: none;
|
|
95
|
+
margin: 20px -10px -10px -10px;
|
|
96
|
+
padding: 10px; border-top: 1px solid #BFDDE0;
|
|
97
|
+
background: #E8EFF0; }
|
|
98
|
+
pre.console div.noframe-traceback pre.syntaxerror { margin-top: -10px; border: none; }
|
|
99
|
+
|
|
100
|
+
pre.console div.box pre.repr { padding: 0; margin: 0; background-color: white; border: none; }
|
|
101
|
+
pre.console div.box table { margin-top: 6px; }
|
|
102
|
+
pre.console div.box pre { border: none; }
|
|
103
|
+
pre.console div.box pre.help { background-color: white; }
|
|
104
|
+
pre.console div.box pre.help:hover { cursor: default; }
|
|
105
|
+
pre.console table tr { vertical-align: top; }
|
|
106
|
+
div.console { border: 1px solid #ccc; padding: 4px; background-color: #fafafa; }
|
|
107
|
+
|
|
108
|
+
div.box table.source { border-collapse: collapse; width: 100%; background: #E8EFF0 }
|
|
109
|
+
div.box table.source td { border-top: 1px solid #E8EFF0; padding: 4px 0 4px 10px; }
|
|
110
|
+
div.box table.source td.lineno { color: #999; padding-right: 10px; width: 1px; }
|
|
111
|
+
div.box table.source tr.in-frame { background-color: white; }
|
|
112
|
+
div.box table.source tr.current { background-color: #EEF7F8; color: #23707E; }
|
|
113
|
+
div.sourceview { max-height: 400px; overflow: auto; border: 1px solid #ccc; }
|
backlash/tbtools.py
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
werkzeug.debug.tbtools
|
|
4
|
+
~~~~~~~~~~~~~~~~~~~~~~
|
|
5
|
+
|
|
6
|
+
This module provides various traceback related utility functions.
|
|
7
|
+
|
|
8
|
+
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
|
9
|
+
:license: BSD.
|
|
10
|
+
"""
|
|
11
|
+
import re
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import json
|
|
15
|
+
import inspect
|
|
16
|
+
import traceback
|
|
17
|
+
import codecs
|
|
18
|
+
from tokenize import TokenError
|
|
19
|
+
|
|
20
|
+
from backlash.utils import escape
|
|
21
|
+
from backlash.console import Console
|
|
22
|
+
|
|
23
|
+
from backlash._compat import PY2, text_, native_, string_types, text_type, exec_, urlopen
|
|
24
|
+
|
|
25
|
+
_coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
|
|
26
|
+
_line_re = re.compile(r'^(.*?)$', re.MULTILINE)
|
|
27
|
+
_funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
|
|
28
|
+
UTF8_COOKIE = '\xef\xbb\xbf'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
HEADER = text_('''\
|
|
32
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
|
33
|
+
"http://www.w3.org/TR/html4/loose.dtd">
|
|
34
|
+
<html>
|
|
35
|
+
<head>
|
|
36
|
+
<title>%(title)s // Backlash</title>
|
|
37
|
+
<link rel="stylesheet" href="?__debugger__=yes&cmd=resource&f=style.css" type="text/css">
|
|
38
|
+
<!-- We need to make sure this has a favicon so that the debugger does not by
|
|
39
|
+
accident trigger a request to /favicon.ico which might change the application
|
|
40
|
+
state. -->
|
|
41
|
+
<link rel="shortcut icon" href="?__debugger__=yes&cmd=resource&f=console.png">
|
|
42
|
+
<script type="text/javascript" src="?__debugger__=yes&cmd=resource&f=jquery.js"></script>
|
|
43
|
+
<script type="text/javascript" src="?__debugger__=yes&cmd=resource&f=debugger.js"></script>
|
|
44
|
+
<script type="text/javascript">
|
|
45
|
+
var TRACEBACK = %(traceback_id)d,
|
|
46
|
+
CONSOLE_MODE = %(console)s,
|
|
47
|
+
EVALEX = %(evalex)s,
|
|
48
|
+
SECRET = "%(secret)s";
|
|
49
|
+
</script>
|
|
50
|
+
</head>
|
|
51
|
+
<body>
|
|
52
|
+
<div class="debugger">
|
|
53
|
+
''')
|
|
54
|
+
|
|
55
|
+
FOOTER = text_('''\
|
|
56
|
+
<div class="footer">
|
|
57
|
+
<strong class="arthur">Backlash</strong>, using
|
|
58
|
+
the Werkzeug Debugger.
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</body>
|
|
62
|
+
</html>
|
|
63
|
+
''')
|
|
64
|
+
|
|
65
|
+
PAGE_HTML = HEADER + text_('''\
|
|
66
|
+
<h1>%(exception_type)s</h1>
|
|
67
|
+
<div class="detail">
|
|
68
|
+
<p class="errormsg">%(exception)s</p>
|
|
69
|
+
</div>
|
|
70
|
+
<h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
|
|
71
|
+
%(summary)s
|
|
72
|
+
<div class="plain">
|
|
73
|
+
<form action="/?__debugger__=yes&cmd=paste" method="post">
|
|
74
|
+
<p>
|
|
75
|
+
<input type="hidden" name="language" value="pytb">
|
|
76
|
+
This is the Copy/Paste friendly version of the traceback. <span
|
|
77
|
+
class="pastemessage">You can also paste this traceback into
|
|
78
|
+
a <a href="https://gist.github.com/">gist</a>:
|
|
79
|
+
<input type="submit" value="create paste"></span>
|
|
80
|
+
</p>
|
|
81
|
+
<textarea cols="50" rows="10" name="code" readonly>%(plaintext)s</textarea>
|
|
82
|
+
</form>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="explanation">
|
|
85
|
+
The debugger caught an exception in your WSGI application. You can now
|
|
86
|
+
look at the traceback which led to the error. <span class="nojavascript">
|
|
87
|
+
If you enable JavaScript you can also use additional features such as code
|
|
88
|
+
execution (if the evalex feature is enabled), automatic pasting of the
|
|
89
|
+
exceptions and much more.</span>
|
|
90
|
+
</div>
|
|
91
|
+
''') + FOOTER + text_('''
|
|
92
|
+
<!--
|
|
93
|
+
|
|
94
|
+
%(plaintext_cs)s
|
|
95
|
+
|
|
96
|
+
-->
|
|
97
|
+
''')
|
|
98
|
+
|
|
99
|
+
CONSOLE_HTML = HEADER + text_('''\
|
|
100
|
+
<h1>Interactive Console</h1>
|
|
101
|
+
<div class="explanation">
|
|
102
|
+
In this console you can execute Python expressions in the context of the
|
|
103
|
+
application. The initial namespace was created by the debugger automatically.
|
|
104
|
+
</div>
|
|
105
|
+
<div class="console"><div class="inner">The Console requires JavaScript.</div></div>
|
|
106
|
+
''') + FOOTER
|
|
107
|
+
|
|
108
|
+
SUMMARY_HTML = text_('''\
|
|
109
|
+
<div class="%(classes)s">
|
|
110
|
+
%(title)s
|
|
111
|
+
<ul>%(frames)s</ul>
|
|
112
|
+
%(description)s
|
|
113
|
+
</div>
|
|
114
|
+
''')
|
|
115
|
+
|
|
116
|
+
FRAME_HTML = text_('''\
|
|
117
|
+
<div class="frame" id="frame-%(id)d">
|
|
118
|
+
<h4>File <cite class="filename">"%(filename)s"</cite>,
|
|
119
|
+
line <em class="line">%(lineno)s</em>,
|
|
120
|
+
in <code class="function">%(function_name)s</code></h4>
|
|
121
|
+
<pre>%(current_line)s</pre>
|
|
122
|
+
</div>
|
|
123
|
+
''')
|
|
124
|
+
|
|
125
|
+
SOURCE_TABLE_HTML = text_('<table class=source>%s</table>')
|
|
126
|
+
|
|
127
|
+
SOURCE_LINE_HTML = text_('''\
|
|
128
|
+
<tr class="%(classes)s">
|
|
129
|
+
<td class=lineno>%(lineno)s</td>
|
|
130
|
+
<td>%(code)s</td>
|
|
131
|
+
</tr>
|
|
132
|
+
''')
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def render_console_html(secret):
|
|
136
|
+
return CONSOLE_HTML % {
|
|
137
|
+
'evalex': 'true',
|
|
138
|
+
'console': 'true',
|
|
139
|
+
'title': 'Console',
|
|
140
|
+
'secret': secret,
|
|
141
|
+
'traceback_id': -1
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_current_traceback(show_hidden_frames=False, skip=0, context=None, exc_info=None):
|
|
146
|
+
"""Get the current exception info as `Traceback` object. Per default
|
|
147
|
+
calling this method will reraise system exceptions such as generator exit,
|
|
148
|
+
system exit or others. This behavior can be disabled by passing `False`
|
|
149
|
+
to the function as first parameter.
|
|
150
|
+
"""
|
|
151
|
+
if exc_info is None:
|
|
152
|
+
exc_info = sys.exc_info()
|
|
153
|
+
|
|
154
|
+
exc_type, exc_value, tb = exc_info
|
|
155
|
+
for x in range(skip):
|
|
156
|
+
if tb.tb_next is None:
|
|
157
|
+
break
|
|
158
|
+
tb = tb.tb_next
|
|
159
|
+
tb = Traceback(exc_type, exc_value, tb, context=context)
|
|
160
|
+
if not show_hidden_frames:
|
|
161
|
+
tb.filter_hidden_frames()
|
|
162
|
+
return tb
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class Line(object):
|
|
166
|
+
"""Helper for the source renderer."""
|
|
167
|
+
__slots__ = ('lineno', 'code', 'in_frame', 'current')
|
|
168
|
+
|
|
169
|
+
def __init__(self, lineno, code):
|
|
170
|
+
self.lineno = lineno
|
|
171
|
+
self.code = code
|
|
172
|
+
self.in_frame = False
|
|
173
|
+
self.current = False
|
|
174
|
+
|
|
175
|
+
def classes(self):
|
|
176
|
+
rv = ['line']
|
|
177
|
+
if self.in_frame:
|
|
178
|
+
rv.append('in-frame')
|
|
179
|
+
if self.current:
|
|
180
|
+
rv.append('current')
|
|
181
|
+
return rv
|
|
182
|
+
classes = property(classes)
|
|
183
|
+
|
|
184
|
+
def render(self):
|
|
185
|
+
return SOURCE_LINE_HTML % {
|
|
186
|
+
'classes': text_(' ').join(self.classes),
|
|
187
|
+
'lineno': self.lineno,
|
|
188
|
+
'code': escape(self.code)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Traceback(object):
|
|
193
|
+
"""Wraps a traceback."""
|
|
194
|
+
|
|
195
|
+
def __init__(self, exc_type, exc_value, tb, context=None):
|
|
196
|
+
self.exc_type = exc_type
|
|
197
|
+
self.exc_value = exc_value
|
|
198
|
+
self.exc_info = (exc_type, exc_value, tb,)
|
|
199
|
+
self.context = context
|
|
200
|
+
|
|
201
|
+
if not isinstance(exc_type, str):
|
|
202
|
+
exception_type = exc_type.__name__
|
|
203
|
+
if exc_type.__module__ not in ('__builtin__', 'exceptions'):
|
|
204
|
+
exception_type = exc_type.__module__ + '.' + exception_type
|
|
205
|
+
else:
|
|
206
|
+
exception_type = exc_type
|
|
207
|
+
self.exception_type = exception_type
|
|
208
|
+
|
|
209
|
+
# we only add frames to the list that are not hidden. This follows
|
|
210
|
+
# the the magic variables as defined by paste.exceptions.collector
|
|
211
|
+
self.frames = []
|
|
212
|
+
while tb:
|
|
213
|
+
self.frames.append(Frame(exc_type, exc_value, tb, context))
|
|
214
|
+
tb = tb.tb_next
|
|
215
|
+
|
|
216
|
+
def filter_hidden_frames(self):
|
|
217
|
+
"""Remove the frames according to the paste spec."""
|
|
218
|
+
if not self.frames:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
new_frames = []
|
|
222
|
+
hidden = False
|
|
223
|
+
for frame in self.frames:
|
|
224
|
+
hide = frame.hide
|
|
225
|
+
if hide in ('before', 'before_and_this'):
|
|
226
|
+
new_frames = []
|
|
227
|
+
hidden = False
|
|
228
|
+
if hide == 'before_and_this':
|
|
229
|
+
continue
|
|
230
|
+
elif hide in ('reset', 'reset_and_this'):
|
|
231
|
+
hidden = False
|
|
232
|
+
if hide == 'reset_and_this':
|
|
233
|
+
continue
|
|
234
|
+
elif hide in ('after', 'after_and_this'):
|
|
235
|
+
hidden = True
|
|
236
|
+
if hide == 'after_and_this':
|
|
237
|
+
continue
|
|
238
|
+
elif hide or hidden:
|
|
239
|
+
continue
|
|
240
|
+
new_frames.append(frame)
|
|
241
|
+
|
|
242
|
+
# if we only have one frame and that frame is from the codeop
|
|
243
|
+
# module, remove it.
|
|
244
|
+
if len(new_frames) == 1 and self.frames[0].module == 'codeop':
|
|
245
|
+
del self.frames[:]
|
|
246
|
+
|
|
247
|
+
# if the last frame is missing something went terrible wrong :(
|
|
248
|
+
elif self.frames[-1] in new_frames:
|
|
249
|
+
self.frames[:] = new_frames
|
|
250
|
+
|
|
251
|
+
def is_syntax_error(self):
|
|
252
|
+
"""Is it a syntax error?"""
|
|
253
|
+
return isinstance(self.exc_value, SyntaxError)
|
|
254
|
+
is_syntax_error = property(is_syntax_error)
|
|
255
|
+
|
|
256
|
+
def exception(self):
|
|
257
|
+
"""String representation of the exception."""
|
|
258
|
+
buf = traceback.format_exception_only(self.exc_type, self.exc_value)
|
|
259
|
+
return native_(''.join(buf).strip(), 'utf-8', 'replace')
|
|
260
|
+
exception = property(exception)
|
|
261
|
+
|
|
262
|
+
def log(self, logfile=None):
|
|
263
|
+
"""Log the ASCII traceback into a file object."""
|
|
264
|
+
if logfile is None:
|
|
265
|
+
logfile = sys.stderr
|
|
266
|
+
tb = self.plaintext.rstrip() + '\n'
|
|
267
|
+
if PY2:
|
|
268
|
+
tb = tb.encode('utf-8')
|
|
269
|
+
logfile.write(tb)
|
|
270
|
+
|
|
271
|
+
def paste(self):
|
|
272
|
+
"""Create a paste and return the paste id."""
|
|
273
|
+
data = json.dumps({
|
|
274
|
+
'description': 'Backlash Internal Server Error',
|
|
275
|
+
'public': False,
|
|
276
|
+
'files': {
|
|
277
|
+
'traceback.txt': {
|
|
278
|
+
'content': self.plaintext
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}).encode('utf-8')
|
|
282
|
+
|
|
283
|
+
rv = urlopen('https://api.github.com/gists', data=data)
|
|
284
|
+
resp = json.loads(rv.read().decode('utf-8'))
|
|
285
|
+
rv.close()
|
|
286
|
+
return {
|
|
287
|
+
'url': resp['html_url'],
|
|
288
|
+
'id': resp['id']
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
def render_summary(self, include_title=True):
|
|
292
|
+
"""Render the traceback for the interactive console."""
|
|
293
|
+
title = ''
|
|
294
|
+
description = ''
|
|
295
|
+
frames = []
|
|
296
|
+
classes = ['traceback']
|
|
297
|
+
if not self.frames:
|
|
298
|
+
classes.append('noframe-traceback')
|
|
299
|
+
|
|
300
|
+
if include_title:
|
|
301
|
+
if self.is_syntax_error:
|
|
302
|
+
title = text_('Syntax Error')
|
|
303
|
+
else:
|
|
304
|
+
title = text_('Traceback <em>(most recent call last)</em>:')
|
|
305
|
+
|
|
306
|
+
for frame in self.frames:
|
|
307
|
+
frames.append(text_('<li%s>%s') % (
|
|
308
|
+
frame.info and text_(' title="%s"') % escape(frame.info) or text_(''),
|
|
309
|
+
frame.render()
|
|
310
|
+
))
|
|
311
|
+
|
|
312
|
+
if self.is_syntax_error:
|
|
313
|
+
description_wrapper = text_('<pre class=syntaxerror>%s</pre>')
|
|
314
|
+
else:
|
|
315
|
+
description_wrapper = text_('<blockquote>%s</blockquote>')
|
|
316
|
+
|
|
317
|
+
return SUMMARY_HTML % {
|
|
318
|
+
'classes': text_(' '.join(classes)),
|
|
319
|
+
'title': title and text_('<h3>%s</h3>' % title) or text_(''),
|
|
320
|
+
'frames': text_('\n'.join(frames)),
|
|
321
|
+
'description': description_wrapper % escape(self.exception)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
def render_full(self, evalex=False, secret=None):
|
|
325
|
+
"""Render the Full HTML page with the traceback info."""
|
|
326
|
+
exc = escape(self.exception)
|
|
327
|
+
return PAGE_HTML % {
|
|
328
|
+
'evalex': evalex and 'true' or 'false',
|
|
329
|
+
'console': 'false',
|
|
330
|
+
'title': exc,
|
|
331
|
+
'exception': exc,
|
|
332
|
+
'exception_type': escape(self.exception_type),
|
|
333
|
+
'summary': self.render_summary(include_title=False),
|
|
334
|
+
'plaintext': self.plaintext,
|
|
335
|
+
'plaintext_cs': re.sub('-{2,}', '-', self.plaintext),
|
|
336
|
+
'traceback_id': self.id,
|
|
337
|
+
'secret': secret
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
def generate_plaintext_traceback(self):
|
|
341
|
+
"""Like the plaintext attribute but returns a generator"""
|
|
342
|
+
yield text_('Traceback (most recent call last):')
|
|
343
|
+
for frame in self.frames:
|
|
344
|
+
yield text_(' File "%s", line %s, in %s' % (
|
|
345
|
+
frame.filename,
|
|
346
|
+
frame.lineno,
|
|
347
|
+
frame.function_name
|
|
348
|
+
))
|
|
349
|
+
yield text_(' ' + frame.current_line.strip())
|
|
350
|
+
yield text_(self.exception)
|
|
351
|
+
|
|
352
|
+
def plaintext(self):
|
|
353
|
+
return text_('\n'.join(self.generate_plaintext_traceback()))
|
|
354
|
+
plaintext = property(plaintext)
|
|
355
|
+
|
|
356
|
+
id = property(lambda x: id(x))
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class Frame(object):
|
|
360
|
+
"""A single frame in a traceback."""
|
|
361
|
+
|
|
362
|
+
def __init__(self, exc_type, exc_value, tb, context=None):
|
|
363
|
+
if inspect.isframe(tb):
|
|
364
|
+
self.lineno = tb.f_lineno
|
|
365
|
+
tb_frame = tb
|
|
366
|
+
else:
|
|
367
|
+
self.lineno = tb.tb_lineno
|
|
368
|
+
tb_frame = tb.tb_frame
|
|
369
|
+
|
|
370
|
+
self.function_name = tb_frame.f_code.co_name
|
|
371
|
+
self.locals = tb_frame.f_locals
|
|
372
|
+
self.globals = tb_frame.f_globals
|
|
373
|
+
self.context = context
|
|
374
|
+
|
|
375
|
+
fn = inspect.getsourcefile(tb) or inspect.getfile(tb)
|
|
376
|
+
if fn[-4:] in ('.pyo', '.pyc'):
|
|
377
|
+
fn = fn[:-1]
|
|
378
|
+
# if it's a file on the file system resolve the real filename.
|
|
379
|
+
if os.path.isfile(fn):
|
|
380
|
+
fn = os.path.realpath(fn)
|
|
381
|
+
self.filename = fn
|
|
382
|
+
self.module = self.globals.get('__name__')
|
|
383
|
+
self.loader = self.globals.get('__loader__')
|
|
384
|
+
self.code = tb_frame.f_code
|
|
385
|
+
|
|
386
|
+
# support for paste's traceback extensions
|
|
387
|
+
self.hide = self.locals.get('__traceback_hide__', False)
|
|
388
|
+
info = self.locals.get('__traceback_info__')
|
|
389
|
+
if info is not None:
|
|
390
|
+
try:
|
|
391
|
+
info = text_(info)
|
|
392
|
+
except UnicodeError:
|
|
393
|
+
info = str(info).decode('utf-8', 'replace')
|
|
394
|
+
self.info = info
|
|
395
|
+
|
|
396
|
+
def __getattr__(self, item):
|
|
397
|
+
# Make it look like a plain python frame/traceback object.
|
|
398
|
+
if item in ('tb_lineno', 'f_lineno'):
|
|
399
|
+
return self.lineno
|
|
400
|
+
elif item in ('f_locals', ):
|
|
401
|
+
return self.locals
|
|
402
|
+
elif item in ('tb_frame', ):
|
|
403
|
+
return self
|
|
404
|
+
elif item in ('f_globals', ):
|
|
405
|
+
return self.globals
|
|
406
|
+
elif item in ('f_code', ):
|
|
407
|
+
return self.code
|
|
408
|
+
|
|
409
|
+
raise AttributeError("'Frame' object has no attribute '%s'" % item)
|
|
410
|
+
|
|
411
|
+
def render(self):
|
|
412
|
+
"""Render a single frame in a traceback."""
|
|
413
|
+
return FRAME_HTML % {
|
|
414
|
+
'id': self.id,
|
|
415
|
+
'filename': escape(self.filename),
|
|
416
|
+
'lineno': self.lineno,
|
|
417
|
+
'function_name': escape(self.function_name),
|
|
418
|
+
'current_line': escape(self.current_line.strip())
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
def get_annotated_lines(self):
|
|
422
|
+
"""Helper function that returns lines with extra information."""
|
|
423
|
+
lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)]
|
|
424
|
+
|
|
425
|
+
# find function definition and mark lines
|
|
426
|
+
if hasattr(self.code, 'co_firstlineno'):
|
|
427
|
+
lineno = self.code.co_firstlineno - 1
|
|
428
|
+
while lineno > 0:
|
|
429
|
+
if _funcdef_re.match(lines[lineno].code):
|
|
430
|
+
break
|
|
431
|
+
lineno -= 1
|
|
432
|
+
try:
|
|
433
|
+
offset = len(inspect.getblock([x.code + '\n' for x
|
|
434
|
+
in lines[lineno:]]))
|
|
435
|
+
except TokenError:
|
|
436
|
+
offset = 0
|
|
437
|
+
for line in lines[lineno:lineno + offset]:
|
|
438
|
+
line.in_frame = True
|
|
439
|
+
|
|
440
|
+
# mark current line
|
|
441
|
+
try:
|
|
442
|
+
lines[self.lineno - 1].current = True
|
|
443
|
+
except IndexError:
|
|
444
|
+
pass
|
|
445
|
+
|
|
446
|
+
return lines
|
|
447
|
+
|
|
448
|
+
def render_source(self):
|
|
449
|
+
"""Render the sourcecode."""
|
|
450
|
+
return SOURCE_TABLE_HTML % text_('\n'.join(line.render() for line in
|
|
451
|
+
self.get_annotated_lines()))
|
|
452
|
+
|
|
453
|
+
def eval(self, code, mode='single'):
|
|
454
|
+
"""Evaluate code in the context of the frame."""
|
|
455
|
+
if isinstance(code, string_types):
|
|
456
|
+
if isinstance(code, text_type):
|
|
457
|
+
code = UTF8_COOKIE + code.encode('utf-8')
|
|
458
|
+
code = compile(code, '<interactive>', mode)
|
|
459
|
+
if mode != 'exec':
|
|
460
|
+
return eval(code, self.globals, self.locals)
|
|
461
|
+
exec_(code, self.globals, self.locals)
|
|
462
|
+
|
|
463
|
+
@property
|
|
464
|
+
def sourcelines(self):
|
|
465
|
+
"""The sourcecode of the file as list of unicode strings."""
|
|
466
|
+
# get sourcecode from loader or file
|
|
467
|
+
source = None
|
|
468
|
+
if self.loader is not None:
|
|
469
|
+
try:
|
|
470
|
+
if hasattr(self.loader, 'get_source'):
|
|
471
|
+
source = self.loader.get_source(self.module)
|
|
472
|
+
elif hasattr(self.loader, 'get_source_by_code'):
|
|
473
|
+
source = self.loader.get_source_by_code(self.code)
|
|
474
|
+
except Exception:
|
|
475
|
+
# we munch the exception so that we don't cause troubles
|
|
476
|
+
# if the loader is broken.
|
|
477
|
+
pass
|
|
478
|
+
|
|
479
|
+
if source is None:
|
|
480
|
+
try:
|
|
481
|
+
f = open(self.filename)
|
|
482
|
+
except IOError:
|
|
483
|
+
return []
|
|
484
|
+
try:
|
|
485
|
+
source = f.read()
|
|
486
|
+
finally:
|
|
487
|
+
f.close()
|
|
488
|
+
|
|
489
|
+
# already unicode? return right away
|
|
490
|
+
if isinstance(source, text_type):
|
|
491
|
+
return source.splitlines()
|
|
492
|
+
|
|
493
|
+
# yes. it should be ascii, but we don't want to reject too many
|
|
494
|
+
# characters in the debugger if something breaks
|
|
495
|
+
charset = 'utf-8'
|
|
496
|
+
if source.startswith(UTF8_COOKIE):
|
|
497
|
+
source = source[3:]
|
|
498
|
+
else:
|
|
499
|
+
for idx, match in enumerate(_line_re.finditer(source)):
|
|
500
|
+
match = _line_re.search(match.group())
|
|
501
|
+
if match is not None:
|
|
502
|
+
charset = match.group(1)
|
|
503
|
+
break
|
|
504
|
+
if idx > 1:
|
|
505
|
+
break
|
|
506
|
+
|
|
507
|
+
# on broken cookies we fall back to utf-8 too
|
|
508
|
+
try:
|
|
509
|
+
codecs.lookup(charset)
|
|
510
|
+
except LookupError:
|
|
511
|
+
charset = 'utf-8'
|
|
512
|
+
|
|
513
|
+
return source.decode(charset, 'replace').splitlines()
|
|
514
|
+
|
|
515
|
+
@property
|
|
516
|
+
def current_line(self):
|
|
517
|
+
try:
|
|
518
|
+
return self.sourcelines[self.lineno - 1]
|
|
519
|
+
except IndexError:
|
|
520
|
+
return text_('')
|
|
521
|
+
|
|
522
|
+
@property
|
|
523
|
+
def console(self):
|
|
524
|
+
return Console(self.globals, self.locals, self.context)
|
|
525
|
+
|
|
526
|
+
id = property(lambda x: id(x))
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# HERE JUST FOR BACKWARD COMPATIBILITY
|
|
2
|
+
from backlash.tracing.errors import TraceErrorsMiddleware
|
|
3
|
+
from backlash.tracing.reporters.mail import EmailReporter
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
warnings.warn(
|
|
7
|
+
'backlash.trace_errors is deprecated. Please use backlash.tracing.errors instead',
|
|
8
|
+
DeprecationWarning)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .middleware import TraceErrorsMiddleware
|