-
-
Notifications
You must be signed in to change notification settings - Fork 147
/
Copy path__init__.py
273 lines (210 loc) · 9.56 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import os
import urllib.parse
import warnings
import flask
from packaging import version as version_builder
from flask import Blueprint, current_app, request, g, send_from_directory, url_for
if version_builder.parse(flask.__version__) >= version_builder.parse("2.2.0"):
from flask.globals import request_ctx
else:
from flask.globals import _request_ctx_stack
from jinja2 import __version__ as __jinja_version__
from jinja2 import Environment, PackageLoader
from flask_debugtoolbar.compat import iteritems
from flask_debugtoolbar.toolbar import DebugToolbar
from flask_debugtoolbar.utils import decode_text, gzip_compress, gzip_decompress
try:
# Python 3.8+
from importlib.metadata import version
__version__ = version("Flask-DebugToolbar")
except ImportError:
import pkg_resources
__version__ = pkg_resources.get_distribution("Flask-DebugToolbar").version
module = Blueprint('debugtoolbar', __name__)
def replace_insensitive(string, target, replacement):
"""Similar to string.replace() but is case insensitive
Code borrowed from:
http://forums.devshed.com/python-programming-11/case-insensitive-string-replace-490921.html
"""
no_case = string.lower()
index = no_case.rfind(target.lower())
if index >= 0:
return string[:index] + replacement + string[index + len(target):]
else: # no results so return the original string
return string
def _printable(value):
try:
return decode_text(repr(value))
except Exception as e:
return '<repr(%s) raised %s: %s>' % (
object.__repr__(value), type(e).__name__, e)
class DebugToolbarExtension(object):
_static_dir = os.path.realpath(
os.path.join(os.path.dirname(__file__), 'static'))
_toolbar_codes = [200, 201, 400, 401, 403, 404, 405, 500, 501, 502, 503, 504]
_redirect_codes = [301, 302, 303, 304]
def __init__(self, app=None):
self.app = app
self.debug_toolbars = {}
jinja_extensions = ['jinja2.ext.i18n']
if __jinja_version__[0] == '2':
jinja_extensions.append('jinja2.ext.with_')
# Configure jinja for the internal templates and add url rules
# for static data
self.jinja_env = Environment(
autoescape=True,
extensions=jinja_extensions,
loader=PackageLoader(__name__, 'templates'))
self.jinja_env.filters['urlencode'] = urllib.parse.quote_plus
self.jinja_env.filters['printable'] = _printable
self.jinja_env.globals['url_for'] = url_for
if app is not None:
self.init_app(app)
def init_app(self, app):
for k, v in iteritems(self._default_config(app)):
app.config.setdefault(k, v)
if not app.config['DEBUG_TB_ENABLED']:
return
if not app.config.get('SECRET_KEY'):
raise RuntimeError(
"The Flask-DebugToolbar requires the 'SECRET_KEY' config "
"var to be set")
DebugToolbar.load_panels(app)
app.before_request(self.process_request)
app.after_request(self.process_response)
app.teardown_request(self.teardown_request)
# Monkey-patch the Flask.dispatch_request method
app.dispatch_request = self.dispatch_request
app.add_url_rule('/_debug_toolbar/static/<path:filename>',
'_debug_toolbar.static', self.send_static_file)
app.register_blueprint(module, url_prefix='/_debug_toolbar/views')
def _default_config(self, app):
return {
'DEBUG_TB_ENABLED': app.debug,
'DEBUG_TB_HOSTS': (),
'DEBUG_TB_INTERCEPT_REDIRECTS': True,
'DEBUG_TB_PANELS': (
'flask_debugtoolbar.panels.versions.VersionDebugPanel',
'flask_debugtoolbar.panels.timer.TimerDebugPanel',
'flask_debugtoolbar.panels.headers.HeaderDebugPanel',
'flask_debugtoolbar.panels.request_vars.RequestVarsDebugPanel',
'flask_debugtoolbar.panels.config_vars.ConfigVarsDebugPanel',
'flask_debugtoolbar.panels.template.TemplateDebugPanel',
'flask_debugtoolbar.panels.sqlalchemy.SQLAlchemyDebugPanel',
'flask_debugtoolbar.panels.logger.LoggingPanel',
'flask_debugtoolbar.panels.route_list.RouteListDebugPanel',
'flask_debugtoolbar.panels.profiler.ProfilerDebugPanel',
'flask_debugtoolbar.panels.g.GDebugPanel',
),
'SQLALCHEMY_RECORD_QUERIES': app.debug,
}
def dispatch_request(self):
"""Modified version of Flask.dispatch_request to call process_view."""
if version_builder.parse(flask.__version__) >= version_builder.parse("2.2.0"):
req = request_ctx.request
else:
req = _request_ctx_stack.top.request
app = current_app
if req.routing_exception is not None:
app.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return app.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
view_func = app.view_functions[rule.endpoint]
view_func = self.process_view(app, view_func, req.view_args)
return view_func(**req.view_args)
def _show_toolbar(self):
"""Return a boolean to indicate if we need to show the toolbar."""
if request.blueprint == 'debugtoolbar':
return False
hosts = current_app.config['DEBUG_TB_HOSTS']
if hosts and request.remote_addr not in hosts:
return False
return True
def send_static_file(self, filename):
"""Send a static file from the flask-debugtoolbar static directory."""
return send_from_directory(self._static_dir, filename)
def process_request(self):
g.debug_toolbar = self
if not self._show_toolbar():
return
real_request = request._get_current_object()
self.debug_toolbars[real_request] = (
DebugToolbar(real_request, self.jinja_env))
for panel in self.debug_toolbars[real_request].panels:
panel.process_request(real_request)
def process_view(self, app, view_func, view_kwargs):
""" This method is called just before the flask view is called.
This is done by the dispatch_request method.
"""
real_request = request._get_current_object()
try:
toolbar = self.debug_toolbars[real_request]
except KeyError:
return view_func
for panel in toolbar.panels:
new_view = panel.process_view(real_request, view_func, view_kwargs)
if new_view:
view_func = new_view
return view_func
def process_response(self, response):
real_request = request._get_current_object()
if real_request not in self.debug_toolbars:
return response
# Intercept http redirect codes and display an html page with a
# link to the target.
if current_app.config['DEBUG_TB_INTERCEPT_REDIRECTS']:
if response.status_code in self._redirect_codes:
redirect_to = response.location
redirect_code = response.status_code
if redirect_to:
content = self.render('redirect.html', {
'redirect_to': redirect_to,
'redirect_code': redirect_code
})
response.content_length = len(content)
response.location = None
response.response = [content]
response.status_code = 200
# If the http response code is an allowed code then we process to add the
# toolbar to the returned html response.
if not (response.status_code in self._toolbar_codes and
response.is_sequence and
response.headers['content-type'].startswith('text/html')):
return response
if 'gzip' in response.headers.get('Content-Encoding'):
response_html = gzip_decompress(response.data).decode()
else:
response_html = response.get_data(as_text=True)
no_case = response_html.lower()
body_end = no_case.rfind('</body>')
if body_end >= 0:
before = response_html[:body_end]
after = response_html[body_end:]
elif no_case.startswith('<!doctype html>'):
before = response_html
after = ''
else:
warnings.warn('Could not insert debug toolbar.'
' </body> tag not found in response.')
return response
toolbar = self.debug_toolbars[real_request]
for panel in toolbar.panels:
panel.process_response(real_request, response)
toolbar_html = toolbar.render_toolbar()
content = ''.join((before, toolbar_html, after))
content = content.encode('utf-8')
if 'gzip' in response.headers.get('Content-Encoding'):
content = gzip_compress(content)
response.response = [content]
response.content_length = len(content)
return response
def teardown_request(self, exc):
self.debug_toolbars.pop(request._get_current_object(), None)
def render(self, template_name, context):
template = self.jinja_env.get_template(template_name)
return template.render(**context)