Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added IPython debugger #26

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That should be lower-cased too.


But you can change that by simply instantiating Rpdb manually::

import rpdb
Expand Down
45 changes: 34 additions & 11 deletions rpdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,18 @@
import traceback
from functools import partial

try:
from IPython.core.debugger import Pdb
IPYTHON_ENABLE = True
except ImportError:
IPYTHON_ENABLE = False
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not ENABLE as much as it is AVAILABLE, also I don't think this should be all upper-cased since this is more variable than it is constant.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I would probably import it as IPdb to avoid confusion lower in the file.


DEFAULT_ADDR = "127.0.0.1"
DEFAULT_PORT = 4444


class FileObjectWrapper(object):

def __init__(self, fileobject, stdio):
self._obj = fileobject
self._io = stdio
Expand All @@ -30,16 +37,18 @@ 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
self.old_stdout = sys.stdout
self.old_stdin = sys.stdin
self.port = port

self.debugger = Pdb if ipython and IPYTHON_ENABLE else pdb.Pdb
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone has ipython == True but the check variable is False, this is silently going to revert to Pdb, I'm okay with that, but I think it should be in the README.


# 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)
Expand All @@ -55,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)
Expand All @@ -74,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()

Expand All @@ -83,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()

Expand All @@ -92,19 +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)
return self.debugger.do_EOF(self, arg)
finally:
self.shutdown()


def set_trace(addr=DEFAULT_ADDR, port=DEFAULT_PORT, frame=None):
def get_debugger_class(base):
class Debugger(base, Rpdb):

def __init__(self, addr=DEFAULT_ADDR, port=DEFAULT_PORT, ipython=False):
Rpdb.__init__(self, addr, port, ipython)

return Debugger


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)
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:
Expand All @@ -128,7 +148,9 @@ 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):
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()
Expand Down Expand Up @@ -166,6 +188,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.
Expand Down