diff --git a/applications/admin/controllers/appadmin.py b/applications/admin/controllers/appadmin.py
index 3060945c..73303f53 100644
--- a/applications/admin/controllers/appadmin.py
+++ b/applications/admin/controllers/appadmin.py
@@ -213,7 +213,7 @@ def select():
if is_imap:
step = 3
-
+
stop = start + step
table = None
@@ -409,7 +409,7 @@ def ccache():
import copy
import time
import math
- from gluon import portalocker
+ from pydal.contrib import portalocker
ram = {
'entries': 0,
@@ -421,7 +421,7 @@ def ccache():
'oldest': time.time(),
'keys': []
}
-
+
disk = copy.copy(ram)
total = copy.copy(ram)
disk['keys'] = []
@@ -465,8 +465,7 @@ def ccache():
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
for key in cache.disk.storage:
- value = cache.disk.storage[key]
- if isinstance(value[1], dict):
+ if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
try:
@@ -482,7 +481,7 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
- ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
+ ram_keys = list(ram) # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
diff --git a/applications/admin/controllers/debug.py b/applications/admin/controllers/debug.py
index 4f04ab09..447948d6 100644
--- a/applications/admin/controllers/debug.py
+++ b/applications/admin/controllers/debug.py
@@ -4,7 +4,7 @@ import gluon.dal
import gluon.html
import gluon.validators
import code
-from gluon.debug import communicate, web_debugger, qdb_debugger
+from gluon.debug import communicate, web_debugger, dbg_debugger
from gluon._compat import thread
import pydoc
@@ -39,7 +39,7 @@ def reset():
return 'done'
-# new implementation using qdb
+# new implementation using dbg
def interact():
app = request.args(0) or 'admin'
@@ -149,7 +149,7 @@ def breakpoints():
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,
+ err = dbg_debugger.do_set_breakpoint(filename,
form.vars.lineno,
form.vars.temporary,
form.vars.condition)
@@ -158,13 +158,13 @@ def breakpoints():
for item in request.vars:
if item[:7] == 'delete_':
- qdb_debugger.do_clear(item[7:])
+ dbg_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()]
+ for bp in dbg_debugger.do_list_breakpoint()]
return dict(breakpoints=breakpoints, form=form)
@@ -193,18 +193,18 @@ def toggle_breakpoint():
else:
lineno = None
if lineno is not None:
- for bp in qdb_debugger.do_list_breakpoint():
+ for bp in dbg_debugger.do_list_breakpoint():
no, bp_filename, bp_lineno, temporary, enabled, hits, cond = bp
# normalize path name: replace slashes, references, etc...
bp_filename = os.path.normpath(os.path.normcase(bp_filename))
if filename == bp_filename and lineno == bp_lineno:
- err = qdb_debugger.do_clear_breakpoint(filename, lineno)
+ err = dbg_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)
+ err = dbg_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
@@ -224,7 +224,7 @@ def list_breakpoints():
'applications', request.vars.filename)
# normalize path name: replace slashes, references, etc...
filename = os.path.normpath(os.path.normcase(filename))
- for bp in qdb_debugger.do_list_breakpoint():
+ for bp in dbg_debugger.do_list_breakpoint():
no, bp_filename, bp_lineno, temporary, enabled, hits, cond = bp
# normalize path name: replace slashes, references, etc...
bp_filename = os.path.normpath(os.path.normcase(bp_filename))
diff --git a/applications/admin/controllers/default.py b/applications/admin/controllers/default.py
index 6ad8b332..94fc91ce 100644
--- a/applications/admin/controllers/default.py
+++ b/applications/admin/controllers/default.py
@@ -1960,7 +1960,7 @@ def git_push():
def plugins():
app = request.args(0)
- from serializers import loads_json
+ from gluon.serializers import loads_json
if not session.plugins:
try:
rawlist = urlopen("http://www.web2pyslices.com/" +
diff --git a/applications/admin/controllers/webservices.py b/applications/admin/controllers/webservices.py
index 4f5a352c..9ae704db 100644
--- a/applications/admin/controllers/webservices.py
+++ b/applications/admin/controllers/webservices.py
@@ -81,44 +81,44 @@ def install(app_name, filename, data, overwrite=True):
@service.jsonrpc
def attach_debugger(host='localhost', port=6000, authkey='secret password'):
- import gluon.contrib.qdb as qdb
+ import gluon.contrib.dbg as dbg
import gluon.debug
from multiprocessing.connection import Listener
if isinstance(authkey, unicode):
authkey = authkey.encode('utf8')
- if not hasattr(gluon.debug, 'qdb_listener'):
+ if not hasattr(gluon.debug, 'dbg_listener'):
# create a remote debugger server and wait for connection
address = (host, port) # family is deduced to be 'AF_INET'
- gluon.debug.qdb_listener = Listener(address, authkey=authkey)
- gluon.debug.qdb_connection = gluon.debug.qdb_listener.accept()
+ gluon.debug.dbg_listener = Listener(address, authkey=authkey)
+ gluon.debug.dbg_connection = gluon.debug.dbg_listener.accept()
# create the backend
- gluon.debug.qdb_debugger = qdb.Qdb(gluon.debug.qdb_connection)
- gluon.debug.dbg = gluon.debug.qdb_debugger
+ gluon.debug.dbg_debugger = dbg.Qdb(gluon.debug.dbg_connection)
+ gluon.debug.dbg = gluon.debug.dbg_debugger
# welcome message (this should be displayed on the frontend)
- print('debugger connected to', gluon.debug.qdb_listener.last_accepted)
+ print('debugger connected to', gluon.debug.dbg_listener.last_accepted)
return True # connection successful!
@service.jsonrpc
def detach_debugger():
- import gluon.contrib.qdb as qdb
+ import gluon.contrib.dbg as dbg
import gluon.debug
# stop current debugger
- if gluon.debug.qdb_debugger:
+ if gluon.debug.dbg_debugger:
try:
- gluon.debug.qdb_debugger.do_quit()
+ gluon.debug.dbg_debugger.do_quit()
except:
pass
- if hasattr(gluon.debug, 'qdb_listener'):
- if gluon.debug.qdb_connection:
- gluon.debug.qdb_connection.close()
- del gluon.debug.qdb_connection
- if gluon.debug.qdb_listener:
- gluon.debug.qdb_listener.close()
- del gluon.debug.qdb_listener
- gluon.debug.qdb_debugger = None
+ if hasattr(gluon.debug, 'dbg_listener'):
+ if gluon.debug.dbg_connection:
+ gluon.debug.dbg_connection.close()
+ del gluon.debug.dbg_connection
+ if gluon.debug.dbg_listener:
+ gluon.debug.dbg_listener.close()
+ del gluon.debug.dbg_listener
+ gluon.debug.dbg_debugger = None
return True
diff --git a/applications/admin/controllers/wizard.py b/applications/admin/controllers/wizard.py
index f4511b96..c3d53548 100644
--- a/applications/admin/controllers/wizard.py
+++ b/applications/admin/controllers/wizard.py
@@ -7,7 +7,7 @@ import pickle
import urllib
import glob
from gluon.admin import app_create, plugin_install
-from gluon.fileutils import abspath, read_file, write_file
+from gluon.fileutils import abspath, read_file, write_file, open_file
def reset(session):
@@ -67,7 +67,7 @@ def index():
'..', app, 'wizard.metadata'))
if os.path.exists(meta):
try:
- metafile = open(meta, 'rb')
+ metafile = open_file(meta, 'rb')
try:
session.app = pickle.load(metafile)
finally:
@@ -402,7 +402,7 @@ db.auth_user.email.requires = (
def fix_db(filename):
params = dict(session.app['params'])
- content = read_file(filename, 'rb')
+ content = read_file(filename, 'r')
if 'auth_user' in session.app['tables']:
auth_user = make_table('auth_user', session.app['table_auth_user'])
content = content.replace('sqlite://storage.sqlite',
@@ -424,7 +424,7 @@ auth.settings.login_form = RPXAccount(request,
domain = settings.login_config.split(':')[0],
url = "http://%s/%s/default/user/login" % (request.env.http_host,request.application))
"""
- write_file(filename, content, 'wb')
+ write_file(filename, content, 'w')
def make_menu(pages):
@@ -493,7 +493,7 @@ def create(options):
### save metadata in newapp/wizard.metadata
try:
meta = os.path.join(request.folder, '..', app, 'wizard.metadata')
- file = open(meta, 'wb')
+ file = open_file(meta, 'wb')
pickle.dump(session.app, file)
file.close()
except IOError:
@@ -521,7 +521,7 @@ def create(options):
### write configuration file into newapp/models/0.py
model = os.path.join(request.folder, '..', app, 'models', '0.py')
- file = open(model, 'wb')
+ file = open_file(model, 'w')
try:
file.write("from gluon.storage import Storage\n")
file.write("settings = Storage()\n\n")
@@ -534,7 +534,7 @@ def create(options):
### write configuration file into newapp/models/menu.py
if options.generate_menu:
model = os.path.join(request.folder, '..', app, 'models', 'menu.py')
- file = open(model, 'wb')
+ file = open_file(model, 'w')
try:
file.write(make_menu(session.app['pages']))
finally:
@@ -548,7 +548,7 @@ def create(options):
if options.generate_model:
model = os.path.join(
request.folder, '..', app, 'models', 'db_wizard.py')
- file = open(model, 'wb')
+ file = open_file(model, 'w')
try:
file.write('### we prepend t_ to tablenames and f_ to fieldnames for disambiguity\n\n')
tables = sort_tables(session.app['tables'])
@@ -564,7 +564,7 @@ def create(options):
if os.path.exists(model):
os.unlink(model)
if options.populate_database and session.app['tables']:
- file = open(model, 'wb')
+ file = open_file(model, 'w')
try:
file.write(populate(session.app['tables']))
finally:
@@ -574,7 +574,7 @@ def create(options):
if options.generate_controller:
controller = os.path.join(
request.folder, '..', app, 'controllers', 'default.py')
- file = open(controller, 'wb')
+ file = open_file(controller, 'w')
try:
file.write("""# -*- coding: utf-8 -*-
### required - do no delete
@@ -594,7 +594,7 @@ def call(): return service()
for page in session.app['pages']:
view = os.path.join(
request.folder, '..', app, 'views', 'default', page + '.html')
- file = open(view, 'wb')
+ file = open_file(view, 'w')
try:
file.write(
make_view(page, session.app.get('page_' + page, '')))
diff --git a/applications/admin/static/js/web2py.js b/applications/admin/static/js/web2py.js
index 7853e8cb..7b9d9802 100644
--- a/applications/admin/static/js/web2py.js
+++ b/applications/admin/static/js/web2py.js
@@ -338,6 +338,7 @@
web2py.trap_form(action, target);
web2py.ajax_init('#' + target);
web2py.after_ajax(xhr);
+ web2py.fire(element, 'w2p:componentComplete', [xhr, status], target); // Let us know the component is finished loading
}
});
}
diff --git a/applications/examples/controllers/appadmin.py b/applications/examples/controllers/appadmin.py
index 3060945c..73303f53 100644
--- a/applications/examples/controllers/appadmin.py
+++ b/applications/examples/controllers/appadmin.py
@@ -213,7 +213,7 @@ def select():
if is_imap:
step = 3
-
+
stop = start + step
table = None
@@ -409,7 +409,7 @@ def ccache():
import copy
import time
import math
- from gluon import portalocker
+ from pydal.contrib import portalocker
ram = {
'entries': 0,
@@ -421,7 +421,7 @@ def ccache():
'oldest': time.time(),
'keys': []
}
-
+
disk = copy.copy(ram)
total = copy.copy(ram)
disk['keys'] = []
@@ -465,8 +465,7 @@ def ccache():
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
for key in cache.disk.storage:
- value = cache.disk.storage[key]
- if isinstance(value[1], dict):
+ if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
try:
@@ -482,7 +481,7 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
- ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
+ ram_keys = list(ram) # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
diff --git a/applications/examples/static/503.html b/applications/examples/static/503.html
new file mode 100644
index 00000000..b063a75b
--- /dev/null
+++ b/applications/examples/static/503.html
@@ -0,0 +1 @@
+
Temporarily down for maintenance
\ No newline at end of file
diff --git a/applications/examples/static/js/web2py.js b/applications/examples/static/js/web2py.js
index 7853e8cb..7b9d9802 100644
--- a/applications/examples/static/js/web2py.js
+++ b/applications/examples/static/js/web2py.js
@@ -338,6 +338,7 @@
web2py.trap_form(action, target);
web2py.ajax_init('#' + target);
web2py.after_ajax(xhr);
+ web2py.fire(element, 'w2p:componentComplete', [xhr, status], target); // Let us know the component is finished loading
}
});
}
diff --git a/applications/welcome/controllers/appadmin.py b/applications/welcome/controllers/appadmin.py
index 4d4e965f..73303f53 100644
--- a/applications/welcome/controllers/appadmin.py
+++ b/applications/welcome/controllers/appadmin.py
@@ -465,8 +465,7 @@ def ccache():
ram['keys'].append((key, GetInHMS(time.time() - value[0])))
for key in cache.disk.storage:
- value = cache.disk.storage[key]
- if isinstance(value[1], dict):
+ if key == 'web2py_cache_statistics' and isinstance(value[1], dict):
disk['hits'] = value[1]['hit_total'] - value[1]['misses']
disk['misses'] = value[1]['misses']
try:
@@ -482,7 +481,7 @@ def ccache():
disk['oldest'] = value[0]
disk['keys'].append((key, GetInHMS(time.time() - value[0])))
- ram_keys = ram.keys() # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
+ ram_keys = list(ram) # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses']
ram_keys.remove('ratio')
ram_keys.remove('oldest')
for key in ram_keys:
diff --git a/applications/welcome/static/503.html b/applications/welcome/static/503.html
new file mode 100644
index 00000000..b063a75b
--- /dev/null
+++ b/applications/welcome/static/503.html
@@ -0,0 +1 @@
+Temporarily down for maintenance
\ No newline at end of file
diff --git a/applications/welcome/static/js/web2py.js b/applications/welcome/static/js/web2py.js
index 7853e8cb..7b9d9802 100644
--- a/applications/welcome/static/js/web2py.js
+++ b/applications/welcome/static/js/web2py.js
@@ -338,6 +338,7 @@
web2py.trap_form(action, target);
web2py.ajax_init('#' + target);
web2py.after_ajax(xhr);
+ web2py.fire(element, 'w2p:componentComplete', [xhr, status], target); // Let us know the component is finished loading
}
});
}
diff --git a/gluon/contrib/qdb.py b/gluon/contrib/dbg.py
similarity index 70%
rename from gluon/contrib/qdb.py
rename to gluon/contrib/dbg.py
index 6e32e6f7..0dcbcf15 100644
--- a/gluon/contrib/qdb.py
+++ b/gluon/contrib/dbg.py
@@ -1,13 +1,14 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# coding:utf-8
-"Queues(Pipe)-based independent remote client-server Python Debugger"
+"Queues(Pipe)-based independent remote client-server Python Debugger (new-py3)"
+
from __future__ import print_function
__author__ = "Mariano Reingart (reingart@gmail.com)"
__copyright__ = "Copyright (C) 2011 Mariano Reingart"
__license__ = "LGPL 3.0"
-__version__ = "1.01b"
+__version__ = "1.5.2"
# remote debugger queue-based (jsonrpc-like interface):
# - bidirectional communication (request - response calls in both ways)
@@ -24,13 +25,19 @@ import traceback
import cmd
import pydoc
import threading
+import collections
+
+
+# Speed Ups: global variables
+breaks = []
class Qdb(bdb.Bdb):
"Qdb Debugger Backend"
def __init__(self, pipe, redirect_stdio=True, allow_interruptions=False,
- skip=[__name__]):
+ use_speedups=True, skip=[__name__]):
+ global breaks
kwargs = {}
if sys.version_info > (2, 7):
kwargs['skip'] = skip
@@ -38,12 +45,15 @@ class Qdb(bdb.Bdb):
self.frame = None
self.i = 1 # sequential RPC call id
self.waiting = False
- self.pipe = pipe # for communication
+ self.pipe = pipe # for communication
self._wait_for_mainpyfile = False
self._wait_for_breakpoint = False
self.mainpyfile = ""
self._lineno = None # last listed line numbre
+ # ignore filenames (avoid spurious interaction specially on py2)
+ self.ignore_files = [self.canonic(f) for f in (__file__, bdb.__file__)]
# replace system standard input and output (send them thru the pipe)
+ self.old_stdio = sys.stdin, sys.stdout, sys.stderr
if redirect_stdio:
sys.stdin = self
sys.stdout = self
@@ -54,6 +64,10 @@ class Qdb(bdb.Bdb):
self.allow_interruptions = allow_interruptions
self.burst = 0 # do not send notifications ("burst" mode)
self.params = {} # optional parameters for interaction
+
+ # flags to reduce overhead (only stop at breakpoint or interrupt)
+ self.use_speedups = use_speedups
+ self.fast_continue = False
def pull_actions(self):
# receive a remote procedure call from the frontend:
@@ -62,14 +76,14 @@ class Qdb(bdb.Bdb):
request = self.pipe.recv()
if request.get("method") == 'run':
return None
- response = {'version': '1.1', 'id': request.get('id'),
- 'result': None,
+ response = {'version': '1.1', 'id': request.get('id'),
+ 'result': None,
'error': None}
try:
# dispatch message (JSON RPC like)
method = getattr(self, request['method'])
- response['result'] = method.__call__(*request['args'],
- **request.get('kwargs', {}))
+ response['result'] = method.__call__(*request['args'],
+ **request.get('kwargs', {}))
except Exception as e:
response['error'] = {'code': 0, 'message': str(e)}
# send the result for normal method calls, not for notifications
@@ -83,9 +97,17 @@ class Qdb(bdb.Bdb):
# check for non-interaction rpc (set_breakpoint, interrupt)
while self.allow_interruptions and self.pipe.poll():
self.pull_actions()
+ # check for non-interaction rpc (set_breakpoint, interrupt)
+ while self.pipe.poll():
+ self.pull_actions()
+ if (frame.f_code.co_filename, frame.f_lineno) not in breaks and \
+ self.fast_continue:
+ return self.trace_dispatch
# process the frame (see Bdb.trace_dispatch)
+ ##if self.fast_continue:
+ ## return self.trace_dispatch
if self.quitting:
- return # None
+ return # None
if event == 'line':
return self.dispatch_line(frame)
if event == 'call':
@@ -102,13 +124,13 @@ class Qdb(bdb.Bdb):
if self._wait_for_mainpyfile or self._wait_for_breakpoint:
return
if self.stop_here(frame):
- self.interaction(frame, None)
-
+ self.interaction(frame)
+
def user_line(self, frame):
"""This function is called when we stop or break at this line."""
if self._wait_for_mainpyfile:
if (not self.canonic(frame.f_code.co_filename).startswith(self.mainpyfile)
- or frame.f_lineno <= 0):
+ or frame.f_lineno<= 0):
return
self._wait_for_mainpyfile = 0
if self._wait_for_breakpoint:
@@ -125,14 +147,15 @@ class Qdb(bdb.Bdb):
extype, exvalue, trace = info
# pre-process stack trace as it isn't pickeable (cannot be sent pure)
msg = ''.join(traceback.format_exception(extype, exvalue, trace))
- trace = traceback.extract_tb(trace)
+ # in python3.5, convert FrameSummary to tuples (py2.7+ compatibility)
+ tb = [tuple(fs) for fs in traceback.extract_tb(trace)]
title = traceback.format_exception_only(extype, exvalue)[0]
# send an Exception notification
- msg = {'method': 'exception',
- 'args': (title, extype.__name__, exvalue, trace, msg),
+ msg = {'method': 'exception',
+ 'args': (title, extype.__name__, repr(exvalue), tb, msg),
'id': None}
self.pipe.send(msg)
- self.interaction(frame, info)
+ self.interaction(frame)
def run(self, code, interp=None, *args, **kwargs):
try:
@@ -151,39 +174,58 @@ class Qdb(bdb.Bdb):
# The script has to run in __main__ namespace (clear it)
import __main__
import imp
+ filename = os.path.abspath(filename)
__main__.__dict__.clear()
- __main__.__dict__.update({"__name__": "__main__",
- "__file__": filename,
+ __main__.__dict__.update({"__name__" : "__main__",
+ "__file__" : filename,
"__builtins__": __builtins__,
- "imp": imp, # need for run
- })
+ "imp" : imp, # need for run
+ })
- # avoid stopping before we reach the main script
+ # avoid stopping before we reach the main script
self._wait_for_mainpyfile = 1
self.mainpyfile = self.canonic(filename)
self._user_requested_quit = 0
- statement = 'imp.load_source("__main__", "%s")' % filename
- # notify and wait frontend to set initial params and breakpoints
- self.pipe.send({'method': 'startup', 'args': (__version__, )})
+ if sys.version_info>(3,0):
+ statement = 'imp.load_source("__main__", "%s")' % filename
+ else:
+ statement = 'execfile(%r)' % filename
+ self.startup()
+ self.run(statement)
+
+ def startup(self):
+ "Notify and wait frontend to set initial params and breakpoints"
+ # send some useful info to identify session
+ thread = threading.current_thread()
+ # get the caller module filename
+ frame = sys._getframe()
+ fn = self.canonic(frame.f_code.co_filename)
+ while frame.f_back and self.canonic(frame.f_code.co_filename) == fn:
+ frame = frame.f_back
+ args = [__version__, os.getpid(), thread.name, " ".join(sys.argv),
+ frame.f_code.co_filename]
+ self.pipe.send({'method': 'startup', 'args': args})
while self.pull_actions() is not None:
pass
- self.run(statement)
# General interaction function
- def interaction(self, frame, info=None):
+ def interaction(self, frame):
# chache frame locals to ensure that modifications are not overwritten
self.frame_locals = frame and frame.f_locals or {}
# extract current filename and line number
code, lineno = frame.f_code, frame.f_lineno
- filename = code.co_filename
+ filename = self.canonic(code.co_filename)
basename = os.path.basename(filename)
+ # check if interaction should be ignored (i.e. debug modules internals)
+ if filename in self.ignore_files:
+ return
message = "%s:%s" % (basename, lineno)
if code.co_name != "?":
message = "%s: %s()" % (message, code.co_name)
- # wait user events
- self.waiting = True
+ # wait user events
+ self.waiting = True
self.frame = frame
try:
while self.waiting:
@@ -202,11 +244,10 @@ class Qdb(bdb.Bdb):
if self.params.get('environment'):
kwargs['environment'] = self.do_environment()
self.pipe.send({'method': 'interaction', 'id': None,
- 'args': (filename, self.frame.f_lineno, line),
- 'kwargs': kwargs})
+ 'args': (filename, self.frame.f_lineno, line),
+ 'kwargs': kwargs})
self.pull_actions()
-
finally:
self.waiting = False
self.frame = None
@@ -228,6 +269,8 @@ class Qdb(bdb.Bdb):
frame = sys._getframe().f_back
self._wait_for_mainpyfile = frame.f_code.co_filename
self._wait_for_breakpoint = 0
+ # reinitialize debugger internal settings
+ self.fast_continue = False
bdb.Bdb.set_trace(self, frame)
# Command definitions, called by interaction()
@@ -235,34 +278,38 @@ class Qdb(bdb.Bdb):
def do_continue(self):
self.set_continue()
self.waiting = False
+ self.fast_continue = self.use_speedups
def do_step(self):
self.set_step()
self.waiting = False
+ self.fast_continue = False
def do_return(self):
self.set_return(self.frame)
self.waiting = False
+ self.fast_continue = False
def do_next(self):
self.set_next(self.frame)
self.waiting = False
+ self.fast_continue = False
def interrupt(self):
- self.set_step()
+ self.set_trace()
+ self.fast_continue = False
def do_quit(self):
self.set_quit()
self.waiting = False
+ self.fast_continue = False
def do_jump(self, lineno):
arg = int(lineno)
try:
self.frame.f_lineno = arg
- return arg
except ValueError as e:
- print('*** Jump failed:', e)
- return False
+ return str(e)
def do_list(self, arg):
last = None
@@ -272,7 +319,7 @@ class Qdb(bdb.Bdb):
else:
first = arg
elif not self._lineno:
- first = max(1, self.frame.f_lineno - 5)
+ first = max(1, self.frame.f_lineno - 5)
else:
first = self._lineno + 1
if last is None:
@@ -280,11 +327,11 @@ class Qdb(bdb.Bdb):
filename = self.frame.f_code.co_filename
breaklist = self.get_file_breaks(filename)
lines = []
- for lineno in range(first, last + 1):
+ for lineno in range(first, last+1):
line = linecache.getline(filename, lineno,
self.frame.f_globals)
if not line:
- lines.append((filename, lineno, '', current, "\n"))
+ lines.append((filename, lineno, '', "", "\n"))
break
else:
breakpoint = "B" if lineno in breaklist else ""
@@ -297,6 +344,8 @@ class Qdb(bdb.Bdb):
return open(filename, "Ur").read()
def do_set_breakpoint(self, filename, lineno, temporary=0, cond=None):
+ global breaks # list for speedups!
+ breaks.append((filename.replace("\\", "/"), int(lineno)))
return self.set_break(filename, int(lineno), temporary, cond)
def do_list_breakpoint(self):
@@ -304,8 +353,8 @@ class Qdb(bdb.Bdb):
if self.breaks: # There's at least one
for bp in bdb.Breakpoint.bpbynumber:
if bp:
- breaks.append((bp.number, bp.file, bp.line,
- bp.temporary, bp.enabled, bp.hits, bp.cond, ))
+ breaks.append((bp.number, bp.file, bp.line,
+ bp.temporary, bp.enabled, bp.hits, bp.cond, ))
return breaks
def do_clear_breakpoint(self, filename, lineno):
@@ -321,24 +370,33 @@ class Qdb(bdb.Bdb):
print('*** DO_CLEAR failed', err)
def do_eval(self, arg, safe=True):
- ret = eval(arg, self.frame.f_globals,
- self.frame_locals)
+ if self.frame:
+ ret = eval(arg, self.frame.f_globals,
+ self.frame_locals)
+ else:
+ ret = RPCError("No current frame available to eval")
if safe:
ret = pydoc.cram(repr(ret), 255)
return ret
- def do_exec(self, arg):
- locals = self.frame_locals
- globals = self.frame.f_globals
- code = compile(arg + '\n', '', 'single')
- save_displayhook = sys.displayhook
- self.displayhook_value = None
- try:
- sys.displayhook = self.displayhook
- exec code in globals, locals
- finally:
- sys.displayhook = save_displayhook
- return self.displayhook_value
+ def do_exec(self, arg, safe=True):
+ if not self.frame:
+ ret = RPCError("No current frame available to exec")
+ else:
+ locals = self.frame_locals
+ globals = self.frame.f_globals
+ code = compile(arg + '\n', '', 'single')
+ save_displayhook = sys.displayhook
+ self.displayhook_value = None
+ try:
+ sys.displayhook = self.displayhook
+ exec(code, globals, locals)
+ ret = self.displayhook_value
+ finally:
+ sys.displayhook = save_displayhook
+ if safe:
+ ret = pydoc.cram(repr(ret), 255)
+ return ret
def do_where(self):
"print_stack_trace"
@@ -355,29 +413,33 @@ class Qdb(bdb.Bdb):
env = {'locals': {}, 'globals': {}}
# converts the frame global and locals to a short text representation:
if self.frame:
- for name, value in self.frame_locals.items():
- env['locals'][name] = pydoc.cram(repr(
- value), 255), repr(type(value))
- for name, value in self.frame.f_globals.items():
- env['globals'][name] = pydoc.cram(repr(
- value), 20), repr(type(value))
+ for scope, max_length, vars in (
+ ("locals", 255, list(self.frame_locals.items())),
+ ("globals", 20, list(self.frame.f_globals.items())), ):
+ for (name, value) in vars:
+ try:
+ short_repr = pydoc.cram(repr(value), max_length)
+ except Exception as e:
+ # some objects cannot be represented...
+ short_repr = "**exception** %s" % repr(e)
+ env[scope][name] = (short_repr, repr(type(value)))
return env
def get_autocomplete_list(self, expression):
"Return list of auto-completion options for expression"
try:
- obj = self.do_eval(expression)
+ obj = self.do_eval(expression, safe=False)
except:
return []
else:
return dir(obj)
-
+
def get_call_tip(self, expression):
"Return list of auto-completion options for expression"
try:
obj = self.do_eval(expression)
except Exception as e:
- return ('', '', str(e))
+ return ('', '', str(e))
else:
name = ''
try:
@@ -405,7 +467,7 @@ class Qdb(bdb.Bdb):
break
if f is not None:
drop_self = 1
- elif callable(obj):
+ elif isinstance(obj, collections.Callable):
# use the obj as a function by default
f = obj
# Get the __call__ method instead.
@@ -416,7 +478,7 @@ class Qdb(bdb.Bdb):
if f:
argspec = inspect.formatargspec(*inspect.getargspec(f))
doc = ''
- if callable(obj):
+ if isinstance(obj, collections.Callable):
try:
doc = inspect.getdoc(obj)
except:
@@ -442,15 +504,21 @@ class Qdb(bdb.Bdb):
self.waiting = False
self.frame = None
- def post_mortem(self, t=None):
+ def post_mortem(self, info=None):
+ "Debug an un-handled python exception"
+ # check if post mortem mode is enabled:
+ if not self.params.get('postmortem', True):
+ return
# handling the default
- if t is None:
+ if info is None:
# sys.exc_info() returns (type, value, traceback) if an exception is
# being handled, otherwise it returns None
- t = sys.exc_info()[2]
- if t is None:
- raise ValueError("A valid traceback must be passed if no "
- "exception is being handled")
+ info = sys.exc_info()
+ # extract the traceback object:
+ t = info[2]
+ if t is None:
+ raise ValueError("A valid traceback must be passed if no "
+ "exception is being handled")
self.reset()
# get last frame:
while t is not None:
@@ -460,9 +528,24 @@ class Qdb(bdb.Bdb):
filename = code.co_filename
line = linecache.getline(filename, lineno)
#(filename, lineno, "", current, line, )}
+ # SyntaxError doesn't execute even one line, so avoid mainpyfile check
+ self._wait_for_mainpyfile = False
+ # send exception information & request interaction
+ self.user_exception(frame, info)
- self.interaction(frame)
-
+ def ping(self):
+ "Minimal method to test that the pipe (connection) is alive"
+ try:
+ # get some non-trivial data to compare:
+ args = (id(object()), )
+ msg = {'method': 'ping', 'args': args, 'id': None}
+ self.pipe.send(msg)
+ msg = self.pipe.recv()
+ # check if data received is ok (alive and synchonized!)
+ return msg['result'] == args
+ except (IOError, EOFError):
+ return None
+
# console file-like object emulation
def readline(self):
"Replacement for stdin.readline()"
@@ -483,9 +566,9 @@ class Qdb(bdb.Bdb):
"Replacement for stdout.write()"
msg = {'method': 'write', 'args': (text, ), 'id': None}
self.pipe.send(msg)
-
+
def writelines(self, l):
- map(self.write, l)
+ list(map(self.write, l))
def flush(self):
pass
@@ -493,10 +576,25 @@ class Qdb(bdb.Bdb):
def isatty(self):
return 0
+ def encoding(self):
+ return None # use default, 'utf-8' should be better...
+
+ def close(self):
+ # revert redirections and close connection
+ if sys:
+ sys.stdin, sys.stdout, sys.stderr = self.old_stdio
+ try:
+ self.pipe.close()
+ except:
+ pass
+
+ def __del__(self):
+ self.close()
+
class QueuePipe(object):
"Simulated pipe for threads (using two queues)"
-
+
def __init__(self, name, in_queue, out_queue):
self.__name = name
self.in_queue = in_queue
@@ -520,12 +618,13 @@ class RPCError(RuntimeError):
"Remote Error (not user exception)"
pass
-
+
class Frontend(object):
"Qdb generic Frontend interface"
-
+
def __init__(self, pipe):
self.i = 1
+ self.info = ()
self.pipe = pipe
self.notifies = []
self.read_lock = threading.RLock()
@@ -545,12 +644,13 @@ class Frontend(object):
finally:
self.write_lock.release()
- def startup(self):
+ def startup(self, version, pid, thread_name, argv, filename):
+ self.info = (version, pid, thread_name, argv, filename)
self.send({'method': 'run', 'args': (), 'id': None})
def interaction(self, filename, lineno, line, *kwargs):
raise NotImplementedError
-
+
def exception(self, title, extype, exvalue, trace, request):
"Show a user_exception"
raise NotImplementedError
@@ -558,7 +658,7 @@ class Frontend(object):
def write(self, text):
"Console output (print)"
raise NotImplementedError
-
+
def readline(self, text):
"Console input/rawinput"
raise NotImplementedError
@@ -570,10 +670,10 @@ class Frontend(object):
# wait for a message...
request = self.recv()
else:
- # process an asyncronus notification received earlier
+ # process an asyncronus notification received earlier
request = self.notifies.pop(0)
return self.process_message(request)
-
+
def process_message(self, request):
if request:
result = None
@@ -584,17 +684,19 @@ class Frontend(object):
elif request.get('method') == 'interaction':
self.interaction(*request.get("args"), **request.get("kwargs"))
elif request.get('method') == 'startup':
- self.startup()
+ self.startup(*request['args'])
elif request.get('method') == 'exception':
self.exception(*request['args'])
elif request.get('method') == 'write':
self.write(*request.get("args"))
elif request.get('method') == 'readline':
result = self.readline()
+ elif request.get('method') == 'ping':
+ result = request['args']
if result:
- response = {'version': '1.1', 'id': request.get('id'),
- 'result': result,
- 'error': None}
+ response = {'version': '1.1', 'id': request.get('id'),
+ 'result': result,
+ 'error': None}
self.send(response)
return True
@@ -603,18 +705,17 @@ class Frontend(object):
req = {'method': method, 'args': args, 'id': self.i}
self.send(req)
self.i += 1 # increment the id
- while True:
+ while 1:
# wait until command acknowledge (response id match the request)
res = self.recv()
if 'id' not in res or not res['id']:
- # nested notification received (i.e. write)! process it!
- self.process_message(res)
+ # nested notification received (i.e. write)! process it later...
+ self.notifies.append(res)
elif 'result' not in res:
# nested request received (i.e. readline)! process it!
self.process_message(res)
- elif long(req['id']) != long(res['id']):
- print("DEBUGGER wrong packet received: expecting id", req[
- 'id'], res['id'])
+ elif int(req['id']) != int(res['id']):
+ print("DEBUGGER wrong packet received: expecting id", req['id'], res['id'])
# protocol state is unknown
elif 'error' in res and res['error']:
raise RPCError(res['error']['message'])
@@ -624,23 +725,23 @@ class Frontend(object):
def do_step(self, arg=None):
"Execute the current line, stop at the first possible occasion"
self.call('do_step')
-
+
def do_next(self, arg=None):
"Execute the current line, do not stop at function calls"
self.call('do_next')
- def do_continue(self, arg=None):
+ def do_continue(self, arg=None):
"Continue execution, only stop when a breakpoint is encountered."
self.call('do_continue')
-
- def do_return(self, arg=None):
+
+ def do_return(self, arg=None):
"Continue execution until the current function returns"
self.call('do_return')
- def do_jump(self, arg):
- "Set the next line that will be executed."
+ def do_jump(self, arg):
+ "Set the next line that will be executed (None if sucess or message)"
res = self.call('do_jump', arg)
- print(res)
+ return res
def do_where(self, arg=None):
"Print a stack trace, with the most recent frame at the bottom."
@@ -649,7 +750,7 @@ class Frontend(object):
def do_quit(self, arg=None):
"Quit from the debugger. The program being executed is aborted."
self.call('do_quit')
-
+
def do_eval(self, expr):
"Inspect the value of the expression"
return self.call('do_eval', expr)
@@ -677,11 +778,11 @@ class Frontend(object):
def do_clear_file_breakpoints(self, filename):
"Remove all breakpoints at filename"
self.call('do_clear_breakpoints', filename, lineno)
-
+
def do_list_breakpoint(self):
"List all breakpoints"
return self.call('do_list_breakpoint')
-
+
def do_exec(self, statement):
return self.call('do_exec', statement)
@@ -690,7 +791,7 @@ class Frontend(object):
def get_call_tip(self, expression):
return self.call('get_call_tip', expression)
-
+
def interrupt(self):
"Immediately stop at the first possible occasion (outside interaction)"
# this is a notification!, do not expect a response
@@ -700,7 +801,7 @@ class Frontend(object):
def set_burst(self, value):
req = {'method': 'set_burst', 'args': (value, )}
self.send(req)
-
+
def set_params(self, params):
req = {'method': 'set_params', 'args': (params, )}
self.send(req)
@@ -708,15 +809,15 @@ class Frontend(object):
class Cli(Frontend, cmd.Cmd):
"Qdb Front-end command line interface"
-
+
def __init__(self, pipe, completekey='tab', stdin=None, stdout=None, skip=None):
cmd.Cmd.__init__(self, completekey, stdin, stdout)
Frontend.__init__(self, pipe)
# redefine Frontend methods:
-
+
def run(self):
- while True:
+ while 1:
try:
Frontend.run(self)
except KeyboardInterrupt:
@@ -736,31 +837,30 @@ class Cli(Frontend, cmd.Cmd):
def write(self, text):
print(text, end=' ')
-
+
def readline(self):
- return raw_input()
-
+ return input()
+
def postcmd(self, stop, line):
- return not line.startswith("h") # stop
+ return not line.startswith("h") # stop
do_h = cmd.Cmd.do_help
-
+
do_s = Frontend.do_step
do_n = Frontend.do_next
- do_c = Frontend.do_continue
+ do_c = Frontend.do_continue
do_r = Frontend.do_return
- do_j = Frontend.do_jump
do_q = Frontend.do_quit
def do_eval(self, args):
"Inspect the value of the expression"
print(Frontend.do_eval(self, args))
-
+
def do_list(self, args):
"List source code for the current file"
lines = Frontend.do_list(self, eval(args, {}, {}) if args else None)
self.print_lines(lines)
-
+
def do_where(self, args):
"Print a stack trace, with the most recent frame at the bottom."
lines = Frontend.do_where(self)
@@ -772,7 +872,7 @@ class Cli(Frontend, cmd.Cmd):
print("=" * 78)
print(key.capitalize())
print("-" * 78)
- for name, value in env[key].items():
+ for name, value in list(env[key].items()):
print("%-12s = %s" % (name, value))
def do_list_breakpoint(self, arg=None):
@@ -794,11 +894,18 @@ class Cli(Frontend, cmd.Cmd):
else:
self.do_list_breakpoint()
+ def do_jump(self, args):
+ "Jump to the selected line"
+ ret = Frontend.do_jump(self, args)
+ if ret: # show error message if failed
+ print("cannot jump:", ret)
+
do_b = do_set_breakpoint
do_l = do_list
do_p = do_eval
do_w = do_where
do_e = do_environment
+ do_j = do_jump
def default(self, line):
"Default command"
@@ -813,61 +920,85 @@ class Cli(Frontend, cmd.Cmd):
print()
+# WORKAROUND for python3 server using pickle's HIGHEST_PROTOCOL (now 3)
+# but python2 client using pickles's protocol version 2
+if sys.version_info[0] > 2:
+
+ import multiprocessing.reduction # forking in py2
+
+ class ForkingPickler2(multiprocessing.reduction.ForkingPickler):
+ def __init__(self, file, protocol=None, fix_imports=True):
+ # downgrade to protocol ver 2
+ protocol = 2
+ super().__init__(file, protocol, fix_imports)
+
+ multiprocessing.reduction.ForkingPickler = ForkingPickler2
+
+
+def f(pipe):
+ "test function to be debugged"
+ print("creating debugger")
+ qdb_test = Qdb(pipe=pipe, redirect_stdio=False, allow_interruptions=True)
+ print("set trace")
+
+ my_var = "Mariano!"
+ qdb_test.set_trace()
+ print("hello world!")
+ for i in range(100000):
+ pass
+ print("good by!")
+
+
def test():
- def f(pipe):
- print("creating debugger")
- qdb = Qdb(pipe=pipe, redirect_stdio=False)
- print("set trace")
-
- my_var = "Mariano!"
- qdb.set_trace()
- print("hello world!")
- print("good by!")
- saraza
-
+ "Create a backend/frontend and time it"
if '--process' in sys.argv:
from multiprocessing import Process, Pipe
- pipe, child_conn = Pipe()
+ front_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
else:
from threading import Thread
- from Queue import Queue
+ from queue import Queue
parent_queue, child_queue = Queue(), Queue()
front_conn = QueuePipe("parent", parent_queue, child_queue)
child_conn = QueuePipe("child", child_queue, parent_queue)
p = Thread(target=f, args=(child_conn,))
-
+
p.start()
import time
class Test(Frontend):
def interaction(self, *args):
print("interaction!", args)
-
+ ##self.do_next()
def exception(self, *args):
print("exception", args)
- #raise RuntimeError("exception %s" % repr(args))
- qdb = Test(front_conn)
- time.sleep(5)
-
- while True:
- print("running...")
- Frontend.run(qdb)
- time.sleep(1)
- print("do_next")
- qdb.do_next()
+ qdb_test = Test(front_conn)
+ time.sleep(1)
+ t0 = time.time()
+
+ print("running...")
+ while front_conn.poll():
+ Frontend.run(qdb_test)
+ qdb_test.do_continue()
p.join()
+ t1 = time.time()
+ print("took", t1 - t0, "seconds")
+ sys.exit(0)
-def connect(host="localhost", port=6000, authkey='secret password'):
- "Connect to a running debugger backend"
-
+def start(host="localhost", port=6000, authkey='secret password'):
+ "Start the CLI server and wait connection from a running debugger backend"
+
address = (host, port)
- from multiprocessing.connection import Client
-
- print("qdb debugger fronted: waiting for connection to", address)
- conn = Client(address, authkey=authkey)
+ from multiprocessing.connection import Listener
+ address = (host, port) # family is deduced to be 'AF_INET'
+ if isinstance(authkey, str):
+ authkey = authkey.encode("utf8")
+ listener = Listener(address, authkey=authkey)
+ print("qdb debugger backend: waiting for connection at", address)
+ conn = listener.accept()
+ print('qdb debugger backend: connected to', listener.last_accepted)
try:
Cli(conn).run()
except EOFError:
@@ -877,13 +1008,13 @@ def connect(host="localhost", port=6000, authkey='secret password'):
def main(host='localhost', port=6000, authkey='secret password'):
- "Debug a script and accept a remote frontend"
-
+ "Debug a script (running under the backend) and connect to remote frontend"
+
if not sys.argv[1:] or sys.argv[1] in ("--help", "-h"):
print("usage: pdb.py scriptfile [arg] ...")
sys.exit(2)
- mainpyfile = sys.argv[1] # Get script filename
+ mainpyfile = sys.argv[1] # Get script filename
if not os.path.exists(mainpyfile):
print('Error:', mainpyfile, 'does not exist')
sys.exit(1)
@@ -893,15 +1024,8 @@ def main(host='localhost', port=6000, authkey='secret password'):
# Replace pdb's dir with script's dir in front of module search path.
sys.path[0] = os.path.dirname(mainpyfile)
- from multiprocessing.connection import Listener
- address = (host, port) # family is deduced to be 'AF_INET'
- listener = Listener(address, authkey=authkey)
- print("qdb debugger backend: waiting for connection at", address)
- conn = listener.accept()
- print('qdb debugger backend: connected to', listener.last_accepted)
-
# create the backend
- qdb = Qdb(conn, redirect_stdio=True, allow_interruptions=True)
+ init(host, port, authkey)
try:
print("running", mainpyfile)
qdb._runscript(mainpyfile)
@@ -911,32 +1035,57 @@ def main(host='localhost', port=6000, authkey='secret password'):
print("The program exited via sys.exit(). Exit status: ", end=' ')
print(sys.exc_info()[1])
raise
- except:
- raise
-
- conn.close()
- listener.close()
+ except Exception:
+ traceback.print_exc()
+ print("Uncaught exception. Entering post mortem debugging")
+ info = sys.exc_info()
+ qdb.post_mortem(info)
+ print("Program terminated!")
+ finally:
+ conn.close()
+ print("qdb debbuger backend: connection closed")
+# "singleton" to store a unique backend per process
qdb = None
-def set_trace(host='localhost', port=6000, authkey='secret password'):
+def init(host='localhost', port=6000, authkey='secret password', redirect=True):
"Simplified interface to debug running programs"
global qdb, listener, conn
-
- from multiprocessing.connection import Listener
+
+ # destroy the debugger if the previous connection is lost (i.e. broken pipe)
+ if qdb and not qdb.ping():
+ qdb.close()
+ qdb = None
+
+ from multiprocessing.connection import Client
# only create it if not currently instantiated
if not qdb:
address = (host, port) # family is deduced to be 'AF_INET'
- listener = Listener(address, authkey=authkey)
- conn = listener.accept()
-
+ print("qdb debugger backend: waiting for connection to", address)
+ if isinstance(authkey, str):
+ authkey = authkey.encode("utf8")
+ conn = Client(address, authkey=authkey)
+ print('qdb debugger backend: connected to', address)
# create the backend
- qdb = Qdb(conn)
+ qdb = Qdb(conn, redirect_stdio=redirect, allow_interruptions=True)
+ # initial hanshake
+ qdb.startup()
+
+
+def set_trace(host='localhost', port=6000, authkey='secret password'):
+ "Simplified interface to start debugging immediately"
+ init(host, port, authkey)
# start debugger backend:
qdb.set_trace()
+def debug(host='localhost', port=6000, authkey='secret password'):
+ "Simplified interface to start debugging immediately (no stop)"
+ init(host, port, authkey)
+ # start debugger backend:
+ qdb.do_debug()
+
def quit():
"Remove trace and quit"
@@ -948,24 +1097,25 @@ def quit():
conn.close()
conn = None
if listener:
- listener.close()
+ listener.close()
listener = None
if __name__ == '__main__':
# When invoked as main program:
- if '--test' in sys.argv:
+ if '--test1' in sys.argv:
test()
# Check environment for configuration parameters:
kwargs = {}
for param in 'host', 'port', 'authkey':
- if 'QDB_%s' % param.upper() in os.environ:
- kwargs[param] = os.environ['QDB_%s' % param.upper()]
+ if 'DBG_%s' % param.upper() in os.environ:
+ kwargs[param] = os.environ['DBG_%s' % param.upper()]
if not sys.argv[1:]:
# connect to a remote debbuger
- connect(**kwargs)
+ start(**kwargs)
else:
# start the debugger on a script
# reimport as global __main__ namespace is destroyed
- import qdb
- qdb.main(**kwargs)
+ import dbg
+ dbg.main(**kwargs)
+
diff --git a/gluon/contrib/populate.py b/gluon/contrib/populate.py
index 92c1fec6..6977e8d5 100644
--- a/gluon/contrib/populate.py
+++ b/gluon/contrib/populate.py
@@ -1,6 +1,7 @@
from __future__ import print_function
+from gluon._compat import pickle, unicodeT
+from gluon.fileutils import open_file
import re
-import cPickle
import random
import datetime
@@ -32,10 +33,10 @@ class Learner:
self.db[item][nextitem] += 1
def save(self, filename):
- cPickle.dump(self.db, open(filename, 'wb'))
+ pickle.dump(self.db, open_file(filename, 'wb'))
def load(self, filename):
- self.loadd(cPickle.load(open(filename, 'rb')))
+ self.loadd(pickle.load(open_file(filename, 'rb')))
def loadd(self, db):
self.db = db
@@ -43,7 +44,7 @@ class Learner:
def generate(self, length=10000, prefix=False):
replacements2 = {' ,': ',', ' \.': '.\n', ' :': ':', ' ;':
';', '\n\s+': '\n'}
- keys = self.db.keys()
+ keys = list(self.db.keys())
key = keys[random.randint(0, len(keys) - 1)]
words = key
words = words.capitalize()
@@ -130,7 +131,7 @@ def populate_generator(table, default=True, compute=False, contents={}):
continue # if user supplied it, let it be.
field = table[fieldname]
- if not isinstance(field.type, (str, unicode)):
+ if not isinstance(field.type, (str, unicodeT)):
continue
elif field.type == 'id':
continue
diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py
index c5e7f3c7..7f6e288b 100644
--- a/gluon/contrib/webclient.py
+++ b/gluon/contrib/webclient.py
@@ -46,6 +46,17 @@ class WebClient(object):
self.default_headers = default_headers
self.sessions = {}
self.session_regex = session_regex and re.compile(session_regex)
+ self.headers = {}
+
+ def _parse_headers_in_cookies(self):
+ self.cookies = {}
+ if 'set-cookie' in self.headers:
+ for item in self.headers['set-cookie'].split(','):
+ cookie = item[:item.find(';')]
+ pos = cookie.find('=')
+ key = cookie[:pos]
+ value = cookie[pos+1:]
+ self.cookies[key.strip()] = value.strip()
def get(self, url, cookies=None, headers=None, auth=None):
return self.post(url, data=None, cookies=cookies,
@@ -149,12 +160,7 @@ class WebClient(object):
else:
raise error
- # parse headers into cookies
- self.cookies = {}
- if 'set-cookie' in self.headers:
- for item in self.headers['set-cookie'].split(','):
- key, value = item[:item.find(';')].split('=')
- self.cookies[key.strip()] = value.strip()
+ self._parse_headers_in_cookies()
# check is a new session id has been issued, symptom of broken session
if self.session_regex is not None:
diff --git a/gluon/dal.py b/gluon/dal.py
index d73720cf..95476a9f 100644
--- a/gluon/dal.py
+++ b/gluon/dal.py
@@ -75,10 +75,10 @@ def _default_validators(db, field):
if field.unique:
requires.insert(0, validators.IS_NOT_IN_DB(db, field))
excluded_fields = ['string', 'upload', 'text', 'password', 'boolean']
- if (field.notnull or field.unique) and not field_type in excluded_fields:
+ if (field.notnull or field.unique) and field_type not in excluded_fields:
requires.insert(0, validators.IS_NOT_EMPTY())
elif not field.notnull and not field.unique and requires:
- requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field in ('string', 'text', 'password') else None)
+ requires[0] = validators.IS_EMPTY_OR(requires[0], null='' if field.type in ('string', 'text', 'password') else None)
return requires
from gluon.serializers import custom_json, xml
diff --git a/gluon/debug.py b/gluon/debug.py
index a4432590..6887f27f 100644
--- a/gluon/debug.py
+++ b/gluon/debug.py
@@ -87,9 +87,9 @@ def communicate(command=None):
return ''.join(result)
-# New debugger implementation using qdb and a web UI
+# New debugger implementation using dbg and a web UI
-import gluon.contrib.qdb as qdb
+import gluon.contrib.dbg as dbg
from threading import RLock
interact_lock = RLock()
@@ -109,11 +109,11 @@ def check_interaction(fn):
return check_fn
-class WebDebugger(qdb.Frontend):
+class WebDebugger(dbg.Frontend):
"""Qdb web2py interface"""
def __init__(self, pipe, completekey='tab', stdin=None, stdout=None):
- qdb.Frontend.__init__(self, pipe)
+ dbg.Frontend.__init__(self, pipe)
self.clear_interaction()
def clear_interaction(self):
@@ -128,7 +128,7 @@ class WebDebugger(qdb.Frontend):
run_lock.acquire()
try:
while self.pipe.poll():
- qdb.Frontend.run(self)
+ dbg.Frontend.run(self)
finally:
run_lock.release()
@@ -149,23 +149,23 @@ class WebDebugger(qdb.Frontend):
@check_interaction
def do_continue(self):
- qdb.Frontend.do_continue(self)
+ dbg.Frontend.do_continue(self)
@check_interaction
def do_step(self):
- qdb.Frontend.do_step(self)
+ dbg.Frontend.do_step(self)
@check_interaction
def do_return(self):
- qdb.Frontend.do_return(self)
+ dbg.Frontend.do_return(self)
@check_interaction
def do_next(self):
- qdb.Frontend.do_next(self)
+ dbg.Frontend.do_next(self)
@check_interaction
def do_quit(self):
- qdb.Frontend.do_quit(self)
+ dbg.Frontend.do_quit(self)
def do_exec(self, statement):
interact_lock.acquire()
@@ -175,22 +175,22 @@ class WebDebugger(qdb.Frontend):
# avoid spurious interaction notifications:
self.set_burst(2)
# execute the statement in the remote debugger:
- return qdb.Frontend.do_exec(self, statement)
+ return dbg.Frontend.do_exec(self, statement)
finally:
interact_lock.release()
# 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)
+front_conn = dbg.QueuePipe("parent", parent_queue, child_queue)
+child_conn = dbg.QueuePipe("child", child_queue, parent_queue)
web_debugger = WebDebugger(front_conn) # frontend
-qdb_debugger = qdb.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend
-dbg = qdb_debugger
+dbg_debugger = dbg.Qdb(pipe=child_conn, redirect_stdio=False, skip=None) # backend
+dbg = dbg_debugger
# enable getting context (stack, globals/locals) at interaction
-qdb_debugger.set_params(dict(call_stack=True, environment=True))
+dbg_debugger.set_params(dict(call_stack=True, environment=True))
import gluon.main
gluon.main.global_settings.debugging = True
diff --git a/gluon/main.py b/gluon/main.py
index a830d7a8..24327a89 100644
--- a/gluon/main.py
+++ b/gluon/main.py
@@ -390,7 +390,11 @@ def wsgibase(environ, responder):
% 'invalid request',
web2py_error='invalid application')
elif not request.is_local and exists(disabled):
- raise HTTP(503, "Temporarily down for maintenance
")
+ five0three = os.path.join(request.folder,'static','503.html')
+ if os.path.exists(five0three):
+ raise HTTP(503, file(five0three, 'r').read())
+ else:
+ raise HTTP(503, "Temporarily down for maintenance
")
# ##################################################
# build missing folders
diff --git a/gluon/rocket.py b/gluon/rocket.py
index c41ac32d..ba13faac 100644
--- a/gluon/rocket.py
+++ b/gluon/rocket.py
@@ -11,7 +11,8 @@ import errno
import socket
import logging
import platform
-from gluon._compat import iteritems, to_bytes, StringIO, urllib_unquote
+from gluon._compat import iteritems, to_bytes, StringIO
+from gluon._compat import urllib_unquote, to_native
# Define Constants
VERSION = '1.2.6'
@@ -1661,7 +1662,7 @@ class WSGIWorker(Worker):
try:
peercert = conn.socket.getpeercert(binary_form=True)
environ['SSL_CLIENT_RAW_CERT'] = \
- peercert and ssl.DER_cert_to_PEM_cert(peercert)
+ peercert and to_native(ssl.DER_cert_to_PEM_cert(peercert))
except Exception:
print(sys.exc_info()[1])
else:
diff --git a/gluon/shell.py b/gluon/shell.py
index 9d93f1a6..b6c8f27b 100644
--- a/gluon/shell.py
+++ b/gluon/shell.py
@@ -254,7 +254,7 @@ def run(
die(errmsg)
if f:
- exec('print %s()' % f, _env)
+ exec('print( %s())' % f, _env)
return
_env.update(exec_pythonrc())
diff --git a/gluon/tests/test_scheduler.py b/gluon/tests/test_scheduler.py
index b473fe7b..ee1270fb 100644
--- a/gluon/tests/test_scheduler.py
+++ b/gluon/tests/test_scheduler.py
@@ -601,7 +601,8 @@ class testForSchedulerRunnerBase(BaseTestScheduler):
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
os.unlink(fdest)
additional_files = [
- os.path.join(current.request.folder, 'private', 'demo8.pholder')
+ os.path.join(current.request.folder, 'private', 'demo8.pholder'),
+ os.path.join(current.request.folder, 'views', 'issue_1485_2.html'),
]
for f in additional_files:
try:
@@ -609,6 +610,12 @@ class testForSchedulerRunnerBase(BaseTestScheduler):
except:
pass
+ def writeview(self, content, dest=None):
+ from gluon import current
+ fdest = os.path.join(current.request.folder, 'views', dest)
+ with open(fdest, 'w') as q:
+ q.write(content)
+
def writefunction(self, content, initlines=None):
from gluon import current
fdest = os.path.join(current.request.folder, 'models', 'scheduler.py')
@@ -620,6 +627,9 @@ from gluon.scheduler import Scheduler
db_dal = os.path.abspath(os.path.join(request.folder, '..', '..', 'dummy2.db'))
sched_dal = DAL('sqlite://%s' % db_dal, folder=os.path.dirname(db_dal))
sched = Scheduler(sched_dal, max_empty_runs=15, migrate=False, heartbeat=1)
+def termination():
+ sched.terminate()
+ sched_dal.commit()
"""
with open(fdest, 'w') as q:
q.write(initlines)
@@ -699,10 +709,11 @@ def demo4():
timeout1 = s.queue_task('demo4', timeout=5)
timeout2 = s.queue_task('demo4')
progress = s.queue_task('demo6', sync_output=2)
+ termination = s.queue_task('termination')
self.db.commit()
self.writefunction(r"""
def demo3():
- time.sleep(15)
+ time.sleep(3)
print(1/0)
return None
@@ -712,7 +723,7 @@ def demo4():
return dict(a=1, b=2)
def demo5():
- time.sleep(15)
+ time.sleep(3)
print("I'm printing something")
rtn = dict(a=1, b=2)
@@ -758,6 +769,7 @@ def demo6():
immediate = s.queue_task('demo1', ['a', 'b'], dict(c=1, d=2), immediate=True)
env = s.queue_task('demo7')
drift = s.queue_task('demo1', ['a', 'b'], dict(c=1, d=2), period=93, prevent_drift=True)
+ termination = s.queue_task('termination')
self.db.commit()
self.writefunction(r"""
def demo1(*args,**vars):
@@ -844,27 +856,41 @@ def demo8():
]
self.exec_asserts(res, 'FAILED_CONSECUTIVE')
- def testHugeResult(self):
+ def testRegressions(self):
s = Scheduler(self.db)
huge_result = s.queue_task('demo10', retry_failed=1, period=1)
+ issue_1485 = s.queue_task('issue_1485')
+ termination = s.queue_task('termination')
self.db.commit()
self.writefunction(r"""
def demo10():
res = 'a' * 99999
return dict(res=res)
+
+def issue_1485():
+ return response.render('issue_1485.html', dict(variable='abc'))
""")
+ self.writeview(r"""{{=variable}}""", 'issue_1485.html')
ret = self.exec_sched()
# process finished just fine
self.assertEqual(ret, 0)
# huge_result - checks
- task = s.task_status(huge_result.id, output=True)
+ task_huge = s.task_status(huge_result.id, output=True)
res = [
- ("task status completed", task.scheduler_task.status == 'COMPLETED'),
- ("task times_run is 1", task.scheduler_task.times_run == 1),
- ("result is the correct one", task.result == dict(res='a' * 99999))
+ ("task status completed", task_huge.scheduler_task.status == 'COMPLETED'),
+ ("task times_run is 1", task_huge.scheduler_task.times_run == 1),
+ ("result is the correct one", task_huge.result == dict(res='a' * 99999))
]
self.exec_asserts(res, 'HUGE_RESULT')
+ task_issue_1485 = s.task_status(issue_1485.id, output=True)
+ res = [
+ ("task status completed", task_issue_1485.scheduler_task.status == 'COMPLETED'),
+ ("task times_run is 1", task_issue_1485.scheduler_task.times_run == 1),
+ ("result is the correct one", task_issue_1485.result == 'abc')
+ ]
+ self.exec_asserts(res, 'issue_1485')
+
if __name__ == '__main__':
unittest.main()
diff --git a/gluon/tests/test_tools.py b/gluon/tests/test_tools.py
index 1fdf80f1..083d83af 100644
--- a/gluon/tests/test_tools.py
+++ b/gluon/tests/test_tools.py
@@ -525,28 +525,47 @@ class TestAuth(unittest.TestCase):
self.auth.define_tables(username=True, signature=False)
self.db.define_table('t0', Field('tt'), self.auth.signature)
self.auth.enable_record_versioning(self.db)
+ self.auth.settings.registration_requires_verification = False
+ self.auth.settings.registration_requires_approval = False
# Create a user
- self.auth.get_or_create_user(dict(first_name='Bart',
- last_name='Simpson',
- username='bart',
- email='bart@simpson.com',
- password='bart_password',
- registration_key='bart',
- registration_id=''
- ),
- login=False)
- self.db.commit()
- self.assertFalse(self.auth.is_logged_in())
- # self.auth.settings.registration_requires_verification = False
- # self.auth.settings.registration_requires_approval = False
+ # Note: get_or_create_user() doesn't seems to create user properly it better to use register_bare() and
+ # prevent login_bare() test from succeed. db insert the user manually not properly work either.
+ # Not working
+ # self.auth.get_or_create_user(dict(first_name='Bart',
+ # last_name='Simpson',
+ # username='bart',
+ # email='bart@simpson.com',
+ # password='bart_password',
+ # # registration_key=None,
+ # #registration_id='bart@simpson.com'
+ # ),
+ # login=False)
+ # Not working
+ # self.db.auth_user.insert(first_name='Bart',
+ # last_name='Simpson',
+ # username='bart',
+ # email='bart@simpson.com',
+ # password='bart_password')
+ # self.db.commit()
+ self.auth.register_bare(first_name='Bart',
+ last_name='Simpson',
+ username='bart',
+ email='bart@simpson.com',
+ password='bart_password')
def test_assert_setup(self):
- self.assertEqual(self.db(self.db.auth_user.username == 'bart').select().first()['username'], 'bart')
self.assertTrue('auth_user' in self.db)
self.assertTrue('auth_group' in self.db)
self.assertTrue('auth_membership' in self.db)
self.assertTrue('auth_permission' in self.db)
self.assertTrue('auth_event' in self.db)
+ bart_record = self.db(self.db.auth_user.username == 'bart').select().first()
+ self.assertEqual(bart_record['username'], 'bart')
+ self.assertEqual(bart_record['registration_key'], '')
+ bart_id = self.db(self.db.auth_user.username == 'bart').select().first().id
+ bart_group_id = self.db(self.db.auth_group.role == 'user_{0}'.format(bart_id)).select().first().id
+ self.assertTrue(self.db((self.db.auth_membership.group_id == bart_group_id) &
+ (self.db.auth_membership.user_id == bart_id)).select().first())
# Just calling many form functions
def test_basic_blank_forms(self):
@@ -652,16 +671,31 @@ class TestAuth(unittest.TestCase):
# TODO: def test_login_user(self):
# TODO: def test__get_login_settings(self):
- # login_bare() seems broken see my post on web2py-developpers
- # commented for now
- # def test_login_bare(self):
- # # The following test case should succeed but failed as I never received the user record but False
- # self.auth.login_bare(username='bart', password='bart_password')
- # self.assertTrue(self.auth.is_logged_in())
- # # Failing login because bad_password
- # self.assertEqual(self.auth.login_bare(username='bart', password='wrong_password'), False)
- # self.auth.logout_bare()
- # self.db.auth_user.truncate()
+ def test_login_bare(self):
+ self.auth.login_bare(username='bart', password='bart_password')
+ self.assertTrue(self.auth.is_logged_in())
+ self.auth.logout_bare()
+ # Failing login because wrong_password
+ self.assertFalse(self.auth.login_bare(username='bart', password='wrong_password'))
+ # NOTE : The following failed for some reason, but I can't find out why
+ # self.auth = Auth(self.db)
+ # self.auth.define_tables(username=False, signature=False)
+ # self.auth.settings.registration_requires_verification = False
+ # self.auth.settings.registration_requires_approval = False
+ # self.auth.register_bare(first_name='Omer',
+ # last_name='Simpson',
+ # # no username field passed, failed with :
+ # # ValueError('register_bare: userfield not provided or invalid')
+ # # Or
+ # # username='omer',
+ # # Or
+ # # username='omer@simpson.com',
+ # # In either previous cases, it failed with :
+ # # self.assertTrue(self.auth.is_logged_in()) AssertionError: False is not true
+ # email='omer@simpson.com',
+ # password='omer_password')
+ # self.auth.login_bare(username='omer@sympson.com', password='omer_password')
+ # self.assertTrue(self.auth.is_logged_in())
def test_register_bare(self):
# corner case empty register call register_bare without args
@@ -817,7 +851,10 @@ class TestAuth(unittest.TestCase):
self.myassertRaisesRegex(HTTP, "400*", self.auth.allows_jwt)
# TODO: def test_requires(self):
- # TODO: def test_requires_login(self):
+
+ # def test_login(self):
+ # Basic testing above in "test_basic_blank_forms()" could be refined here
+
# TODO: def test_requires_login_or_token(self):
# TODO: def test_requires_membership(self):
# TODO: def test_requires_permission(self):
diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py
index 4a3ef3bf..adac0f28 100644
--- a/gluon/tests/test_web.py
+++ b/gluon/tests/test_web.py
@@ -49,6 +49,24 @@ def stopwebserver():
webserverprocess.terminate()
+class Cookie(unittest.TestCase):
+ def testParseMultipleEquals(self):
+ """ Test for issue #1500.
+ Ensure that a cookie containing one or more '=' is correctly parsed
+ """
+ client = WebClient()
+ client.headers['set-cookie'] = "key = value with one =;"
+ client._parse_headers_in_cookies()
+ self.assertIn("key", client.cookies)
+ self.assertEqual(client.cookies['key'], "value with one =")
+
+ client.headers['set-cookie'] = "key = value with one = and another one =;"
+ client._parse_headers_in_cookies()
+ client._parse_headers_in_cookies()
+ self.assertIn("key", client.cookies)
+ self.assertEqual(client.cookies['key'], "value with one = and another one =")
+
+
class LiveTest(unittest.TestCase):
@classmethod