GvR's rpc patch

This commit is contained in:
Chui Tey 2002-05-26 13:36:41 +00:00
parent 38d53451b7
commit 5d2af63cc3
7 changed files with 1208 additions and 94 deletions

View File

@ -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)

View File

@ -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':

View File

@ -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

View File

@ -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]

View File

@ -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]
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):

530
Lib/idlelib/rpc.py Normal file
View File

@ -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()

49
Lib/idlelib/run.py Normal file
View File

@ -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)