debugger
This commit is contained in:
@@ -1 +1 @@
|
|||||||
Version 1.99.4 (2012-02-19 22:24:06) stable
|
Version 1.99.4 (2012-02-19 22:30:50) stable
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import cStringIO
|
import cStringIO
|
||||||
import gluon.contrib.shell
|
import gluon.contrib.shell
|
||||||
|
import gluon.dal
|
||||||
|
import gluon.html
|
||||||
|
import gluon.validators
|
||||||
import code, thread
|
import code, thread
|
||||||
from gluon.debug import communicate
|
from gluon.debug import communicate, web_debugger, qdb_debugger
|
||||||
|
import pydoc
|
||||||
|
|
||||||
|
|
||||||
if DEMO_MODE or MULTI_USER_MODE:
|
if DEMO_MODE or MULTI_USER_MODE:
|
||||||
@@ -32,4 +37,159 @@ def reset():
|
|||||||
return 'done'
|
return 'done'
|
||||||
|
|
||||||
|
|
||||||
|
# new implementation using qdb
|
||||||
|
|
||||||
|
def interact():
|
||||||
|
app = request.args(0) or 'admin'
|
||||||
|
reset()
|
||||||
|
|
||||||
|
# process all pending messages in the frontend
|
||||||
|
web_debugger.run()
|
||||||
|
|
||||||
|
# if debugging, filename and lineno should have valid values
|
||||||
|
filename = web_debugger.filename
|
||||||
|
lineno = web_debugger.lineno
|
||||||
|
if filename:
|
||||||
|
lines = dict([(i+1, l) for (i, l) in enumerate(
|
||||||
|
[l.strip("\n").strip("\r") for l
|
||||||
|
in open(filename).readlines()])])
|
||||||
|
filename = os.path.basename(filename)
|
||||||
|
else:
|
||||||
|
lines = {}
|
||||||
|
|
||||||
|
if filename:
|
||||||
|
env = web_debugger.do_environment()
|
||||||
|
f_locals = env['locals']
|
||||||
|
f_globals = {}
|
||||||
|
for name, value in env['globals'].items():
|
||||||
|
if name not in gluon.html.__all__ and \
|
||||||
|
name not in gluon.validators.__all__ and \
|
||||||
|
name not in gluon.dal.__all__:
|
||||||
|
f_globals[name] = pydoc.text.repr(value)
|
||||||
|
else:
|
||||||
|
f_locals = {}
|
||||||
|
f_globals = {}
|
||||||
|
response.headers['refresh'] = "3"
|
||||||
|
|
||||||
|
if web_debugger.exception_info:
|
||||||
|
response.flash = T('"User Exception" debug mode. '
|
||||||
|
'An error ticket could be issued!')
|
||||||
|
|
||||||
|
return dict(app=app, data="",
|
||||||
|
filename=web_debugger.filename, lines=lines, lineno=lineno,
|
||||||
|
f_globals=f_globals, f_locals=f_locals,
|
||||||
|
exception=web_debugger.exception_info)
|
||||||
|
|
||||||
|
def step():
|
||||||
|
web_debugger.do_step()
|
||||||
|
redirect(URL("interact"))
|
||||||
|
|
||||||
|
def next():
|
||||||
|
web_debugger.do_next()
|
||||||
|
redirect(URL("interact"))
|
||||||
|
|
||||||
|
def cont():
|
||||||
|
web_debugger.do_continue()
|
||||||
|
redirect(URL("interact"))
|
||||||
|
|
||||||
|
def ret():
|
||||||
|
web_debugger.do_return()
|
||||||
|
redirect(URL("interact"))
|
||||||
|
|
||||||
|
def stop():
|
||||||
|
web_debugger.do_quit()
|
||||||
|
redirect(URL("interact"))
|
||||||
|
|
||||||
|
def execute():
|
||||||
|
app = request.args[0]
|
||||||
|
command = request.vars.statement
|
||||||
|
session['debug_commands:'+app].append(command)
|
||||||
|
try:
|
||||||
|
output = web_debugger.do_exec(command)
|
||||||
|
if output is None:
|
||||||
|
output = ""
|
||||||
|
except Exception, e:
|
||||||
|
output = T("Exception %s") % str(e)
|
||||||
|
k = len(session['debug_commands:'+app]) - 1
|
||||||
|
return '[%i] %s%s\n' % (k + 1, command, output)
|
||||||
|
|
||||||
|
|
||||||
|
def breakpoints():
|
||||||
|
"Add or remove breakpoints"
|
||||||
|
|
||||||
|
# Get all .py files
|
||||||
|
files = listdir(apath('', r=request), '.*\.py$')
|
||||||
|
files = [filename for filename in files
|
||||||
|
if filename and 'languages' not in filename
|
||||||
|
and not filename.startswith("admin")
|
||||||
|
and not filename.startswith("examples")]
|
||||||
|
|
||||||
|
form = SQLFORM.factory(
|
||||||
|
Field('filename', requires=IS_IN_SET(files), label=T("Filename")),
|
||||||
|
Field('lineno', 'integer', label=T("Line number"),
|
||||||
|
requires=IS_NOT_EMPTY()),
|
||||||
|
Field('temporary', 'boolean', label=T("Temporary"),
|
||||||
|
comment=T("deleted after first hit")),
|
||||||
|
Field('condition', 'string', label=T("Condition"),
|
||||||
|
comment=T("honored only if the expression evaluates to true")),
|
||||||
|
)
|
||||||
|
|
||||||
|
if form.accepts(request.vars, session):
|
||||||
|
filename = os.path.join(request.env['applications_parent'],
|
||||||
|
'applications', form.vars.filename)
|
||||||
|
err = qdb_debugger.do_set_breakpoint(filename,
|
||||||
|
form.vars.lineno,
|
||||||
|
form.vars.temporary,
|
||||||
|
form.vars.condition)
|
||||||
|
response.flash = T("Set Breakpoint on %s at line %s: %s") % (
|
||||||
|
filename, form.vars.lineno, err or T('successful'))
|
||||||
|
|
||||||
|
for item in request.vars:
|
||||||
|
if item[:7] == 'delete_':
|
||||||
|
qdb_debugger.do_clear(item[7:])
|
||||||
|
|
||||||
|
breakpoints = [{'number': bp[0], 'filename': os.path.basename(bp[1]),
|
||||||
|
'path': bp[1], 'lineno': bp[2],
|
||||||
|
'temporary': bp[3], 'enabled': bp[4], 'hits': bp[5],
|
||||||
|
'condition': bp[6]}
|
||||||
|
for bp in qdb_debugger.do_list_breakpoint()]
|
||||||
|
|
||||||
|
return dict(breakpoints=breakpoints, form=form)
|
||||||
|
|
||||||
|
|
||||||
|
def toggle_breakpoint():
|
||||||
|
"Set or clear a breakpoint"
|
||||||
|
|
||||||
|
lineno = None
|
||||||
|
ok = None
|
||||||
|
try:
|
||||||
|
filename = os.path.join(request.env['applications_parent'],
|
||||||
|
'applications', request.vars.filename)
|
||||||
|
start = 0
|
||||||
|
sel_start = int(request.vars.sel_start)
|
||||||
|
for lineno, line in enumerate(request.vars.data.split("\n")):
|
||||||
|
if sel_start <= start:
|
||||||
|
break
|
||||||
|
start += len(line) + 1
|
||||||
|
else:
|
||||||
|
lineno = None
|
||||||
|
if lineno is not None:
|
||||||
|
for bp in qdb_debugger.do_list_breakpoint():
|
||||||
|
no, bp_filename, bp_lineno, temporary, enabled, hits, cond = bp
|
||||||
|
if filename == bp_filename and lineno == bp_lineno:
|
||||||
|
err = qdb_debugger.do_clear_breakpoint(filename, lineno)
|
||||||
|
response.flash = T("Removed Breakpoint on %s at line %s", (
|
||||||
|
filename, lineno))
|
||||||
|
ok = False
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
err = qdb_debugger.do_set_breakpoint(filename, lineno)
|
||||||
|
response.flash = T("Set Breakpoint on %s at line %s: %s") % (
|
||||||
|
filename, lineno, err or T('successful'))
|
||||||
|
ok = True
|
||||||
|
else:
|
||||||
|
response.flash = T("Unable to determine the line number!")
|
||||||
|
except Exception, e:
|
||||||
|
session.flash = str(e)
|
||||||
|
return response.json({'ok': ok, 'lineno': lineno})
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,9 @@ if session.authorized:
|
|||||||
else:
|
else:
|
||||||
session.last_time = t0
|
session.last_time = t0
|
||||||
|
|
||||||
if not session.authorized and not \
|
if request.controller == "webservices":
|
||||||
|
pass
|
||||||
|
elif not session.authorized and not \
|
||||||
(request.controller == 'default' and \
|
(request.controller == 'default' and \
|
||||||
request.function in ('index','user')):
|
request.function in ('index','user')):
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ if not session.authorized:
|
|||||||
else:
|
else:
|
||||||
response.menu.append((T('Logout'), False,
|
response.menu.append((T('Logout'), False,
|
||||||
URL(_a,'default',f='logout')))
|
URL(_a,'default',f='logout')))
|
||||||
|
response.menu.append((T('Debug'), False,
|
||||||
|
URL(_a, 'debug','interact')))
|
||||||
|
|
||||||
if os.path.exists('applications/examples'):
|
if os.path.exists('applications/examples'):
|
||||||
response.menu.append((T('Help'), False, URL('examples','default','index')))
|
response.menu.append((T('Help'), False, URL('examples','default','index')))
|
||||||
|
|||||||
@@ -930,6 +930,19 @@ ul#snapshot > li {
|
|||||||
background-image: url(../images/dim_bullet.gif);
|
background-image: url(../images/dim_bullet.gif);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Debug */
|
||||||
|
|
||||||
|
.debug h3.not_paused {
|
||||||
|
background-image: url(../images/red_bullet.gif);
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug h3.exception {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
background: url(../images/ticket_section.png) no-repeat;
|
||||||
|
padding: 5px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Footer */
|
/* Footer */
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ function doClickSave() {
|
|||||||
prepareDataForSave('data', data),
|
prepareDataForSave('data', data),
|
||||||
prepareDataForSave('file_hash', jQuery("input[name='file_hash']").val()),
|
prepareDataForSave('file_hash', jQuery("input[name='file_hash']").val()),
|
||||||
prepareDataForSave('saved_on', jQuery("input[name='saved_on']").val()),
|
prepareDataForSave('saved_on', jQuery("input[name='saved_on']").val()),
|
||||||
|
prepareDataForSave('saved_on', jQuery("input[name='saved_on']").val()),
|
||||||
prepareDataForSave('from_ajax','true')));
|
prepareDataForSave('from_ajax','true')));
|
||||||
// console.info(area.textarea.value);
|
// console.info(area.textarea.value);
|
||||||
jQuery("input[name='saved_on']").attr('style','background-color:yellow');
|
jQuery("input[name='saved_on']").attr('style','background-color:yellow');
|
||||||
@@ -94,6 +95,53 @@ function doClickSave() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function doToggleBreakpoint(filename, url) {
|
||||||
|
try {
|
||||||
|
var data = eamy.instances[0].getText();
|
||||||
|
} catch(e) {
|
||||||
|
var data = area.textarea.value;
|
||||||
|
var sel = editAreaLoader.getSelectionRange('body');
|
||||||
|
}
|
||||||
|
var dataForPost = prepareMultiPartPOST(new Array(
|
||||||
|
prepareDataForSave('filename', filename),
|
||||||
|
prepareDataForSave('sel_start', sel["start"]),
|
||||||
|
prepareDataForSave('sel_end', sel["end"]),
|
||||||
|
prepareDataForSave('data', data)));
|
||||||
|
jQuery.ajax({
|
||||||
|
type: "POST",
|
||||||
|
contentType: 'multipart/form-data;boundary="' + dataForPost[1] + '"',
|
||||||
|
url: url,
|
||||||
|
dataType: "json",
|
||||||
|
data: dataForPost[0],
|
||||||
|
timeout: 5000,
|
||||||
|
beforeSend: function(xhr) {
|
||||||
|
xhr.setRequestHeader('web2py-component-location',document.location);
|
||||||
|
xhr.setRequestHeader('web2py-component-element','doSetBreakpoint');},
|
||||||
|
success: function(json,text,xhr){
|
||||||
|
|
||||||
|
// show flash message (if any)
|
||||||
|
var flash=xhr.getResponseHeader('web2py-component-flash');
|
||||||
|
if (flash) jQuery('.flash').html(flash).slideDown();
|
||||||
|
else jQuery('.flash').hide();
|
||||||
|
try {
|
||||||
|
if (json.error) {
|
||||||
|
window.location.href=json.redirect;
|
||||||
|
} else {
|
||||||
|
// mark the breakpoint if ok=True, remove mark if ok=False
|
||||||
|
// do nothing if ok = null
|
||||||
|
// alert(json.ok + json.lineno);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
on_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
error: function(json) { on_error(); }
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function keepalive(url) {
|
function keepalive(url) {
|
||||||
jQuery.ajax({
|
jQuery.ajax({
|
||||||
type: "GET",
|
type: "GET",
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ jQuery(document).ready(function(){
|
|||||||
{{pass}}
|
{{pass}}
|
||||||
|
|
||||||
<p class="right controls">
|
<p class="right controls">
|
||||||
|
{{if filetype=='python':}}
|
||||||
|
{{=A(SPAN(T('toggle breakpoint')),
|
||||||
|
_value="breakpoint", _name="breakpoint",
|
||||||
|
_onclick="return doToggleBreakpoint('%s','%s://%s%s');" % (filename,
|
||||||
|
request.env['wsgi_url_scheme'], request.env['http_host'],
|
||||||
|
URL(c='debug', f='toggle_breakpoint')),
|
||||||
|
_class="button special")}}
|
||||||
|
{{pass}}
|
||||||
{{=button(URL('design',args=request.args[0]), T('back'))}}
|
{{=button(URL('design',args=request.args[0]), T('back'))}}
|
||||||
{{if edit_controller:}}
|
{{if edit_controller:}}
|
||||||
{{=button(edit_controller, T('edit controller'))}}
|
{{=button(edit_controller, T('edit controller'))}}
|
||||||
|
|||||||
+1
-1
@@ -1970,7 +1970,7 @@ class PostgreSQLAdapter(BaseAdapter):
|
|||||||
if self.drivers.get('psycopg2'):
|
if self.drivers.get('psycopg2'):
|
||||||
self.driver = self.drivers['psycopg2']
|
self.driver = self.drivers['psycopg2']
|
||||||
elif self.drivers.get('pg8000'):
|
elif self.drivers.get('pg8000'):
|
||||||
self.driver = drivers['pg8000']
|
self.driver = drivers.get('pg8000')
|
||||||
elif library == "postgres:psycopg2":
|
elif library == "postgres:psycopg2":
|
||||||
self.driver = self.drivers.get('psycopg2')
|
self.driver = self.drivers.get('psycopg2')
|
||||||
elif library == "postgres:pg8000":
|
elif library == "postgres:pg8000":
|
||||||
|
|||||||
+100
@@ -10,6 +10,7 @@ License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import pdb
|
import pdb
|
||||||
import Queue
|
import Queue
|
||||||
import sys
|
import sys
|
||||||
@@ -82,7 +83,106 @@ def communicate(command=None):
|
|||||||
return ''.join(result)
|
return ''.join(result)
|
||||||
|
|
||||||
|
|
||||||
|
# New debugger implementation using qdb and a web UI
|
||||||
|
|
||||||
|
import gluon.contrib.qdb as qdb
|
||||||
|
from gluon import portalocker
|
||||||
|
|
||||||
|
|
||||||
|
def lock(name=''):
|
||||||
|
from gluon.globals import current
|
||||||
|
locker = open(os.path.join(current.request.folder, 'debug_%s.lock' % name),
|
||||||
|
'a')
|
||||||
|
portalocker.lock(locker, portalocker.LOCK_EX)
|
||||||
|
return locker
|
||||||
|
|
||||||
|
|
||||||
|
def unlock(locker):
|
||||||
|
portalocker.unlock(locker)
|
||||||
|
locker.close()
|
||||||
|
|
||||||
|
|
||||||
|
def check_interaction(fn):
|
||||||
|
"Decorator to clean and prevent interaction when not available"
|
||||||
|
def check_fn(self, *args, **kwargs):
|
||||||
|
locker = lock('interact')
|
||||||
|
try:
|
||||||
|
if self.filename:
|
||||||
|
self.clear_interaction()
|
||||||
|
fn(self, *args, **kwargs)
|
||||||
|
finally:
|
||||||
|
unlock(locker)
|
||||||
|
return check_fn
|
||||||
|
|
||||||
|
|
||||||
|
class WebDebugger(qdb.Frontend):
|
||||||
|
"Qdb web2py interface"
|
||||||
|
|
||||||
|
def __init__(self, pipe, completekey='tab', stdin=None, stdout=None, skip=None):
|
||||||
|
qdb.Frontend.__init__(self, pipe)
|
||||||
|
self.clear_interaction()
|
||||||
|
|
||||||
|
def clear_interaction(self):
|
||||||
|
self.filename = None
|
||||||
|
self.lineno = None
|
||||||
|
self.exception_info = None
|
||||||
|
|
||||||
|
# redefine Frontend methods:
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
locker = lock('run')
|
||||||
|
try:
|
||||||
|
while self.pipe.poll():
|
||||||
|
qdb.Frontend.run(self)
|
||||||
|
finally:
|
||||||
|
unlock(locker)
|
||||||
|
|
||||||
|
def interaction(self, filename, lineno, line):
|
||||||
|
# store current status
|
||||||
|
locker = lock('interact')
|
||||||
|
try:
|
||||||
|
self.filename = filename
|
||||||
|
self.lineno = lineno
|
||||||
|
finally:
|
||||||
|
unlock(locker)
|
||||||
|
|
||||||
|
def exception(self, title, extype, exvalue, trace, request):
|
||||||
|
self.exception_info = {'title': title,
|
||||||
|
'extype': extype, 'exvalue': exvalue,
|
||||||
|
'trace': trace, 'request': request}
|
||||||
|
|
||||||
|
@check_interaction
|
||||||
|
def do_continue(self):
|
||||||
|
qdb.Frontend.do_continue(self)
|
||||||
|
|
||||||
|
@check_interaction
|
||||||
|
def do_step(self):
|
||||||
|
qdb.Frontend.do_step(self)
|
||||||
|
|
||||||
|
@check_interaction
|
||||||
|
def do_return(self):
|
||||||
|
qdb.Frontend.do_return(self)
|
||||||
|
|
||||||
|
@check_interaction
|
||||||
|
def do_next(self):
|
||||||
|
qdb.Frontend.do_next(self)
|
||||||
|
|
||||||
|
@check_interaction
|
||||||
|
def do_quit(self):
|
||||||
|
qdb.Frontend.do_quit(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# create the connection between threads:
|
||||||
|
|
||||||
|
parent_queue, child_queue = Queue.Queue(), Queue.Queue()
|
||||||
|
front_conn = qdb.QueuePipe("parent", parent_queue, child_queue)
|
||||||
|
child_conn = qdb.QueuePipe("child", child_queue, parent_queue)
|
||||||
|
|
||||||
|
web_debugger = WebDebugger(front_conn) # frontend
|
||||||
|
qdb_debugger = qdb.Qdb(pipe=child_conn, redirect_stdio=False) # backend
|
||||||
|
dbg = qdb_debugger
|
||||||
|
|
||||||
|
import gluon.main
|
||||||
|
gluon.main.global_settings.debugging = True
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ def highlight(
|
|||||||
counter=1,
|
counter=1,
|
||||||
styles=None,
|
styles=None,
|
||||||
highlight_line=None,
|
highlight_line=None,
|
||||||
|
context_lines=None,
|
||||||
attributes=None,
|
attributes=None,
|
||||||
):
|
):
|
||||||
styles = styles or {}
|
styles = styles or {}
|
||||||
@@ -311,6 +312,14 @@ def highlight(
|
|||||||
lines[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, lines[lineno])
|
lines[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, lines[lineno])
|
||||||
linenumbers[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, linenumbers[lineno])
|
linenumbers[lineno] = '<div style="%s">%s</div>' % (linehighlight_style, linenumbers[lineno])
|
||||||
|
|
||||||
|
if context_lines:
|
||||||
|
if lineno + context_lines < len(lines):
|
||||||
|
del lines[lineno + context_lines:]
|
||||||
|
del linenumbers[lineno + context_lines:]
|
||||||
|
if lineno -context_lines > 0:
|
||||||
|
del lines[0:lineno - context_lines]
|
||||||
|
del linenumbers[0:lineno - context_lines]
|
||||||
|
|
||||||
code = '<br/>'.join(lines)
|
code = '<br/>'.join(lines)
|
||||||
numbers = '<br/>'.join(linenumbers)
|
numbers = '<br/>'.join(linenumbers)
|
||||||
|
|
||||||
|
|||||||
@@ -1413,6 +1413,7 @@ class CODE(DIV):
|
|||||||
link = self['link']
|
link = self['link']
|
||||||
counter = self.attributes.get('counter', 1)
|
counter = self.attributes.get('counter', 1)
|
||||||
highlight_line = self.attributes.get('highlight_line', None)
|
highlight_line = self.attributes.get('highlight_line', None)
|
||||||
|
context_lines = self.attributes.get('context_lines', None)
|
||||||
styles = self['styles'] or {}
|
styles = self['styles'] or {}
|
||||||
return highlight(
|
return highlight(
|
||||||
join(self.components),
|
join(self.components),
|
||||||
@@ -1422,6 +1423,7 @@ class CODE(DIV):
|
|||||||
styles=styles,
|
styles=styles,
|
||||||
attributes=self.attributes,
|
attributes=self.attributes,
|
||||||
highlight_line=highlight_line,
|
highlight_line=highlight_line,
|
||||||
|
context_lines=context_lines,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+5
-3
@@ -490,6 +490,11 @@ def wsgibase(environ, responder):
|
|||||||
# run controller
|
# run controller
|
||||||
# ##################################################
|
# ##################################################
|
||||||
|
|
||||||
|
if global_settings.debugging and request.application != "admin":
|
||||||
|
import gluon.debug
|
||||||
|
# activate the debugger and wait to reach application code
|
||||||
|
gluon.debug.dbg.do_debug(mainpyfile=request.folder)
|
||||||
|
|
||||||
serve_controller(request, response, session)
|
serve_controller(request, response, session)
|
||||||
|
|
||||||
except HTTP, http_response:
|
except HTTP, http_response:
|
||||||
@@ -591,9 +596,6 @@ def wsgibase(environ, responder):
|
|||||||
if response and hasattr(response, 'session_file') \
|
if response and hasattr(response, 'session_file') \
|
||||||
and response.session_file:
|
and response.session_file:
|
||||||
response.session_file.close()
|
response.session_file.close()
|
||||||
# if global_settings.debugging:
|
|
||||||
# import gluon.debug
|
|
||||||
# gluon.debug.stop_trace()
|
|
||||||
|
|
||||||
session._unlock(response)
|
session._unlock(response)
|
||||||
http_response, new_environ = rewrite.try_rewrite_on_error(
|
http_response, new_environ = rewrite.try_rewrite_on_error(
|
||||||
|
|||||||
Reference in New Issue
Block a user