From 5d2af63cc36ca1141e1ec7412fc33866f3908408 Mon Sep 17 00:00:00 2001 From: Chui Tey Date: Sun, 26 May 2002 13:36:41 +0000 Subject: GvR's rpc patch --- Lib/idlelib/Debugger.py | 140 ++++++---- Lib/idlelib/PyShell.py | 205 ++++++++++++-- Lib/idlelib/RemoteDebugger.py | 287 ++++++++++++++++++++ Lib/idlelib/RemoteObjectBrowser.py | 36 +++ Lib/idlelib/ScriptBinding.py | 55 ++-- Lib/idlelib/rpc.py | 530 +++++++++++++++++++++++++++++++++++++ Lib/idlelib/run.py | 49 ++++ 7 files changed, 1208 insertions(+), 94 deletions(-) create mode 100644 Lib/idlelib/RemoteDebugger.py create mode 100644 Lib/idlelib/RemoteObjectBrowser.py create mode 100644 Lib/idlelib/rpc.py create mode 100644 Lib/idlelib/run.py diff --git a/Lib/idlelib/Debugger.py b/Lib/idlelib/Debugger.py index 949a0f8..a483168 100644 --- a/Lib/idlelib/Debugger.py +++ b/Lib/idlelib/Debugger.py @@ -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', ''): + 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 - interacting = 0 +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) diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py index 9616e35..3e512e3 100644 --- a/Lib/idlelib/PyShell.py +++ b/Lib/idlelib/PyShell.py @@ -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 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(">file, name + ":", " ".join(map(str, args)) + if self.tkconsole.getvar("<>"): + 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("<>"): + 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("<>"): - self.tkconsole.open_stack_viewer() except: self.showtraceback() - if self.tkconsole.getvar("<>"): - 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': diff --git a/Lib/idlelib/RemoteDebugger.py b/Lib/idlelib/RemoteDebugger.py new file mode 100644 index 0000000..0f8eac5 --- /dev/null +++ b/Lib/idlelib/RemoteDebugger.py @@ -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 diff --git a/Lib/idlelib/RemoteObjectBrowser.py b/Lib/idlelib/RemoteObjectBrowser.py new file mode 100644 index 0000000..6ba3391 --- /dev/null +++ b/Lib/idlelib/RemoteObjectBrowser.py @@ -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] diff --git a/Lib/idlelib/ScriptBinding.py b/Lib/idlelib/ScriptBinding.py index 2b3bf99..906b4f2 100644 --- a/Lib/idlelib/ScriptBinding.py +++ b/Lib/idlelib/ScriptBinding.py @@ -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): diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py new file mode 100644 index 0000000..a4d2705 --- /dev/null +++ b/Lib/idlelib/rpc.py @@ -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(" 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("= 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() + + diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py new file mode 100644 index 0000000..1b84d4d --- /dev/null +++ b/Lib/idlelib/run.py @@ -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) -- cgit v0.12