From a3cca621dccbb7389263947a4f6076015d2d2924 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 17 Sep 2018 16:58:50 +0200 Subject: [PATCH 1/4] + IPython added --- rpdb/__init__.py | 79 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/rpdb/__init__.py b/rpdb/__init__.py index c79c622..18a5f49 100644 --- a/rpdb/__init__.py +++ b/rpdb/__init__.py @@ -10,9 +10,11 @@ import sys import traceback from functools import partial +from IPython.core.debugger import Pdb DEFAULT_ADDR = "127.0.0.1" DEFAULT_PORT = 4444 +_IPython = False class FileObjectWrapper(object): @@ -97,14 +99,83 @@ def do_EOF(self, arg): self.shutdown() -def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT, frame=None): +class IRpdb(Pdb): + + def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT): + """Initialize the socket and initialize pdb.""" + + # Backup stdin and stdout before replacing them by the socket handle + self.old_stdout = sys.stdout + self.old_stdin = sys.stdin + self.port = port + + # Open a 'reusable' socket to let the webapp reload on the same port + self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) + self.skt.bind((addr, port)) + self.skt.listen(1) + + # Writes to stdout are forbidden in mod_wsgi environments + try: + sys.stderr.write("pdb is running on %s:%d\n" + % self.skt.getsockname()) + except IOError: + pass + + (clientsocket, address) = self.skt.accept() + handle = clientsocket.makefile('rw') + Pdb.__init__(self, completekey='tab', + stdin=FileObjectWrapper(handle, self.old_stdin), + stdout=FileObjectWrapper(handle, self.old_stdin)) + sys.stdout = sys.stdin = handle + self.handle = handle + OCCUPIED.claim(port, sys.stdout) + + def shutdown(self): + """Revert stdin and stdout, close the socket.""" + sys.stdout = self.old_stdout + sys.stdin = self.old_stdin + self.handle.close() + OCCUPIED.unclaim(self.port) + self.skt.shutdown(socket.SHUT_RDWR) + self.skt.close() + + def do_continue(self, arg): + """Clean-up and do underlying continue.""" + try: + return Pdb.do_continue(self, arg) + finally: + self.shutdown() + + do_c = do_cont = do_continue + + def do_quit(self, arg): + """Clean-up and do underlying quit.""" + try: + return Pdb.do_quit(self, arg) + finally: + self.shutdown() + + do_q = do_exit = do_quit + + def do_EOF(self, arg): + """Clean-up and do underlying EOF.""" + try: + return Pdb.do_EOF(self, arg) + finally: + self.shutdown() + + +def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT, frame=None, IPython=False): """Wrapper function to keep the same import x; x.set_trace() interface. We catch all the possible exceptions from pdb and cleanup. """ try: - debugger = Rpdb(addr=addr, port=port) + _IPython = IPython + debugger = IRpdb(addr=addr, port=port) if IPython else Rpdb( + addr=addr, port=port) except socket.error: if OCCUPIED.is_claimed(port, sys.stdout): # rpdb is already on this port - good enough, let it go on: @@ -129,7 +200,8 @@ def handle_trap(addr=DEFAULT_ADDR, port=DEFAULT_PORT): def post_mortem(addr=DEFAULT_ADDR, port=DEFAULT_PORT): - debugger = Rpdb(addr=addr, port=port) + debugger = IRpdb(addr=addr, port=port) if _IPython else Rpdb( + addr=addr, port=port) type, value, tb = sys.exc_info() traceback.print_exc() debugger.reset() @@ -166,6 +238,7 @@ def unclaim(self, port): del self.claims[port] self.lock.release() + # {port: sys.stdout} pairs to track recursive rpdb invocation on same port. # This scheme doesn't interfere with recursive invocations on separate ports - # useful, eg, for concurrently debugging separate threads. From f2aaff56abd67cd9d2f325e6b17f8dfd3926c272 Mon Sep 17 00:00:00 2001 From: Isaac Date: Mon, 17 Sep 2018 17:07:48 +0200 Subject: [PATCH 2/4] + Readme updated --- README.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.rst b/README.rst index aed483b..93adc02 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,10 @@ handler. By default it opens the debugger on port 4444:: import rpdb; rpdb.set_trace() +For using IPython debugger + + import rpdb; rpdb.set_trace(IPython=True) + But you can change that by simply instantiating Rpdb manually:: import rpdb From abfd5172a2eebce8ad11cb7ce94c164fc04ba671 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 20 Sep 2018 01:07:27 +0200 Subject: [PATCH 3/4] + Fix PR + snake case use + refactor code to one class + import protected with try/catch + thank for all the comments --- rpdb/__init__.py | 110 +++++++++++++---------------------------------- 1 file changed, 30 insertions(+), 80 deletions(-) diff --git a/rpdb/__init__.py b/rpdb/__init__.py index 18a5f49..081e940 100644 --- a/rpdb/__init__.py +++ b/rpdb/__init__.py @@ -10,14 +10,19 @@ import sys import traceback from functools import partial -from IPython.core.debugger import Pdb + +try: + from IPython.core.debugger import Pdb + IPYTHON_ENABLE = True +except ImportError: + IPYTHON_ENABLE = False DEFAULT_ADDR = "127.0.0.1" DEFAULT_PORT = 4444 -_IPython = False class FileObjectWrapper(object): + def __init__(self, fileobject, stdio): self._obj = fileobject self._io = stdio @@ -32,9 +37,9 @@ def __getattr__(self, attr): return attr -class Rpdb(pdb.Pdb): +class Rpdb: - def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT): + def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT, ipython=False): """Initialize the socket and initialize pdb.""" # Backup stdin and stdout before replacing them by the socket handle @@ -42,6 +47,8 @@ def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT): self.old_stdin = sys.stdin self.port = port + self.debugger = Pdb if ipython and IPYTHON_ENABLE else pdb.Pdb + # Open a 'reusable' socket to let the webapp reload on the same port self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) @@ -57,9 +64,9 @@ def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT): (clientsocket, address) = self.skt.accept() handle = clientsocket.makefile('rw') - pdb.Pdb.__init__(self, completekey='tab', - stdin=FileObjectWrapper(handle, self.old_stdin), - stdout=FileObjectWrapper(handle, self.old_stdin)) + self.debugger.__init__(self, completekey='tab', + stdin=FileObjectWrapper(handle, self.old_stdin), + stdout=FileObjectWrapper(handle, self.old_stdin)) sys.stdout = sys.stdin = handle self.handle = handle OCCUPIED.claim(port, sys.stdout) @@ -76,7 +83,7 @@ def shutdown(self): def do_continue(self, arg): """Clean-up and do underlying continue.""" try: - return pdb.Pdb.do_continue(self, arg) + return self.debugger.do_continue(self, arg) finally: self.shutdown() @@ -85,7 +92,7 @@ def do_continue(self, arg): def do_quit(self, arg): """Clean-up and do underlying quit.""" try: - return pdb.Pdb.do_quit(self, arg) + return self.debugger.do_quit(self, arg) finally: self.shutdown() @@ -94,88 +101,30 @@ def do_quit(self, arg): def do_EOF(self, arg): """Clean-up and do underlying EOF.""" try: - return pdb.Pdb.do_EOF(self, arg) - finally: - self.shutdown() - - -class IRpdb(Pdb): - - def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT): - """Initialize the socket and initialize pdb.""" - - # Backup stdin and stdout before replacing them by the socket handle - self.old_stdout = sys.stdout - self.old_stdin = sys.stdin - self.port = port - - # Open a 'reusable' socket to let the webapp reload on the same port - self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.skt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) - self.skt.bind((addr, port)) - self.skt.listen(1) - - # Writes to stdout are forbidden in mod_wsgi environments - try: - sys.stderr.write("pdb is running on %s:%d\n" - % self.skt.getsockname()) - except IOError: - pass - - (clientsocket, address) = self.skt.accept() - handle = clientsocket.makefile('rw') - Pdb.__init__(self, completekey='tab', - stdin=FileObjectWrapper(handle, self.old_stdin), - stdout=FileObjectWrapper(handle, self.old_stdin)) - sys.stdout = sys.stdin = handle - self.handle = handle - OCCUPIED.claim(port, sys.stdout) - - def shutdown(self): - """Revert stdin and stdout, close the socket.""" - sys.stdout = self.old_stdout - sys.stdin = self.old_stdin - self.handle.close() - OCCUPIED.unclaim(self.port) - self.skt.shutdown(socket.SHUT_RDWR) - self.skt.close() - - def do_continue(self, arg): - """Clean-up and do underlying continue.""" - try: - return Pdb.do_continue(self, arg) + return self.debugger.do_EOF(self, arg) finally: self.shutdown() - do_c = do_cont = do_continue - def do_quit(self, arg): - """Clean-up and do underlying quit.""" - try: - return Pdb.do_quit(self, arg) - finally: - self.shutdown() +def get_debugger_class(base): + class Debugger(base, Rpdb): - do_q = do_exit = do_quit + def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT, ipython=False): + Rpdb.__init__(self, addr, port, ipython) - def do_EOF(self, arg): - """Clean-up and do underlying EOF.""" - try: - return Pdb.do_EOF(self, arg) - finally: - self.shutdown() + return Debugger -def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT, frame=None, IPython=False): +def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT, frame=None, ipython=False): """Wrapper function to keep the same import x; x.set_trace() interface. We catch all the possible exceptions from pdb and cleanup. """ try: - _IPython = IPython - debugger = IRpdb(addr=addr, port=port) if IPython else Rpdb( - addr=addr, port=port) + debugger_class = Pdb if ipython and IPYTHON_ENABLE else pdb.Pdb + Rpdb = get_debugger_class(debugger_class) + debugger = Rpdb(addr=addr, port=port, ipython=ipython) except socket.error: if OCCUPIED.is_claimed(port, sys.stdout): # rpdb is already on this port - good enough, let it go on: @@ -199,9 +148,10 @@ def handle_trap(addr=DEFAULT_ADDR, port=DEFAULT_PORT): signal.signal(signal.SIGTRAP, partial(_trap_handler, addr, port)) -def post_mortem(addr=DEFAULT_ADDR, port=DEFAULT_PORT): - debugger = IRpdb(addr=addr, port=port) if _IPython else Rpdb( - addr=addr, port=port) +def post_mortem(addr=DEFAULT_ADDR, port=DEFAULT_PORT, ipython=False): + debugger_class = Pdb if ipython and IPYTHON_ENABLE else pdb.Pdb + Rpdb = get_debugger_class(debugger_class) + debugger = Rpdb(addr=addr, port=port) type, value, tb = sys.exc_info() traceback.print_exc() debugger.reset() From 179fdcbd2017fc33d3eb3ede18ffec32937218b1 Mon Sep 17 00:00:00 2001 From: Isaac Date: Thu, 20 Sep 2018 09:24:59 +0200 Subject: [PATCH 4/4] + Fix PR2 --- README.rst | 4 ++-- rpdb/__init__.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 93adc02..190f567 100644 --- a/README.rst +++ b/README.rst @@ -6,9 +6,9 @@ handler. By default it opens the debugger on port 4444:: import rpdb; rpdb.set_trace() -For using IPython debugger +For using IPython debugger (if not available will revert into Pdb) - import rpdb; rpdb.set_trace(IPython=True) + import rpdb; rpdb.set_trace(ipython=True) But you can change that by simply instantiating Rpdb manually:: diff --git a/rpdb/__init__.py b/rpdb/__init__.py index 081e940..e3f3d1f 100644 --- a/rpdb/__init__.py +++ b/rpdb/__init__.py @@ -12,10 +12,10 @@ from functools import partial try: - from IPython.core.debugger import Pdb - IPYTHON_ENABLE = True + from IPython.core.debugger import IPdb + ipython_available = True except ImportError: - IPYTHON_ENABLE = False + ipython_available = False DEFAULT_ADDR = "127.0.0.1" DEFAULT_PORT = 4444 @@ -47,7 +47,7 @@ def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT, ipython=False): self.old_stdin = sys.stdin self.port = port - self.debugger = Pdb if ipython and IPYTHON_ENABLE else pdb.Pdb + self.debugger = IPdb if ipython and ipython_available else pdb.Pdb # Open a 'reusable' socket to let the webapp reload on the same port self.skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -122,7 +122,7 @@ def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT, frame=None, ipython=False): """ try: - debugger_class = Pdb if ipython and IPYTHON_ENABLE else pdb.Pdb + debugger_class = IPdb if ipython and ipython_available else pdb.Pdb Rpdb = get_debugger_class(debugger_class) debugger = Rpdb(addr=addr, port=port, ipython=ipython) except socket.error: @@ -149,7 +149,7 @@ def handle_trap(addr=DEFAULT_ADDR, port=DEFAULT_PORT): def post_mortem(addr=DEFAULT_ADDR, port=DEFAULT_PORT, ipython=False): - debugger_class = Pdb if ipython and IPYTHON_ENABLE else pdb.Pdb + debugger_class = Pdb if ipython and ipython_available else pdb.IPdb Rpdb = get_debugger_class(debugger_class) debugger = Rpdb(addr=addr, port=port) type, value, tb = sys.exc_info()