GvR's rpc patch
This commit is contained in:
parent
38d53451b7
commit
5d2af63cc3
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import bdb
|
||||
import types
|
||||
import traceback
|
||||
from Tkinter import *
|
||||
from WindowList import ListedToplevel
|
||||
|
@ -7,20 +8,66 @@ from WindowList import ListedToplevel
|
|||
import StackViewer
|
||||
|
||||
|
||||
class Debugger(bdb.Bdb):
|
||||
class Idb(bdb.Bdb):
|
||||
|
||||
def __init__(self, gui):
|
||||
self.gui = gui
|
||||
bdb.Bdb.__init__(self)
|
||||
|
||||
def user_line(self, frame):
|
||||
# get the currently executing function
|
||||
co_filename = frame.f_code.co_filename
|
||||
co_name = frame.f_code.co_name
|
||||
try:
|
||||
func = frame.f_locals[co_name]
|
||||
if getattr(func, "DebuggerStepThrough", 0):
|
||||
print "XXXX DEBUGGER STEPPING THROUGH"
|
||||
self.set_step()
|
||||
return
|
||||
except:
|
||||
pass
|
||||
if co_filename in ('rpc.py', '<string>'):
|
||||
self.set_step()
|
||||
return
|
||||
if co_filename.endswith('threading.py'):
|
||||
self.set_step()
|
||||
return
|
||||
message = self.__frame2message(frame)
|
||||
self.gui.interaction(message, frame)
|
||||
|
||||
def user_exception(self, frame, info):
|
||||
message = self.__frame2message(frame)
|
||||
self.gui.interaction(message, frame, info)
|
||||
|
||||
def __frame2message(self, frame):
|
||||
code = frame.f_code
|
||||
filename = code.co_filename
|
||||
lineno = frame.f_lineno
|
||||
basename = os.path.basename(filename)
|
||||
message = "%s:%s" % (basename, lineno)
|
||||
if code.co_name != "?":
|
||||
message = "%s: %s()" % (message, code.co_name)
|
||||
return message
|
||||
|
||||
|
||||
class Debugger:
|
||||
|
||||
interacting = 0
|
||||
|
||||
vstack = vsource = vlocals = vglobals = None
|
||||
|
||||
def __init__(self, pyshell):
|
||||
bdb.Bdb.__init__(self)
|
||||
def __init__(self, pyshell, idb=None):
|
||||
if idb is None:
|
||||
idb = Idb(self)
|
||||
self.pyshell = pyshell
|
||||
self.idb = idb
|
||||
self.make_gui()
|
||||
|
||||
def canonic(self, filename):
|
||||
# Canonicalize filename -- called by Bdb
|
||||
return os.path.normcase(os.path.abspath(filename))
|
||||
def run(self, *args):
|
||||
try:
|
||||
self.interacting = 1
|
||||
return self.idb.run(*args)
|
||||
finally:
|
||||
self.interacting = 0
|
||||
|
||||
def close(self, event=None):
|
||||
if self.interacting:
|
||||
|
@ -31,24 +78,6 @@ class Debugger(bdb.Bdb):
|
|||
self.pyshell.close_debugger()
|
||||
self.top.destroy()
|
||||
|
||||
def run(self, *args):
|
||||
try:
|
||||
self.interacting = 1
|
||||
return apply(bdb.Bdb.run, (self,) + args)
|
||||
finally:
|
||||
self.interacting = 0
|
||||
|
||||
def user_line(self, frame):
|
||||
self.interaction(frame)
|
||||
|
||||
def user_return(self, frame, rv):
|
||||
# XXX show rv?
|
||||
##self.interaction(frame)
|
||||
pass
|
||||
|
||||
def user_exception(self, frame, info):
|
||||
self.interaction(frame, info)
|
||||
|
||||
def make_gui(self):
|
||||
pyshell = self.pyshell
|
||||
self.flist = pyshell.flist
|
||||
|
@ -128,16 +157,8 @@ class Debugger(bdb.Bdb):
|
|||
|
||||
frame = None
|
||||
|
||||
def interaction(self, frame, info=None):
|
||||
def interaction(self, message, frame, info=None):
|
||||
self.frame = frame
|
||||
code = frame.f_code
|
||||
file = code.co_filename
|
||||
base = os.path.basename(file)
|
||||
lineno = frame.f_lineno
|
||||
#
|
||||
message = "%s:%s" % (base, lineno)
|
||||
if code.co_name != "?":
|
||||
message = "%s: %s()" % (message, code.co_name)
|
||||
self.status.configure(text=message)
|
||||
#
|
||||
if info:
|
||||
|
@ -160,7 +181,7 @@ class Debugger(bdb.Bdb):
|
|||
#
|
||||
sv = self.stackviewer
|
||||
if sv:
|
||||
stack, i = self.get_stack(self.frame, tb)
|
||||
stack, i = self.idb.get_stack(self.frame, tb)
|
||||
sv.load_stack(stack, i)
|
||||
#
|
||||
self.show_variables(1)
|
||||
|
@ -184,32 +205,34 @@ class Debugger(bdb.Bdb):
|
|||
frame = self.frame
|
||||
if not frame:
|
||||
return
|
||||
filename, lineno = self.__frame2fileline(frame)
|
||||
if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
|
||||
self.flist.gotofileline(filename, lineno)
|
||||
|
||||
def __frame2fileline(self, frame):
|
||||
code = frame.f_code
|
||||
file = code.co_filename
|
||||
filename = code.co_filename
|
||||
lineno = frame.f_lineno
|
||||
if file[:1] + file[-1:] != "<>" and os.path.exists(file):
|
||||
edit = self.flist.open(file)
|
||||
if edit:
|
||||
edit.gotoline(lineno)
|
||||
return filename, lineno
|
||||
|
||||
def cont(self):
|
||||
self.set_continue()
|
||||
self.idb.set_continue()
|
||||
self.root.quit()
|
||||
|
||||
def step(self):
|
||||
self.set_step()
|
||||
self.idb.set_step()
|
||||
self.root.quit()
|
||||
|
||||
def next(self):
|
||||
self.set_next(self.frame)
|
||||
self.idb.set_next(self.frame)
|
||||
self.root.quit()
|
||||
|
||||
def ret(self):
|
||||
self.set_return(self.frame)
|
||||
self.idb.set_return(self.frame)
|
||||
self.root.quit()
|
||||
|
||||
def quit(self):
|
||||
self.set_quit()
|
||||
self.idb.set_quit()
|
||||
self.root.quit()
|
||||
|
||||
stackviewer = None
|
||||
|
@ -219,7 +242,7 @@ class Debugger(bdb.Bdb):
|
|||
self.stackviewer = sv = StackViewer.StackViewer(
|
||||
self.fstack, self.flist, self)
|
||||
if self.frame:
|
||||
stack, i = self.get_stack(self.frame, None)
|
||||
stack, i = self.idb.get_stack(self.frame, None)
|
||||
sv.load_stack(stack, i)
|
||||
else:
|
||||
sv = self.stackviewer
|
||||
|
@ -233,6 +256,7 @@ class Debugger(bdb.Bdb):
|
|||
self.sync_source_line()
|
||||
|
||||
def show_frame(self, (frame, lineno)):
|
||||
# Called from OldStackViewer
|
||||
self.frame = frame
|
||||
self.show_variables()
|
||||
|
||||
|
@ -295,15 +319,15 @@ class Debugger(bdb.Bdb):
|
|||
text.tag_add("BREAK", "insert linestart", "insert lineend +1char")
|
||||
|
||||
# A literal copy of Bdb.set_break() without the print statement at the end
|
||||
def set_break(self, filename, lineno, temporary=0, cond = None):
|
||||
import linecache # Import as late as possible
|
||||
filename = self.canonic(filename)
|
||||
line = linecache.getline(filename, lineno)
|
||||
if not line:
|
||||
return 'That line does not exist!'
|
||||
if not self.breaks.has_key(filename):
|
||||
self.breaks[filename] = []
|
||||
list = self.breaks[filename]
|
||||
if not lineno in list:
|
||||
list.append(lineno)
|
||||
bp = bdb.Breakpoint(filename, lineno, temporary, cond)
|
||||
#def set_break(self, filename, lineno, temporary=0, cond = None):
|
||||
# import linecache # Import as late as possible
|
||||
# filename = self.canonic(filename)
|
||||
# line = linecache.getline(filename, lineno)
|
||||
# if not line:
|
||||
# return 'That line does not exist!'
|
||||
# if not self.breaks.has_key(filename):
|
||||
# self.breaks[filename] = []
|
||||
# list = self.breaks[filename]
|
||||
# if not lineno in list:
|
||||
# list.append(lineno)
|
||||
# bp = bdb.Breakpoint(filename, lineno, temporary, cond)
|
||||
|
|
|
@ -29,7 +29,10 @@ import string
|
|||
import getopt
|
||||
import re
|
||||
import protocol
|
||||
import socket
|
||||
import time
|
||||
import warnings
|
||||
import traceback
|
||||
|
||||
import linecache
|
||||
from code import InteractiveInterpreter
|
||||
|
@ -45,6 +48,21 @@ from OutputWindow import OutputWindow, OnDemandOutputWindow
|
|||
from configHandler import idleConf
|
||||
import idlever
|
||||
|
||||
import rpc
|
||||
|
||||
use_subprocess = 0 # Set to 1 to spawn subprocess for command execution
|
||||
|
||||
# Change warnings module to write to sys.__stderr__
|
||||
try:
|
||||
import warnings
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
def idle_showwarning(message, category, filename, lineno):
|
||||
file = sys.__stderr__
|
||||
file.write(warnings.formatwarning(message, category, filename, lineno))
|
||||
warnings.showwarning = idle_showwarning
|
||||
|
||||
# We need to patch linecache.checkcache, because we don't want it
|
||||
# to throw away our <pyshell#...> entries.
|
||||
# Rather than repeating its code here, we save those entries,
|
||||
|
@ -186,6 +204,99 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
|||
InteractiveInterpreter.__init__(self, locals=locals)
|
||||
self.save_warnings_filters = None
|
||||
|
||||
global flist
|
||||
self.output = OnDemandOutputWindow(flist)
|
||||
|
||||
rpcclt = None
|
||||
rpcpid = None
|
||||
|
||||
def spawn_subprocess(self):
|
||||
port = 8833
|
||||
addr = ("localhost", port)
|
||||
w = ['-W' + s for s in sys.warnoptions]
|
||||
args = [sys.executable] + w + ["-c", "__import__('run').main()",
|
||||
str(port)]
|
||||
self.rpcpid = os.spawnv(os.P_NOWAIT, args[0], args)
|
||||
for i in range(5):
|
||||
time.sleep(i)
|
||||
try:
|
||||
self.rpcclt = rpc.RPCClient(addr)
|
||||
break
|
||||
except socket.error, err:
|
||||
if i > 3:
|
||||
print >>sys.__stderr__, "Socket error:", err, "; retry..."
|
||||
else:
|
||||
# XXX Make this a dialog?
|
||||
print >>sys.__stderr__, "Can't spawn subprocess!"
|
||||
return
|
||||
self.output.stdout=PseudoFile(self.output, "stdout")
|
||||
self.output.stderr=PseudoFile(self.output, "stderr")
|
||||
self.rpcclt.register("stdin", self.output)
|
||||
self.rpcclt.register("stdout", self.output.stdout)
|
||||
self.rpcclt.register("stderr", self.output.stderr)
|
||||
self.rpcclt.register("flist", self.tkconsole.flist)
|
||||
self.poll_subprocess()
|
||||
|
||||
active_seq = None
|
||||
|
||||
def poll_subprocess(self):
|
||||
clt = self.rpcclt
|
||||
if clt is None:
|
||||
return
|
||||
response = clt.pollresponse(self.active_seq)
|
||||
self.tkconsole.text.after(50, self.poll_subprocess)
|
||||
if response:
|
||||
self.tkconsole.resetoutput()
|
||||
self.active_seq = None
|
||||
how, what = response
|
||||
file = self.tkconsole.console
|
||||
if how == "OK":
|
||||
if what is not None:
|
||||
print >>file, `what`
|
||||
elif how == "EXCEPTION":
|
||||
mod, name, args, tb = what
|
||||
print >>file, 'Traceback (most recent call last):'
|
||||
while tb and tb[0][0] in ("run.py", "rpc.py"):
|
||||
del tb[0]
|
||||
while tb and tb[-1][0] in ("run.py", "rpc.py"):
|
||||
del tb[-1]
|
||||
for i in range(len(tb)):
|
||||
fn, ln, nm, line = tb[i]
|
||||
if not line and fn.startswith("<pyshell#"):
|
||||
line = linecache.getline(fn, ln)
|
||||
tb[i] = fn, ln, nm, line
|
||||
traceback.print_list(tb, file=file)
|
||||
if mod and mod != "exceptions":
|
||||
name = mod + "." + name
|
||||
print >>file, name + ":", " ".join(map(str, args))
|
||||
if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
|
||||
self.remote_stack_viewer()
|
||||
elif how == "ERROR":
|
||||
print >>sys.__stderr__, "Oops:", how, what
|
||||
print >>file, "Oops:", how, what
|
||||
self.tkconsole.endexecuting()
|
||||
|
||||
def kill_subprocess(self):
|
||||
clt = self.rpcclt
|
||||
self.rpcclt = None
|
||||
if clt is not None:
|
||||
clt.close()
|
||||
|
||||
def remote_stack_viewer(self):
|
||||
import RemoteObjectBrowser
|
||||
oid = self.rpcclt.remotecall("exec", "stackviewer", ("flist",), {})
|
||||
if oid is None:
|
||||
self.tkconsole.root.bell()
|
||||
return
|
||||
item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
|
||||
from TreeWidget import ScrolledCanvas, TreeNode
|
||||
top = Toplevel(self.tkconsole.root)
|
||||
sc = ScrolledCanvas(top, bg="white", highlightthickness=0)
|
||||
sc.frame.pack(expand=1, fill="both")
|
||||
node = TreeNode(sc.canvas, None, item)
|
||||
node.expand()
|
||||
# XXX Should GC the remote tree when closing the window
|
||||
|
||||
gid = 0
|
||||
|
||||
def execsource(self, source):
|
||||
|
@ -264,10 +375,11 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
|||
|
||||
def showtraceback(self):
|
||||
# Extend base class method to reset output properly
|
||||
text = self.tkconsole.text
|
||||
self.tkconsole.resetoutput()
|
||||
self.checklinecache()
|
||||
InteractiveInterpreter.showtraceback(self)
|
||||
if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
|
||||
self.tkconsole.open_stack_viewer()
|
||||
|
||||
def checklinecache(self):
|
||||
c = linecache.cache
|
||||
|
@ -283,12 +395,43 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
|||
def getdebugger(self):
|
||||
return self.debugger
|
||||
|
||||
def runcommand(self, code):
|
||||
# This runs the code without invoking the debugger.
|
||||
# The code better not raise an exception!
|
||||
if self.tkconsole.executing:
|
||||
tkMessageBox.showerror(
|
||||
"Already executing",
|
||||
"The Python Shell window is already executing a command; "
|
||||
"please wait until it is finished.",
|
||||
master=self.tkconsole.text)
|
||||
return 0
|
||||
if self.rpcclt:
|
||||
self.rpcclt.remotecall("exec", "runcode", (code,), {})
|
||||
else:
|
||||
exec code in self.locals
|
||||
return 1
|
||||
|
||||
def runcode(self, code):
|
||||
# Override base class method
|
||||
if self.tkconsole.executing:
|
||||
tkMessageBox.showerror(
|
||||
"Already executing",
|
||||
"The Python Shell window is already executing a command; "
|
||||
"please wait until it is finished.",
|
||||
master=self.tkconsole.text)
|
||||
return
|
||||
|
||||
self.checklinecache()
|
||||
if self.save_warnings_filters is not None:
|
||||
warnings.filters[:] = self.save_warnings_filters
|
||||
self.save_warnings_filters = None
|
||||
debugger = self.debugger
|
||||
if not debugger and self.rpcclt is not None:
|
||||
self.tkconsole.beginexecuting()
|
||||
self.active_seq = self.rpcclt.asynccall("exec", "runcode",
|
||||
(code,), {})
|
||||
return
|
||||
|
||||
try:
|
||||
self.tkconsole.beginexecuting()
|
||||
try:
|
||||
|
@ -305,12 +448,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
|||
raise
|
||||
else:
|
||||
self.showtraceback()
|
||||
if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
|
||||
self.tkconsole.open_stack_viewer()
|
||||
except:
|
||||
self.showtraceback()
|
||||
if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
|
||||
self.tkconsole.open_stack_viewer()
|
||||
|
||||
finally:
|
||||
self.tkconsole.endexecuting()
|
||||
|
@ -319,7 +458,6 @@ class ModifiedInterpreter(InteractiveInterpreter):
|
|||
# Override base class write
|
||||
self.tkconsole.console.write(s)
|
||||
|
||||
|
||||
class PyShell(OutputWindow):
|
||||
|
||||
shell_title = "Python Shell"
|
||||
|
@ -366,13 +504,19 @@ class PyShell(OutputWindow):
|
|||
self.save_stdout = sys.stdout
|
||||
self.save_stderr = sys.stderr
|
||||
self.save_stdin = sys.stdin
|
||||
sys.stdout = PseudoFile(self, "stdout")
|
||||
sys.stderr = PseudoFile(self, "stderr")
|
||||
sys.stdin = self
|
||||
self.stdout = PseudoFile(self, "stdout")
|
||||
self.stderr = PseudoFile(self, "stderr")
|
||||
self.console = PseudoFile(self, "console")
|
||||
if not use_subprocess:
|
||||
sys.stdout = self.stdout
|
||||
sys.stderr = self.stderr
|
||||
sys.stdin = self
|
||||
|
||||
self.history = self.History(self.text)
|
||||
|
||||
if use_subprocess:
|
||||
self.interp.spawn_subprocess()
|
||||
|
||||
reading = 0
|
||||
executing = 0
|
||||
canceled = 0
|
||||
|
@ -411,12 +555,22 @@ class PyShell(OutputWindow):
|
|||
self.set_debugger_indicator()
|
||||
|
||||
def open_debugger(self):
|
||||
if self.interp.rpcclt:
|
||||
return self.open_remote_debugger()
|
||||
import Debugger
|
||||
self.interp.setdebugger(Debugger.Debugger(self))
|
||||
sys.ps1 = "[DEBUG ON]\n>>> "
|
||||
self.showprompt()
|
||||
self.set_debugger_indicator()
|
||||
|
||||
def open_remote_debugger(self):
|
||||
import RemoteDebugger
|
||||
gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, self)
|
||||
self.interp.setdebugger(gui)
|
||||
sys.ps1 = "[DEBUG ON]\n>>> "
|
||||
self.showprompt()
|
||||
self.set_debugger_indicator()
|
||||
|
||||
def beginexecuting(self):
|
||||
# Helper for ModifiedInterpreter
|
||||
self.resetoutput()
|
||||
|
@ -430,6 +584,7 @@ class PyShell(OutputWindow):
|
|||
##self._cancel_check = None
|
||||
self.executing = 0
|
||||
self.canceled = 0
|
||||
self.showprompt()
|
||||
|
||||
def close(self):
|
||||
# Extend base class method
|
||||
|
@ -449,6 +604,7 @@ class PyShell(OutputWindow):
|
|||
|
||||
def _close(self):
|
||||
self.close_debugger()
|
||||
self.interp.kill_subprocess()
|
||||
# Restore std streams
|
||||
sys.stdout = self.save_stdout
|
||||
sys.stderr = self.save_stderr
|
||||
|
@ -520,9 +676,18 @@ class PyShell(OutputWindow):
|
|||
self.showprompt()
|
||||
return "break"
|
||||
self.endoffile = 0
|
||||
self.canceled = 1
|
||||
if self.reading:
|
||||
self.canceled = 1
|
||||
self.top.quit()
|
||||
elif (self.executing and self.interp.rpcclt and
|
||||
self.interp.rpcpid and hasattr(os, "kill")):
|
||||
try:
|
||||
from signal import SIGINT
|
||||
except ImportError:
|
||||
SIGINT = 2
|
||||
os.kill(self.interp.rpcpid, SIGINT)
|
||||
else:
|
||||
self.canceled = 1
|
||||
return "break"
|
||||
|
||||
def eof_callback(self, event):
|
||||
|
@ -532,11 +697,6 @@ class PyShell(OutputWindow):
|
|||
self.text.compare("insert", "==", "end-1c")):
|
||||
return # Let the default binding (delete next char) take over
|
||||
if not self.executing:
|
||||
## if not tkMessageBox.askokcancel(
|
||||
## "Exit?",
|
||||
## "Are you sure you want to exit?",
|
||||
## default="ok", master=self.text):
|
||||
## return "break"
|
||||
self.resetoutput()
|
||||
self.close()
|
||||
else:
|
||||
|
@ -656,6 +816,8 @@ class PyShell(OutputWindow):
|
|||
return self._cancel_check
|
||||
|
||||
def open_stack_viewer(self, event=None):
|
||||
if self.interp.rpcclt:
|
||||
return self.interp.remote_stack_viewer()
|
||||
try:
|
||||
sys.last_traceback
|
||||
except:
|
||||
|
@ -675,6 +837,7 @@ class PyShell(OutputWindow):
|
|||
s = ""
|
||||
self.console.write(s)
|
||||
self.text.mark_set("insert", "end-1c")
|
||||
self.set_line_and_column()
|
||||
|
||||
def resetoutput(self):
|
||||
source = self.text.get("iomark", "end-1c")
|
||||
|
@ -683,6 +846,7 @@ class PyShell(OutputWindow):
|
|||
if self.text.get("end-2c") != "\n":
|
||||
self.text.insert("end-1c", "\n")
|
||||
self.text.mark_set("iomark", "end-1c")
|
||||
self.set_line_and_column()
|
||||
sys.stdout.softspace = 0
|
||||
|
||||
def write(self, s, tags=()):
|
||||
|
@ -698,6 +862,7 @@ class PseudoFile:
|
|||
def __init__(self, shell, tags):
|
||||
self.shell = shell
|
||||
self.tags = tags
|
||||
self.softspace = 0
|
||||
|
||||
def write(self, s):
|
||||
self.shell.write(s, self.tags)
|
||||
|
@ -718,9 +883,10 @@ idle file(s) (without options) edit the file(s)
|
|||
|
||||
-c cmd run the command in a shell
|
||||
-d enable the debugger
|
||||
-e edit mode; arguments are files to be edited
|
||||
-i open an interactive shell
|
||||
-i file(s) open a shell and also an editor window for each file
|
||||
-r script run a file as a script in a shell
|
||||
-r script use experimental remote (subprocess) execution feature
|
||||
-s run $IDLESTARTUP or $PYTHONSTARTUP before anything else
|
||||
-t title set title of shell window
|
||||
|
||||
|
@ -822,9 +988,10 @@ be a security risk on a single-user machine.
|
|||
interactive = 0
|
||||
script = None
|
||||
startup = 0
|
||||
global use_subprocess
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(argv, "c:dir:st:")
|
||||
opts, args = getopt.getopt(sys.argv[1:], "c:deir:st:")
|
||||
except getopt.error, msg:
|
||||
sys.stderr.write("Error: %s\n" % str(msg))
|
||||
sys.stderr.write(usage_msg)
|
||||
|
@ -836,10 +1003,14 @@ be a security risk on a single-user machine.
|
|||
cmd = a
|
||||
if o == '-d':
|
||||
debug = 1
|
||||
if o == '-e':
|
||||
edit = 1
|
||||
if o == '-i':
|
||||
interactive = 1
|
||||
if o == '-r':
|
||||
edit = 1
|
||||
script = a
|
||||
use_subprocess = 1
|
||||
if o == '-s':
|
||||
startup = 1
|
||||
if o == '-t':
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
"""Support for remote Python debugging.
|
||||
|
||||
Some ASCII art to describe the structure:
|
||||
|
||||
IN PYTHON SUBPROCESS # IN IDLE PROCESS
|
||||
#
|
||||
# oid='gui_adapter'
|
||||
+----------+ # +------------+ +-----+
|
||||
| GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
|
||||
+-----+--calls-->+----------+ # +------------+ +-----+
|
||||
| Idb | # /
|
||||
+-----+<-calls--+------------+ # +----------+<--calls-/
|
||||
| IdbAdapter |<--remote#call--| IdbProxy |
|
||||
+------------+ # +----------+
|
||||
oid='idb_adapter' #
|
||||
|
||||
The purpose of the Proxy and Adapter classes is to translate certain
|
||||
arguments and return values that cannot be transported through the RPC
|
||||
barrier, in particular frame and traceback objects.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import rpc
|
||||
import Debugger
|
||||
|
||||
# In the PYTHON subprocess
|
||||
|
||||
frametable = {}
|
||||
dicttable = {}
|
||||
codetable = {}
|
||||
|
||||
def wrap_frame(frame):
|
||||
fid = id(frame)
|
||||
frametable[fid] = frame
|
||||
return fid
|
||||
|
||||
def wrap_info(info):
|
||||
if info is None:
|
||||
return None
|
||||
else:
|
||||
return None # XXX for now
|
||||
|
||||
class GUIProxy:
|
||||
|
||||
def __init__(self, conn, oid):
|
||||
self.conn = conn
|
||||
self.oid = oid
|
||||
|
||||
def interaction(self, message, frame, info=None):
|
||||
self.conn.remotecall(self.oid, "interaction",
|
||||
(message, wrap_frame(frame), wrap_info(info)),
|
||||
{})
|
||||
|
||||
class IdbAdapter:
|
||||
|
||||
def __init__(self, idb):
|
||||
self.idb = idb
|
||||
|
||||
def set_step(self):
|
||||
self.idb.set_step()
|
||||
|
||||
def set_quit(self):
|
||||
self.idb.set_quit()
|
||||
|
||||
def set_continue(self):
|
||||
self.idb.set_continue()
|
||||
|
||||
def set_next(self, fid):
|
||||
frame = frametable[fid]
|
||||
self.idb.set_next(frame)
|
||||
|
||||
def set_return(self, fid):
|
||||
frame = frametable[fid]
|
||||
self.idb.set_return(frame)
|
||||
|
||||
def get_stack(self, fid, tbid):
|
||||
##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
|
||||
frame = frametable[fid]
|
||||
tb = None # XXX for now
|
||||
stack, i = self.idb.get_stack(frame, tb)
|
||||
##print >>sys.__stderr__, "get_stack() ->", stack
|
||||
stack = [(wrap_frame(frame), k) for frame, k in stack]
|
||||
##print >>sys.__stderr__, "get_stack() ->", stack
|
||||
return stack, i
|
||||
|
||||
def run(self, cmd):
|
||||
import __main__
|
||||
self.idb.run(cmd, __main__.__dict__)
|
||||
|
||||
def frame_attr(self, fid, name):
|
||||
frame = frametable[fid]
|
||||
return getattr(frame, name)
|
||||
|
||||
def frame_globals(self, fid):
|
||||
frame = frametable[fid]
|
||||
dict = frame.f_globals
|
||||
did = id(dict)
|
||||
dicttable[did] = dict
|
||||
return did
|
||||
|
||||
def frame_locals(self, fid):
|
||||
frame = frametable[fid]
|
||||
dict = frame.f_locals
|
||||
did = id(dict)
|
||||
dicttable[did] = dict
|
||||
return did
|
||||
|
||||
def frame_code(self, fid):
|
||||
frame = frametable[fid]
|
||||
code = frame.f_code
|
||||
cid = id(code)
|
||||
codetable[cid] = code
|
||||
return cid
|
||||
|
||||
def code_name(self, cid):
|
||||
code = codetable[cid]
|
||||
return code.co_name
|
||||
|
||||
def code_filename(self, cid):
|
||||
code = codetable[cid]
|
||||
return code.co_filename
|
||||
|
||||
def dict_keys(self, did):
|
||||
dict = dicttable[did]
|
||||
return dict.keys()
|
||||
|
||||
def dict_item(self, did, key):
|
||||
dict = dicttable[did]
|
||||
value = dict[key]
|
||||
try:
|
||||
# Test for picklability
|
||||
import cPickle
|
||||
cPickle.dumps(value)
|
||||
except:
|
||||
value = None
|
||||
return value
|
||||
|
||||
def start_debugger(conn, gui_oid):
|
||||
#
|
||||
# launched in the python subprocess
|
||||
#
|
||||
gui = GUIProxy(conn, gui_oid)
|
||||
idb = Debugger.Idb(gui)
|
||||
ada = IdbAdapter(idb)
|
||||
ada_oid = "idb_adapter"
|
||||
conn.register(ada_oid, ada)
|
||||
return ada_oid
|
||||
|
||||
# In the IDLE process
|
||||
|
||||
class FrameProxy:
|
||||
|
||||
def __init__(self, conn, fid):
|
||||
self._conn = conn
|
||||
self._fid = fid
|
||||
self._oid = "idb_adapter"
|
||||
self._dictcache = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name[:1] == "_":
|
||||
raise AttributeError, name
|
||||
if name == "f_code":
|
||||
return self._get_f_code()
|
||||
if name == "f_globals":
|
||||
return self._get_f_globals()
|
||||
if name == "f_locals":
|
||||
return self._get_f_locals()
|
||||
return self._conn.remotecall(self._oid, "frame_attr",
|
||||
(self._fid, name), {})
|
||||
|
||||
def _get_f_code(self):
|
||||
cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
|
||||
return CodeProxy(self._conn, self._oid, cid)
|
||||
|
||||
def _get_f_globals(self):
|
||||
did = self._conn.remotecall(self._oid, "frame_globals",
|
||||
(self._fid,), {})
|
||||
return self._get_dict_proxy(did)
|
||||
|
||||
def _get_f_locals(self):
|
||||
did = self._conn.remotecall(self._oid, "frame_locals",
|
||||
(self._fid,), {})
|
||||
return self._get_dict_proxy(did)
|
||||
|
||||
def _get_dict_proxy(self, did):
|
||||
if self._dictcache.has_key(did):
|
||||
return self._dictcache[did]
|
||||
dp = DictProxy(self._conn, self._oid, did)
|
||||
self._dictcache[did] = dp
|
||||
return dp
|
||||
|
||||
class CodeProxy:
|
||||
|
||||
def __init__(self, conn, oid, cid):
|
||||
self._conn = conn
|
||||
self._oid = oid
|
||||
self._cid = cid
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == "co_name":
|
||||
return self._conn.remotecall(self._oid, "code_name",
|
||||
(self._cid,), {})
|
||||
if name == "co_filename":
|
||||
return self._conn.remotecall(self._oid, "code_filename",
|
||||
(self._cid,), {})
|
||||
|
||||
class DictProxy:
|
||||
|
||||
def __init__(self, conn, oid, did):
|
||||
self._conn = conn
|
||||
self._oid = oid
|
||||
self._did = did
|
||||
|
||||
def keys(self):
|
||||
return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._conn.remotecall(self._oid, "dict_item",
|
||||
(self._did, key), {})
|
||||
|
||||
def __getattr__(self, name):
|
||||
##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
|
||||
raise AttributeError, name
|
||||
|
||||
class GUIAdaper:
|
||||
|
||||
def __init__(self, conn, gui):
|
||||
self.conn = conn
|
||||
self.gui = gui
|
||||
|
||||
def interaction(self, message, fid, iid):
|
||||
print "interaction(%s, %s, %s)" % (`message`, `fid`, `iid`)
|
||||
frame = FrameProxy(self.conn, fid)
|
||||
info = None # XXX for now
|
||||
self.gui.interaction(message, frame, info)
|
||||
|
||||
class IdbProxy:
|
||||
|
||||
def __init__(self, conn, oid):
|
||||
self.oid = oid
|
||||
self.conn = conn
|
||||
|
||||
def call(self, methodname, *args, **kwargs):
|
||||
##print "call %s %s %s" % (methodname, args, kwargs)
|
||||
value = self.conn.remotecall(self.oid, methodname, args, kwargs)
|
||||
##print "return %s" % `value`
|
||||
return value
|
||||
|
||||
def run(self, cmd, locals):
|
||||
# Ignores locals on purpose!
|
||||
self.call("run", cmd)
|
||||
|
||||
def get_stack(self, frame, tb):
|
||||
stack, i = self.call("get_stack", frame._fid, None)
|
||||
stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
|
||||
return stack, i
|
||||
|
||||
def set_continue(self):
|
||||
self.call("set_continue")
|
||||
|
||||
def set_step(self):
|
||||
self.call("set_step")
|
||||
|
||||
def set_next(self, frame):
|
||||
self.call("set_next", frame._fid)
|
||||
|
||||
def set_return(self, frame):
|
||||
self.call("set_return", frame._fid)
|
||||
|
||||
def set_quit(self):
|
||||
self.call("set_quit")
|
||||
|
||||
def start_remote_debugger(conn, pyshell):
|
||||
#
|
||||
# instruct the (remote) subprocess to create
|
||||
# a debugger instance, and lets it know that
|
||||
# the local GUIAdapter called "gui_adapter"
|
||||
# is waiting notification of debugging events
|
||||
#
|
||||
ada_oid = "gui_adapter"
|
||||
idb_oid = conn.remotecall("exec", "start_debugger", (ada_oid,), {})
|
||||
idb = IdbProxy(conn, idb_oid)
|
||||
gui = Debugger.Debugger(pyshell, idb)
|
||||
ada = GUIAdaper(conn, gui)
|
||||
conn.register(ada_oid, ada)
|
||||
return gui
|
|
@ -0,0 +1,36 @@
|
|||
import rpc
|
||||
|
||||
def remote_object_tree_item(item):
|
||||
wrapper = WrappedObjectTreeItem(item)
|
||||
oid = id(wrapper)
|
||||
rpc.objecttable[oid] = wrapper
|
||||
return oid
|
||||
|
||||
class WrappedObjectTreeItem:
|
||||
# Lives in PYTHON subprocess
|
||||
|
||||
def __init__(self, item):
|
||||
self.__item = item
|
||||
|
||||
def __getattr__(self, name):
|
||||
value = getattr(self.__item, name)
|
||||
return value
|
||||
|
||||
def _GetSubList(self):
|
||||
list = self.__item._GetSubList()
|
||||
return map(remote_object_tree_item, list)
|
||||
|
||||
class StubObjectTreeItem:
|
||||
# Lives in IDLE process
|
||||
|
||||
def __init__(self, sockio, oid):
|
||||
self.sockio = sockio
|
||||
self.oid = oid
|
||||
|
||||
def __getattr__(self, name):
|
||||
value = rpc.MethodProxy(self.sockio, self.oid, name)
|
||||
return value
|
||||
|
||||
def _GetSubList(self):
|
||||
list = self.sockio.remotecall(self.oid, "_GetSubList", (), {})
|
||||
return [StubObjectTreeItem(self.sockio, oid) for oid in list]
|
|
@ -14,6 +14,14 @@ namespace. Output goes to the shell window.
|
|||
- Run module (Control-F5) does the same but executes the module's
|
||||
code in the __main__ namespace.
|
||||
|
||||
XXX Redesign this interface (yet again) as follows:
|
||||
|
||||
- Present a dialog box for ``Run script''
|
||||
|
||||
- Allow specify command line arguments in the dialog box
|
||||
|
||||
- Restart the interpreter when running a script
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
@ -25,9 +33,9 @@ indent_message = """Error: Inconsistent indentation detected!
|
|||
|
||||
This means that either:
|
||||
|
||||
(1) your indentation is outright incorrect (easy to fix), or
|
||||
1) your indentation is outright incorrect (easy to fix), or
|
||||
|
||||
(2) your indentation mixes tabs and spaces in a way that depends on \
|
||||
2) your indentation mixes tabs and spaces in a way that depends on \
|
||||
how many spaces a tab is worth.
|
||||
|
||||
To fix case 2, change all tabs to spaces by using Select All followed \
|
||||
|
@ -105,28 +113,31 @@ class ScriptBinding:
|
|||
return 1
|
||||
|
||||
def import_module_event(self, event):
|
||||
flist = self.editwin.flist
|
||||
shell = flist.open_shell()
|
||||
interp = shell.interp
|
||||
|
||||
filename = self.getfilename()
|
||||
if not filename:
|
||||
return
|
||||
|
||||
modname, ext = os.path.splitext(os.path.basename(filename))
|
||||
if sys.modules.has_key(modname):
|
||||
mod = sys.modules[modname]
|
||||
else:
|
||||
mod = imp.new_module(modname)
|
||||
sys.modules[modname] = mod
|
||||
mod.__file__ = filename
|
||||
setattr(sys.modules['__main__'], modname, mod)
|
||||
|
||||
dir = os.path.dirname(filename)
|
||||
dir = os.path.normpath(os.path.abspath(dir))
|
||||
if dir not in sys.path:
|
||||
sys.path.insert(0, dir)
|
||||
|
||||
flist = self.editwin.flist
|
||||
shell = flist.open_shell()
|
||||
interp = shell.interp
|
||||
interp.runcode("reload(%s)" % modname)
|
||||
interp.runcode("""if 1:
|
||||
import sys as _sys
|
||||
if %s not in _sys.path:
|
||||
_sys.path.insert(0, %s)
|
||||
if _sys.modules.get(%s):
|
||||
del _sys
|
||||
import %s
|
||||
reload(%s)
|
||||
else:
|
||||
del _sys
|
||||
import %s
|
||||
\n""" % (`dir`, `dir`, `modname`, modname, modname, modname))
|
||||
|
||||
def run_script_event(self, event):
|
||||
filename = self.getfilename()
|
||||
|
@ -136,10 +147,16 @@ class ScriptBinding:
|
|||
flist = self.editwin.flist
|
||||
shell = flist.open_shell()
|
||||
interp = shell.interp
|
||||
if (not sys.argv or
|
||||
os.path.basename(sys.argv[0]) != os.path.basename(filename)):
|
||||
# XXX Too often this discards arguments the user just set...
|
||||
sys.argv = [filename]
|
||||
# XXX Too often this discards arguments the user just set...
|
||||
interp.runcommand("""if 1:
|
||||
_filename = %s
|
||||
import sys as _sys
|
||||
from os.path import basename as _basename
|
||||
if (not _sys.argv or
|
||||
_basename(_sys.argv[0]) != _basename(_filename)):
|
||||
_sys.argv = [_filename]
|
||||
del _filename, _sys, _basename
|
||||
\n""" % `filename`)
|
||||
interp.execfile(filename)
|
||||
|
||||
def getfilename(self):
|
||||
|
|
|
@ -0,0 +1,530 @@
|
|||
# ASCII-art documentation
|
||||
#
|
||||
# +---------------------------------+ +----------+
|
||||
# | SocketServer.BaseRequestHandler | | SocketIO |
|
||||
# +---------------------------------+ +----------+
|
||||
# ^ ^ ^
|
||||
# | | |
|
||||
# | + -------------------+ |
|
||||
# | | |
|
||||
# +-------------------------+ +-----------------+
|
||||
# | RPCHandler | | RPCClient |
|
||||
# |-------------------------| |-----------------|
|
||||
# | register() | | remotecall() |
|
||||
# | unregister() | | register() |
|
||||
# | | | unregister() |
|
||||
# | | | get_remote_proxy|
|
||||
# +-------------------------+ +-----------------+
|
||||
#
|
||||
import sys
|
||||
import socket
|
||||
import select
|
||||
import SocketServer
|
||||
import struct
|
||||
import cPickle as pickle
|
||||
import threading
|
||||
import traceback
|
||||
import copy_reg
|
||||
import types
|
||||
import marshal
|
||||
|
||||
def unpickle_code(ms):
|
||||
co = marshal.loads(ms)
|
||||
assert isinstance(co, types.CodeType)
|
||||
return co
|
||||
|
||||
def pickle_code(co):
|
||||
assert isinstance(co, types.CodeType)
|
||||
ms = marshal.dumps(co)
|
||||
return unpickle_code, (ms,)
|
||||
|
||||
def unpickle_function(ms):
|
||||
return ms
|
||||
|
||||
def pickle_function(fn):
|
||||
assert isinstance(fn, type.FunctionType)
|
||||
return `fn`
|
||||
|
||||
copy_reg.pickle(types.CodeType, pickle_code, unpickle_code)
|
||||
copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function)
|
||||
|
||||
BUFSIZE = 8*1024
|
||||
|
||||
class RPCServer(SocketServer.TCPServer):
|
||||
|
||||
def __init__(self, addr, handlerclass=None):
|
||||
if handlerclass is None:
|
||||
handlerclass = RPCHandler
|
||||
self.objtable = objecttable
|
||||
SocketServer.TCPServer.__init__(self, addr, handlerclass)
|
||||
|
||||
def verify_request(self, request, client_address):
|
||||
host, port = client_address
|
||||
if host != "127.0.0.1":
|
||||
print "Disallowed host:", host
|
||||
return 0
|
||||
else:
|
||||
return 1
|
||||
|
||||
def register(self, oid, object):
|
||||
self.objtable[oid] = object
|
||||
|
||||
def unregister(self, oid):
|
||||
try:
|
||||
del self.objtable[oid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
objecttable = {}
|
||||
|
||||
class SocketIO:
|
||||
|
||||
debugging = 0
|
||||
|
||||
def __init__(self, sock, objtable=None, debugging=None):
|
||||
self.mainthread = threading.currentThread()
|
||||
if debugging is not None:
|
||||
self.debugging = debugging
|
||||
self.sock = sock
|
||||
if objtable is None:
|
||||
objtable = objecttable
|
||||
self.objtable = objtable
|
||||
self.statelock = threading.Lock()
|
||||
self.responses = {}
|
||||
self.cvars = {}
|
||||
|
||||
def close(self):
|
||||
sock = self.sock
|
||||
self.sock = None
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
|
||||
def debug(self, *args):
|
||||
if not self.debugging:
|
||||
return
|
||||
s = str(threading.currentThread().getName())
|
||||
for a in args:
|
||||
s = s + " " + str(a)
|
||||
s = s + "\n"
|
||||
sys.__stderr__.write(s)
|
||||
|
||||
def register(self, oid, object):
|
||||
self.objtable[oid] = object
|
||||
|
||||
def unregister(self, oid):
|
||||
try:
|
||||
del self.objtable[oid]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def localcall(self, request):
|
||||
##self.debug("localcall:", request)
|
||||
try:
|
||||
how, (oid, methodname, args, kwargs) = request
|
||||
except TypeError:
|
||||
return ("ERROR", "Bad request format")
|
||||
assert how == "call"
|
||||
if not self.objtable.has_key(oid):
|
||||
return ("ERROR", "Unknown object id: %s" % `oid`)
|
||||
obj = self.objtable[oid]
|
||||
if methodname == "__methods__":
|
||||
methods = {}
|
||||
_getmethods(obj, methods)
|
||||
return ("OK", methods)
|
||||
if methodname == "__attributes__":
|
||||
attributes = {}
|
||||
_getattributes(obj, attributes)
|
||||
return ("OK", attributes)
|
||||
if not hasattr(obj, methodname):
|
||||
return ("ERROR", "Unsupported method name: %s" % `methodname`)
|
||||
method = getattr(obj, methodname)
|
||||
try:
|
||||
ret = method(*args, **kwargs)
|
||||
if isinstance(ret, RemoteObject):
|
||||
ret = remoteref(ret)
|
||||
return ("OK", ret)
|
||||
except:
|
||||
##traceback.print_exc(file=sys.__stderr__)
|
||||
typ, val, tb = info = sys.exc_info()
|
||||
sys.last_type, sys.last_value, sys.last_traceback = info
|
||||
if isinstance(typ, type(Exception)):
|
||||
# Class exceptions
|
||||
mod = typ.__module__
|
||||
name = typ.__name__
|
||||
if issubclass(typ, Exception):
|
||||
args = val.args
|
||||
else:
|
||||
args = (str(val),)
|
||||
else:
|
||||
# String exceptions
|
||||
mod = None
|
||||
name = typ
|
||||
args = (str(val),)
|
||||
tb = traceback.extract_tb(tb)
|
||||
return ("EXCEPTION", (mod, name, args, tb))
|
||||
|
||||
def remotecall(self, oid, methodname, args, kwargs):
|
||||
seq = self.asynccall(oid, methodname, args, kwargs)
|
||||
return self.asyncreturn(seq)
|
||||
|
||||
def asynccall(self, oid, methodname, args, kwargs):
|
||||
request = ("call", (oid, methodname, args, kwargs))
|
||||
seq = self.putrequest(request)
|
||||
return seq
|
||||
|
||||
def asyncreturn(self, seq):
|
||||
response = self.getresponse(seq)
|
||||
return self.decoderesponse(response)
|
||||
|
||||
def decoderesponse(self, response):
|
||||
how, what = response
|
||||
if how == "OK":
|
||||
return what
|
||||
if how == "EXCEPTION":
|
||||
mod, name, args, tb = what
|
||||
self.traceback = tb
|
||||
if mod:
|
||||
try:
|
||||
__import__(mod)
|
||||
module = sys.modules[mod]
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
cls = getattr(module, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
raise getattr(__import__(mod), name)(*args)
|
||||
else:
|
||||
if mod:
|
||||
name = mod + "." + name
|
||||
raise name, args
|
||||
if how == "ERROR":
|
||||
raise RuntimeError, what
|
||||
raise SystemError, (how, what)
|
||||
|
||||
def mainloop(self):
|
||||
try:
|
||||
self.getresponse(None)
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
def getresponse(self, myseq):
|
||||
response = self._getresponse(myseq)
|
||||
if response is not None:
|
||||
how, what = response
|
||||
if how == "OK":
|
||||
response = how, self._proxify(what)
|
||||
return response
|
||||
|
||||
def _proxify(self, obj):
|
||||
if isinstance(obj, RemoteProxy):
|
||||
return RPCProxy(self, obj.oid)
|
||||
if isinstance(obj, types.ListType):
|
||||
return map(self._proxify, obj)
|
||||
# XXX Check for other types -- not currently needed
|
||||
return obj
|
||||
|
||||
def _getresponse(self, myseq):
|
||||
if threading.currentThread() is self.mainthread:
|
||||
# Main thread: does all reading of requests and responses
|
||||
while 1:
|
||||
response = self.pollresponse(myseq, None)
|
||||
if response is not None:
|
||||
return response
|
||||
else:
|
||||
# Auxiliary thread: wait for notification from main thread
|
||||
cvar = threading.Condition(self.statelock)
|
||||
self.statelock.acquire()
|
||||
self.cvars[myseq] = cvar
|
||||
while not self.responses.has_key(myseq):
|
||||
cvar.wait()
|
||||
response = self.responses[myseq]
|
||||
del self.responses[myseq]
|
||||
del self.cvars[myseq]
|
||||
self.statelock.release()
|
||||
return response
|
||||
|
||||
def putrequest(self, request):
|
||||
seq = self.newseq()
|
||||
self.putmessage((seq, request))
|
||||
return seq
|
||||
|
||||
nextseq = 0
|
||||
|
||||
def newseq(self):
|
||||
self.nextseq = seq = self.nextseq + 2
|
||||
return seq
|
||||
|
||||
def putmessage(self, message):
|
||||
try:
|
||||
s = pickle.dumps(message)
|
||||
except:
|
||||
print >>sys.__stderr__, "Cannot pickle:", `message`
|
||||
raise
|
||||
s = struct.pack("<i", len(s)) + s
|
||||
while len(s) > 0:
|
||||
n = self.sock.send(s)
|
||||
s = s[n:]
|
||||
|
||||
def ioready(self, wait=0.0):
|
||||
r, w, x = select.select([self.sock.fileno()], [], [], wait)
|
||||
return len(r)
|
||||
|
||||
buffer = ""
|
||||
bufneed = 4
|
||||
bufstate = 0 # meaning: 0 => reading count; 1 => reading data
|
||||
|
||||
def pollpacket(self, wait=0.0):
|
||||
self._stage0()
|
||||
if len(self.buffer) < self.bufneed:
|
||||
if not self.ioready(wait):
|
||||
return None
|
||||
try:
|
||||
s = self.sock.recv(BUFSIZE)
|
||||
except socket.error:
|
||||
raise EOFError
|
||||
if len(s) == 0:
|
||||
raise EOFError
|
||||
self.buffer += s
|
||||
self._stage0()
|
||||
return self._stage1()
|
||||
|
||||
def _stage0(self):
|
||||
if self.bufstate == 0 and len(self.buffer) >= 4:
|
||||
s = self.buffer[:4]
|
||||
self.buffer = self.buffer[4:]
|
||||
self.bufneed = struct.unpack("<i", s)[0]
|
||||
self.bufstate = 1
|
||||
|
||||
def _stage1(self):
|
||||
if self.bufstate == 1 and len(self.buffer) >= self.bufneed:
|
||||
packet = self.buffer[:self.bufneed]
|
||||
self.buffer = self.buffer[self.bufneed:]
|
||||
self.bufneed = 4
|
||||
self.bufstate = 0
|
||||
return packet
|
||||
|
||||
def pollmessage(self, wait=0.0):
|
||||
packet = self.pollpacket(wait)
|
||||
if packet is None:
|
||||
return None
|
||||
try:
|
||||
message = pickle.loads(packet)
|
||||
except:
|
||||
print >>sys.__stderr__, "-----------------------"
|
||||
print >>sys.__stderr__, "cannot unpickle packet:", `packet`
|
||||
traceback.print_stack(file=sys.__stderr__)
|
||||
print >>sys.__stderr__, "-----------------------"
|
||||
raise
|
||||
return message
|
||||
|
||||
def pollresponse(self, myseq, wait=0.0):
|
||||
# Loop while there's no more buffered input or until specific response
|
||||
while 1:
|
||||
message = self.pollmessage(wait)
|
||||
if message is None:
|
||||
return None
|
||||
wait = 0.0
|
||||
seq, resq = message
|
||||
if resq[0] == "call":
|
||||
response = self.localcall(resq)
|
||||
self.putmessage((seq, response))
|
||||
continue
|
||||
elif seq == myseq:
|
||||
return resq
|
||||
else:
|
||||
self.statelock.acquire()
|
||||
self.responses[seq] = resq
|
||||
cv = self.cvars.get(seq)
|
||||
if cv is not None:
|
||||
cv.notify()
|
||||
self.statelock.release()
|
||||
continue
|
||||
|
||||
class RemoteObject:
|
||||
# Token mix-in class
|
||||
pass
|
||||
|
||||
def remoteref(obj):
|
||||
oid = id(obj)
|
||||
objecttable[oid] = obj
|
||||
return RemoteProxy(oid)
|
||||
|
||||
class RemoteProxy:
|
||||
|
||||
def __init__(self, oid):
|
||||
self.oid = oid
|
||||
|
||||
class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
|
||||
|
||||
debugging = 0
|
||||
|
||||
def __init__(self, sock, addr, svr):
|
||||
svr.current_handler = self ## cgt xxx
|
||||
SocketIO.__init__(self, sock)
|
||||
SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
|
||||
|
||||
def setup(self):
|
||||
SocketServer.BaseRequestHandler.setup(self)
|
||||
print >>sys.__stderr__, "Connection from", self.client_address
|
||||
|
||||
def finish(self):
|
||||
print >>sys.__stderr__, "End connection from", self.client_address
|
||||
SocketServer.BaseRequestHandler.finish(self)
|
||||
|
||||
def handle(self):
|
||||
self.mainloop()
|
||||
|
||||
def get_remote_proxy(self, oid):
|
||||
return RPCProxy(self, oid)
|
||||
|
||||
class RPCClient(SocketIO):
|
||||
|
||||
nextseq = 1 # Requests coming from the client are odd
|
||||
|
||||
def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
|
||||
sock = socket.socket(family, type)
|
||||
sock.connect(address)
|
||||
SocketIO.__init__(self, sock)
|
||||
|
||||
def get_remote_proxy(self, oid):
|
||||
return RPCProxy(self, oid)
|
||||
|
||||
class RPCProxy:
|
||||
|
||||
__methods = None
|
||||
__attributes = None
|
||||
|
||||
def __init__(self, sockio, oid):
|
||||
self.sockio = sockio
|
||||
self.oid = oid
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self.__methods is None:
|
||||
self.__getmethods()
|
||||
if self.__methods.get(name):
|
||||
return MethodProxy(self.sockio, self.oid, name)
|
||||
if self.__attributes is None:
|
||||
self.__getattributes()
|
||||
if not self.__attributes.has_key(name):
|
||||
raise AttributeError, name
|
||||
__getattr__.DebuggerStepThrough=1
|
||||
|
||||
def __getattributes(self):
|
||||
self.__attributes = self.sockio.remotecall(self.oid,
|
||||
"__attributes__", (), {})
|
||||
|
||||
def __getmethods(self):
|
||||
self.__methods = self.sockio.remotecall(self.oid,
|
||||
"__methods__", (), {})
|
||||
|
||||
def _getmethods(obj, methods):
|
||||
# Helper to get a list of methods from an object
|
||||
# Adds names to dictionary argument 'methods'
|
||||
for name in dir(obj):
|
||||
attr = getattr(obj, name)
|
||||
if callable(attr):
|
||||
methods[name] = 1
|
||||
if type(obj) == types.InstanceType:
|
||||
_getmethods(obj.__class__, methods)
|
||||
if type(obj) == types.ClassType:
|
||||
for super in obj.__bases__:
|
||||
_getmethods(super, methods)
|
||||
|
||||
def _getattributes(obj, attributes):
|
||||
for name in dir(obj):
|
||||
attr = getattr(obj, name)
|
||||
if not callable(attr):
|
||||
attributes[name] = 1
|
||||
|
||||
class MethodProxy:
|
||||
|
||||
def __init__(self, sockio, oid, name):
|
||||
self.sockio = sockio
|
||||
self.oid = oid
|
||||
self.name = name
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
|
||||
return value
|
||||
|
||||
#
|
||||
# Self Test
|
||||
#
|
||||
|
||||
def testServer(addr):
|
||||
class RemotePerson:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
def greet(self, name):
|
||||
print "(someone called greet)"
|
||||
print "Hello %s, I am %s." % (name, self.name)
|
||||
print
|
||||
def getName(self):
|
||||
print "(someone called getName)"
|
||||
print
|
||||
return self.name
|
||||
def greet_this_guy(self, name):
|
||||
print "(someone called greet_this_guy)"
|
||||
print "About to greet %s ..." % name
|
||||
remote_guy = self.server.current_handler.get_remote_proxy(name)
|
||||
remote_guy.greet("Thomas Edison")
|
||||
print "Done."
|
||||
print
|
||||
|
||||
person = RemotePerson("Thomas Edison")
|
||||
svr = RPCServer(addr)
|
||||
svr.register('thomas', person)
|
||||
person.server = svr # only required if callbacks are used
|
||||
|
||||
# svr.serve_forever()
|
||||
svr.handle_request() # process once only
|
||||
|
||||
def testClient(addr):
|
||||
|
||||
#
|
||||
# demonstrates RPC Client
|
||||
#
|
||||
import time
|
||||
clt=RPCClient(addr)
|
||||
thomas = clt.get_remote_proxy("thomas")
|
||||
print "The remote person's name is ..."
|
||||
print thomas.getName()
|
||||
# print clt.remotecall("thomas", "getName", (), {})
|
||||
print
|
||||
time.sleep(1)
|
||||
print "Getting remote thomas to say hi..."
|
||||
thomas.greet("Alexander Bell")
|
||||
#clt.remotecall("thomas","greet",("Alexander Bell",), {})
|
||||
print "Done."
|
||||
print
|
||||
time.sleep(2)
|
||||
|
||||
# demonstrates remote server calling local instance
|
||||
class LocalPerson:
|
||||
def __init__(self,name):
|
||||
self.name = name
|
||||
def greet(self, name):
|
||||
print "You've greeted me!"
|
||||
def getName(self):
|
||||
return self.name
|
||||
person = LocalPerson("Alexander Bell")
|
||||
clt.register("alexander",person)
|
||||
thomas.greet_this_guy("alexander")
|
||||
# clt.remotecall("thomas","greet_this_guy",("alexander",), {})
|
||||
|
||||
def test():
|
||||
addr=("localhost",8833)
|
||||
if len(sys.argv) == 2:
|
||||
if sys.argv[1]=='-server':
|
||||
testServer(addr)
|
||||
return
|
||||
testClient(addr)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import sys
|
||||
import rpc
|
||||
|
||||
def main():
|
||||
port = 8833
|
||||
if sys.argv[1:]:
|
||||
port = int(sys.argv[1])
|
||||
sys.argv[:] = [""]
|
||||
addr = ("localhost", port)
|
||||
svr = rpc.RPCServer(addr, MyHandler)
|
||||
svr.handle_request() # A single request only
|
||||
|
||||
class MyHandler(rpc.RPCHandler):
|
||||
|
||||
def handle(self):
|
||||
executive = Executive(self)
|
||||
self.register("exec", executive)
|
||||
sys.stdin = self.get_remote_proxy("stdin")
|
||||
sys.stdout = self.get_remote_proxy("stdout")
|
||||
sys.stderr = self.get_remote_proxy("stderr")
|
||||
rpc.RPCHandler.handle(self)
|
||||
|
||||
class Executive:
|
||||
|
||||
def __init__(self, rpchandler):
|
||||
self.conn = rpchandler
|
||||
import __main__
|
||||
self.locals = __main__.__dict__
|
||||
|
||||
def runcode(self, code):
|
||||
exec code in self.locals
|
||||
|
||||
def start_debugger(self, gui_oid):
|
||||
import RemoteDebugger
|
||||
return RemoteDebugger.start_debugger(self.conn, gui_oid)
|
||||
|
||||
def stackviewer(self, flist_oid=None):
|
||||
if not hasattr(sys, "last_traceback"):
|
||||
return None
|
||||
flist = None
|
||||
if flist_oid is not None:
|
||||
flist = self.conn.get_remote_proxy(flist_oid)
|
||||
import RemoteObjectBrowser
|
||||
import StackViewer
|
||||
tb = sys.last_traceback
|
||||
while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
|
||||
tb = tb.tb_next
|
||||
item = StackViewer.StackTreeItem(flist, tb)
|
||||
return RemoteObjectBrowser.remote_object_tree_item(item)
|
Loading…
Reference in New Issue