RemotePdb   A
last analyzed

Complexity

Total Complexity 13

Size/Duplication

Total Lines 69
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
c 6
b 0
f 0
dl 0
loc 69
rs 10
wmc 13

5 Methods

Rating   Name   Duplication   Size   Complexity  
A set_quit() 0 2 1
A do_quit() 0 4 1
B __init__() 0 26 4
A __restore() 0 7 3
A set_trace() 0 8 4
1
from __future__ import print_function
2
3
import errno
4
import logging
5
import re
6
import socket
7
import sys
8
9
from pdb import Pdb
10
11
__version__ = "1.2.0"
12
13
PY3 = sys.version_info[0] == 3
14
15
16
def cry(message, stderr=sys.__stderr__):
17
    logging.critical(message)
18
    print(message, file=stderr)
19
    stderr.flush()
20
21
22
class LF2CRLF_FileWrapper(object):
23
    def __init__(self, fh):
24
        self.stream = fh
25
        self.read = fh.read
26
        self.readline = fh.readline
27
        self.readlines = fh.readlines
28
        self.close = fh.close
29
        self.flush = fh.flush
30
        self.fileno = fh.fileno
31
32
    @property
33
    def encoding(self):
34
        return self.stream.encoding
35
36
    def __iter__(self):
37
        return self.stream.__iter__()
38
39
    def write(self, data, nl_rex=re.compile("\r?\n")):
40
        self.stream.write(nl_rex.sub("\r\n", data))
41
        # we have to explicitly flush, and unfortunately we cannot just disable buffering because on Python 3 text
42
        # streams line buffering seems the minimum and on Windows line buffering doesn't work properly because we
43
        # write unix-style line endings
44
        self.stream.flush()
45
46
    def writelines(self, lines, nl_rex=re.compile("\r?\n")):
47
        self.stream.writelines(nl_rex.sub("\r\n", line) for line in lines)
48
        self.stream.flush()
49
50
51
class RemotePdb(Pdb):
52
    """
53
    This will run pdb as a ephemeral telnet service. Once you connect no one
54
    else can connect. On construction this object will block execution till a
55
    client has connected.
56
57
    Based on https://github.com/tamentis/rpdb I think ...
58
59
    To use this::
60
61
        RemotePdb(host='0.0.0.0', port=4444).set_trace()
62
63
    Then run: telnet 127.0.0.1 4444
64
    """
65
    active_instance = None
66
67
    def __init__(self, host, port, patch_stdstreams=False):
68
        listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
69
        listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
70
        listen_socket.bind((host, port))
71
        cry("RemotePdb session open at %s:%s, waiting for connection ..." % listen_socket.getsockname())
72
        listen_socket.listen(1)
73
        connection, address = listen_socket.accept()
74
        cry("RemotePdb accepted connection from %s." % repr(address))
75
        if PY3:
76
            self.handle = LF2CRLF_FileWrapper(connection.makefile('rw'))
77
        else:
78
            self.handle = LF2CRLF_FileWrapper(connection.makefile())
79
        Pdb.__init__(self, completekey='tab', stdin=self.handle, stdout=self.handle)
80
        self.backup = []
81
        if patch_stdstreams:
82
            for name in (
83
                    'stderr',
84
                    'stdout',
85
                    '__stderr__',
86
                    '__stdout__',
87
                    'stdin',
88
                    '__stdin__',
89
            ):
90
                self.backup.append((name, getattr(sys, name)))
91
                setattr(sys, name, self.handle)
92
        RemotePdb.active_instance = self
93
94
    def __restore(self):
95
        if self.backup:
96
            cry('Restoring streams: %s ...' % self.backup)
97
        for name, fh in self.backup:
98
            setattr(sys, name, fh)
99
        self.handle.close()
100
        RemotePdb.active_instance = None
101
102
    def do_quit(self, arg):
103
        self.__restore()
104
        self.set_quit()
105
        return 1
106
107
    do_q = do_exit = do_quit
108
109
    def set_trace(self, frame=None):
110
        if frame is None:
111
            frame = sys._getframe().f_back
112
        try:
113
            Pdb.set_trace(self, frame)
114
        except IOError as exc:
115
            if exc.errno != errno.ECONNRESET:
116
                raise
117
118
    def set_quit(self):
119
        sys.settrace(None)
120
121
122
def set_trace(host='127.0.0.1', port=0, patch_stdstreams=False):
123
    """
124
    Opens a remote PDB on first available port.
125
    """
126
    rdb = RemotePdb(host=host, port=port, patch_stdstreams=patch_stdstreams)
127
    rdb.set_trace(frame=sys._getframe().f_back)
128