-
-
Notifications
You must be signed in to change notification settings - Fork 148
Expand file tree
/
Copy pathsqlalchemy.py
More file actions
167 lines (122 loc) · 4.46 KB
/
Copy pathsqlalchemy.py
File metadata and controls
167 lines (122 loc) · 4.46 KB
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
try:
from flask_sqlalchemy import get_debug_queries, SQLAlchemy
except ImportError:
sqlalchemy_available = False
get_debug_queries = SQLAlchemy = None
else:
sqlalchemy_available = True
from flask import request, current_app, abort, json_available, g
from flask_debugtoolbar import module
from flask_debugtoolbar.panels import DebugPanel
from flask_debugtoolbar.utils import format_fname, format_sql
import itsdangerous
_engine = None
@module.record_once
def store_engine(state):
global _engine
_engine = state.options.get('sqlalchemy_engine')
def get_engine():
if _engine is not None:
return _engine
elif sqlalchemy_available:
return SQLAlchemy().get_engine(current_app)
_ = lambda x: x
def query_signer():
return itsdangerous.URLSafeSerializer(current_app.config['SECRET_KEY'],
salt='fdt-sql-query')
def is_select(statement):
prefix = b'select' if isinstance(statement, bytes) else 'select'
return statement.lower().strip().startswith(prefix)
def dump_query(statement, params):
if not params or not is_select(statement):
return None
try:
return query_signer().dumps([statement, params])
except TypeError:
return None
def load_query(data):
try:
statement, params = query_signer().loads(request.args['query'])
except (itsdangerous.BadSignature, TypeError):
abort(406)
# Make sure it is a select statement
if not is_select(statement):
abort(406)
return statement, params
def extension_used():
return 'sqlalchemy' in current_app.extensions
def recording_enabled():
return (current_app.debug
or current_app.config.get('SQLALCHEMY_RECORD_QUERIES'))
def is_available():
return (json_available and sqlalchemy_available
and (extension_used() or (_engine is not None)) and recording_enabled())
def get_queries():
if get_debug_queries:
return get_debug_queries()
else:
return []
class SQLAlchemyDebugPanel(DebugPanel):
"""
Panel that displays the time a response took in milliseconds.
"""
name = 'SQLAlchemy'
@property
def has_content(self):
return bool(get_queries()) or not is_available()
def process_request(self, request):
pass
def process_response(self, request, response):
pass
def nav_title(self):
return _('SQLAlchemy')
def nav_subtitle(self):
count = len(get_queries())
if not count and not is_available():
return 'Unavailable'
return '%d %s' % (count, 'query' if count == 1 else 'queries')
def title(self):
return _('SQLAlchemy queries')
def url(self):
return ''
def content(self):
queries = get_queries()
if not queries and not is_available():
return self.render('panels/sqlalchemy_error.html', {
'json_available': json_available,
'sqlalchemy_available': sqlalchemy_available,
'sqlalchemy_engine_available': _engine is not None,
'extension_used': extension_used(),
'recording_enabled': recording_enabled(),
})
data = []
for query in queries:
data.append({
'duration': query.duration,
'sql': format_sql(query.statement, query.parameters),
'signed_query': dump_query(query.statement, query.parameters),
'context_long': query.context,
'context': format_fname(query.context)
})
return self.render('panels/sqlalchemy.html', {'queries': data})
# Panel views
@module.route('/sqlalchemy/sql_select', methods=['GET', 'POST'])
@module.route('/sqlalchemy/sql_explain', methods=['GET', 'POST'],
defaults=dict(explain=True))
def sql_select(explain=False):
statement, params = load_query(request.args['query'])
engine = get_engine()
if engine is None:
return 'No SQLAlchemy engine has been configured.'
if explain:
if engine.driver == 'pysqlite':
statement = 'EXPLAIN QUERY PLAN\n%s' % statement
else:
statement = 'EXPLAIN\n%s' % statement
result = engine.execute(statement, params)
return g.debug_toolbar.render('panels/sqlalchemy_select.html', {
'result': result.fetchall(),
'headers': result.keys(),
'sql': format_sql(statement, params),
'duration': float(request.args['duration']),
})