jpayne@69: """RPC Implementation, originally written for the Python Idle IDE jpayne@69: jpayne@69: For security reasons, GvR requested that Idle's Python execution server process jpayne@69: connect to the Idle process, which listens for the connection. Since Idle has jpayne@69: only one client per server, this was not a limitation. jpayne@69: jpayne@69: +---------------------------------+ +-------------+ jpayne@69: | socketserver.BaseRequestHandler | | SocketIO | jpayne@69: +---------------------------------+ +-------------+ jpayne@69: ^ | register() | jpayne@69: | | unregister()| jpayne@69: | +-------------+ jpayne@69: | ^ ^ jpayne@69: | | | jpayne@69: | + -------------------+ | jpayne@69: | | | jpayne@69: +-------------------------+ +-----------------+ jpayne@69: | RPCHandler | | RPCClient | jpayne@69: | [attribute of RPCServer]| | | jpayne@69: +-------------------------+ +-----------------+ jpayne@69: jpayne@69: The RPCServer handler class is expected to provide register/unregister methods. jpayne@69: RPCHandler inherits the mix-in class SocketIO, which provides these methods. jpayne@69: jpayne@69: See the Idle run.main() docstring for further information on how this was jpayne@69: accomplished in Idle. jpayne@69: jpayne@69: """ jpayne@69: import builtins jpayne@69: import copyreg jpayne@69: import io jpayne@69: import marshal jpayne@69: import os jpayne@69: import pickle jpayne@69: import queue jpayne@69: import select jpayne@69: import socket jpayne@69: import socketserver jpayne@69: import struct jpayne@69: import sys jpayne@69: import threading jpayne@69: import traceback jpayne@69: import types jpayne@69: jpayne@69: def unpickle_code(ms): jpayne@69: "Return code object from marshal string ms." jpayne@69: co = marshal.loads(ms) jpayne@69: assert isinstance(co, types.CodeType) jpayne@69: return co jpayne@69: jpayne@69: def pickle_code(co): jpayne@69: "Return unpickle function and tuple with marshalled co code object." jpayne@69: assert isinstance(co, types.CodeType) jpayne@69: ms = marshal.dumps(co) jpayne@69: return unpickle_code, (ms,) jpayne@69: jpayne@69: def dumps(obj, protocol=None): jpayne@69: "Return pickled (or marshalled) string for obj." jpayne@69: # IDLE passes 'None' to select pickle.DEFAULT_PROTOCOL. jpayne@69: f = io.BytesIO() jpayne@69: p = CodePickler(f, protocol) jpayne@69: p.dump(obj) jpayne@69: return f.getvalue() jpayne@69: jpayne@69: jpayne@69: class CodePickler(pickle.Pickler): jpayne@69: dispatch_table = {types.CodeType: pickle_code, **copyreg.dispatch_table} jpayne@69: jpayne@69: jpayne@69: BUFSIZE = 8*1024 jpayne@69: LOCALHOST = '127.0.0.1' jpayne@69: jpayne@69: class RPCServer(socketserver.TCPServer): jpayne@69: jpayne@69: def __init__(self, addr, handlerclass=None): jpayne@69: if handlerclass is None: jpayne@69: handlerclass = RPCHandler jpayne@69: socketserver.TCPServer.__init__(self, addr, handlerclass) jpayne@69: jpayne@69: def server_bind(self): jpayne@69: "Override TCPServer method, no bind() phase for connecting entity" jpayne@69: pass jpayne@69: jpayne@69: def server_activate(self): jpayne@69: """Override TCPServer method, connect() instead of listen() jpayne@69: jpayne@69: Due to the reversed connection, self.server_address is actually the jpayne@69: address of the Idle Client to which we are connecting. jpayne@69: jpayne@69: """ jpayne@69: self.socket.connect(self.server_address) jpayne@69: jpayne@69: def get_request(self): jpayne@69: "Override TCPServer method, return already connected socket" jpayne@69: return self.socket, self.server_address jpayne@69: jpayne@69: def handle_error(self, request, client_address): jpayne@69: """Override TCPServer method jpayne@69: jpayne@69: Error message goes to __stderr__. No error message if exiting jpayne@69: normally or socket raised EOF. Other exceptions not handled in jpayne@69: server code will cause os._exit. jpayne@69: jpayne@69: """ jpayne@69: try: jpayne@69: raise jpayne@69: except SystemExit: jpayne@69: raise jpayne@69: except: jpayne@69: erf = sys.__stderr__ jpayne@69: print('\n' + '-'*40, file=erf) jpayne@69: print('Unhandled server exception!', file=erf) jpayne@69: print('Thread: %s' % threading.current_thread().name, file=erf) jpayne@69: print('Client Address: ', client_address, file=erf) jpayne@69: print('Request: ', repr(request), file=erf) jpayne@69: traceback.print_exc(file=erf) jpayne@69: print('\n*** Unrecoverable, server exiting!', file=erf) jpayne@69: print('-'*40, file=erf) jpayne@69: os._exit(0) jpayne@69: jpayne@69: #----------------- end class RPCServer -------------------- jpayne@69: jpayne@69: objecttable = {} jpayne@69: request_queue = queue.Queue(0) jpayne@69: response_queue = queue.Queue(0) jpayne@69: jpayne@69: jpayne@69: class SocketIO(object): jpayne@69: jpayne@69: nextseq = 0 jpayne@69: jpayne@69: def __init__(self, sock, objtable=None, debugging=None): jpayne@69: self.sockthread = threading.current_thread() jpayne@69: if debugging is not None: jpayne@69: self.debugging = debugging jpayne@69: self.sock = sock jpayne@69: if objtable is None: jpayne@69: objtable = objecttable jpayne@69: self.objtable = objtable jpayne@69: self.responses = {} jpayne@69: self.cvars = {} jpayne@69: jpayne@69: def close(self): jpayne@69: sock = self.sock jpayne@69: self.sock = None jpayne@69: if sock is not None: jpayne@69: sock.close() jpayne@69: jpayne@69: def exithook(self): jpayne@69: "override for specific exit action" jpayne@69: os._exit(0) jpayne@69: jpayne@69: def debug(self, *args): jpayne@69: if not self.debugging: jpayne@69: return jpayne@69: s = self.location + " " + str(threading.current_thread().name) jpayne@69: for a in args: jpayne@69: s = s + " " + str(a) jpayne@69: print(s, file=sys.__stderr__) jpayne@69: jpayne@69: def register(self, oid, object): jpayne@69: self.objtable[oid] = object jpayne@69: jpayne@69: def unregister(self, oid): jpayne@69: try: jpayne@69: del self.objtable[oid] jpayne@69: except KeyError: jpayne@69: pass jpayne@69: jpayne@69: def localcall(self, seq, request): jpayne@69: self.debug("localcall:", request) jpayne@69: try: jpayne@69: how, (oid, methodname, args, kwargs) = request jpayne@69: except TypeError: jpayne@69: return ("ERROR", "Bad request format") jpayne@69: if oid not in self.objtable: jpayne@69: return ("ERROR", "Unknown object id: %r" % (oid,)) jpayne@69: obj = self.objtable[oid] jpayne@69: if methodname == "__methods__": jpayne@69: methods = {} jpayne@69: _getmethods(obj, methods) jpayne@69: return ("OK", methods) jpayne@69: if methodname == "__attributes__": jpayne@69: attributes = {} jpayne@69: _getattributes(obj, attributes) jpayne@69: return ("OK", attributes) jpayne@69: if not hasattr(obj, methodname): jpayne@69: return ("ERROR", "Unsupported method name: %r" % (methodname,)) jpayne@69: method = getattr(obj, methodname) jpayne@69: try: jpayne@69: if how == 'CALL': jpayne@69: ret = method(*args, **kwargs) jpayne@69: if isinstance(ret, RemoteObject): jpayne@69: ret = remoteref(ret) jpayne@69: return ("OK", ret) jpayne@69: elif how == 'QUEUE': jpayne@69: request_queue.put((seq, (method, args, kwargs))) jpayne@69: return("QUEUED", None) jpayne@69: else: jpayne@69: return ("ERROR", "Unsupported message type: %s" % how) jpayne@69: except SystemExit: jpayne@69: raise jpayne@69: except KeyboardInterrupt: jpayne@69: raise jpayne@69: except OSError: jpayne@69: raise jpayne@69: except Exception as ex: jpayne@69: return ("CALLEXC", ex) jpayne@69: except: jpayne@69: msg = "*** Internal Error: rpc.py:SocketIO.localcall()\n\n"\ jpayne@69: " Object: %s \n Method: %s \n Args: %s\n" jpayne@69: print(msg % (oid, method, args), file=sys.__stderr__) jpayne@69: traceback.print_exc(file=sys.__stderr__) jpayne@69: return ("EXCEPTION", None) jpayne@69: jpayne@69: def remotecall(self, oid, methodname, args, kwargs): jpayne@69: self.debug("remotecall:asynccall: ", oid, methodname) jpayne@69: seq = self.asynccall(oid, methodname, args, kwargs) jpayne@69: return self.asyncreturn(seq) jpayne@69: jpayne@69: def remotequeue(self, oid, methodname, args, kwargs): jpayne@69: self.debug("remotequeue:asyncqueue: ", oid, methodname) jpayne@69: seq = self.asyncqueue(oid, methodname, args, kwargs) jpayne@69: return self.asyncreturn(seq) jpayne@69: jpayne@69: def asynccall(self, oid, methodname, args, kwargs): jpayne@69: request = ("CALL", (oid, methodname, args, kwargs)) jpayne@69: seq = self.newseq() jpayne@69: if threading.current_thread() != self.sockthread: jpayne@69: cvar = threading.Condition() jpayne@69: self.cvars[seq] = cvar jpayne@69: self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs) jpayne@69: self.putmessage((seq, request)) jpayne@69: return seq jpayne@69: jpayne@69: def asyncqueue(self, oid, methodname, args, kwargs): jpayne@69: request = ("QUEUE", (oid, methodname, args, kwargs)) jpayne@69: seq = self.newseq() jpayne@69: if threading.current_thread() != self.sockthread: jpayne@69: cvar = threading.Condition() jpayne@69: self.cvars[seq] = cvar jpayne@69: self.debug(("asyncqueue:%d:" % seq), oid, methodname, args, kwargs) jpayne@69: self.putmessage((seq, request)) jpayne@69: return seq jpayne@69: jpayne@69: def asyncreturn(self, seq): jpayne@69: self.debug("asyncreturn:%d:call getresponse(): " % seq) jpayne@69: response = self.getresponse(seq, wait=0.05) jpayne@69: self.debug(("asyncreturn:%d:response: " % seq), response) jpayne@69: return self.decoderesponse(response) jpayne@69: jpayne@69: def decoderesponse(self, response): jpayne@69: how, what = response jpayne@69: if how == "OK": jpayne@69: return what jpayne@69: if how == "QUEUED": jpayne@69: return None jpayne@69: if how == "EXCEPTION": jpayne@69: self.debug("decoderesponse: EXCEPTION") jpayne@69: return None jpayne@69: if how == "EOF": jpayne@69: self.debug("decoderesponse: EOF") jpayne@69: self.decode_interrupthook() jpayne@69: return None jpayne@69: if how == "ERROR": jpayne@69: self.debug("decoderesponse: Internal ERROR:", what) jpayne@69: raise RuntimeError(what) jpayne@69: if how == "CALLEXC": jpayne@69: self.debug("decoderesponse: Call Exception:", what) jpayne@69: raise what jpayne@69: raise SystemError(how, what) jpayne@69: jpayne@69: def decode_interrupthook(self): jpayne@69: "" jpayne@69: raise EOFError jpayne@69: jpayne@69: def mainloop(self): jpayne@69: """Listen on socket until I/O not ready or EOF jpayne@69: jpayne@69: pollresponse() will loop looking for seq number None, which jpayne@69: never comes, and exit on EOFError. jpayne@69: jpayne@69: """ jpayne@69: try: jpayne@69: self.getresponse(myseq=None, wait=0.05) jpayne@69: except EOFError: jpayne@69: self.debug("mainloop:return") jpayne@69: return jpayne@69: jpayne@69: def getresponse(self, myseq, wait): jpayne@69: response = self._getresponse(myseq, wait) jpayne@69: if response is not None: jpayne@69: how, what = response jpayne@69: if how == "OK": jpayne@69: response = how, self._proxify(what) jpayne@69: return response jpayne@69: jpayne@69: def _proxify(self, obj): jpayne@69: if isinstance(obj, RemoteProxy): jpayne@69: return RPCProxy(self, obj.oid) jpayne@69: if isinstance(obj, list): jpayne@69: return list(map(self._proxify, obj)) jpayne@69: # XXX Check for other types -- not currently needed jpayne@69: return obj jpayne@69: jpayne@69: def _getresponse(self, myseq, wait): jpayne@69: self.debug("_getresponse:myseq:", myseq) jpayne@69: if threading.current_thread() is self.sockthread: jpayne@69: # this thread does all reading of requests or responses jpayne@69: while 1: jpayne@69: response = self.pollresponse(myseq, wait) jpayne@69: if response is not None: jpayne@69: return response jpayne@69: else: jpayne@69: # wait for notification from socket handling thread jpayne@69: cvar = self.cvars[myseq] jpayne@69: cvar.acquire() jpayne@69: while myseq not in self.responses: jpayne@69: cvar.wait() jpayne@69: response = self.responses[myseq] jpayne@69: self.debug("_getresponse:%s: thread woke up: response: %s" % jpayne@69: (myseq, response)) jpayne@69: del self.responses[myseq] jpayne@69: del self.cvars[myseq] jpayne@69: cvar.release() jpayne@69: return response jpayne@69: jpayne@69: def newseq(self): jpayne@69: self.nextseq = seq = self.nextseq + 2 jpayne@69: return seq jpayne@69: jpayne@69: def putmessage(self, message): jpayne@69: self.debug("putmessage:%d:" % message[0]) jpayne@69: try: jpayne@69: s = dumps(message) jpayne@69: except pickle.PicklingError: jpayne@69: print("Cannot pickle:", repr(message), file=sys.__stderr__) jpayne@69: raise jpayne@69: s = struct.pack(" 0: jpayne@69: try: jpayne@69: r, w, x = select.select([], [self.sock], []) jpayne@69: n = self.sock.send(s[:BUFSIZE]) jpayne@69: except (AttributeError, TypeError): jpayne@69: raise OSError("socket no longer exists") jpayne@69: s = s[n:] jpayne@69: jpayne@69: buff = b'' jpayne@69: bufneed = 4 jpayne@69: bufstate = 0 # meaning: 0 => reading count; 1 => reading data jpayne@69: jpayne@69: def pollpacket(self, wait): jpayne@69: self._stage0() jpayne@69: if len(self.buff) < self.bufneed: jpayne@69: r, w, x = select.select([self.sock.fileno()], [], [], wait) jpayne@69: if len(r) == 0: jpayne@69: return None jpayne@69: try: jpayne@69: s = self.sock.recv(BUFSIZE) jpayne@69: except OSError: jpayne@69: raise EOFError jpayne@69: if len(s) == 0: jpayne@69: raise EOFError jpayne@69: self.buff += s jpayne@69: self._stage0() jpayne@69: return self._stage1() jpayne@69: jpayne@69: def _stage0(self): jpayne@69: if self.bufstate == 0 and len(self.buff) >= 4: jpayne@69: s = self.buff[:4] jpayne@69: self.buff = self.buff[4:] jpayne@69: self.bufneed = struct.unpack("= self.bufneed: jpayne@69: packet = self.buff[:self.bufneed] jpayne@69: self.buff = self.buff[self.bufneed:] jpayne@69: self.bufneed = 4 jpayne@69: self.bufstate = 0 jpayne@69: return packet jpayne@69: jpayne@69: def pollmessage(self, wait): jpayne@69: packet = self.pollpacket(wait) jpayne@69: if packet is None: jpayne@69: return None jpayne@69: try: jpayne@69: message = pickle.loads(packet) jpayne@69: except pickle.UnpicklingError: jpayne@69: print("-----------------------", file=sys.__stderr__) jpayne@69: print("cannot unpickle packet:", repr(packet), file=sys.__stderr__) jpayne@69: traceback.print_stack(file=sys.__stderr__) jpayne@69: print("-----------------------", file=sys.__stderr__) jpayne@69: raise jpayne@69: return message jpayne@69: jpayne@69: def pollresponse(self, myseq, wait): jpayne@69: """Handle messages received on the socket. jpayne@69: jpayne@69: Some messages received may be asynchronous 'call' or 'queue' requests, jpayne@69: and some may be responses for other threads. jpayne@69: jpayne@69: 'call' requests are passed to self.localcall() with the expectation of jpayne@69: immediate execution, during which time the socket is not serviced. jpayne@69: jpayne@69: 'queue' requests are used for tasks (which may block or hang) to be jpayne@69: processed in a different thread. These requests are fed into jpayne@69: request_queue by self.localcall(). Responses to queued requests are jpayne@69: taken from response_queue and sent across the link with the associated jpayne@69: sequence numbers. Messages in the queues are (sequence_number, jpayne@69: request/response) tuples and code using this module removing messages jpayne@69: from the request_queue is responsible for returning the correct jpayne@69: sequence number in the response_queue. jpayne@69: jpayne@69: pollresponse() will loop until a response message with the myseq jpayne@69: sequence number is received, and will save other responses in jpayne@69: self.responses and notify the owning thread. jpayne@69: jpayne@69: """ jpayne@69: while 1: jpayne@69: # send queued response if there is one available jpayne@69: try: jpayne@69: qmsg = response_queue.get(0) jpayne@69: except queue.Empty: jpayne@69: pass jpayne@69: else: jpayne@69: seq, response = qmsg jpayne@69: message = (seq, ('OK', response)) jpayne@69: self.putmessage(message) jpayne@69: # poll for message on link jpayne@69: try: jpayne@69: message = self.pollmessage(wait) jpayne@69: if message is None: # socket not ready jpayne@69: return None jpayne@69: except EOFError: jpayne@69: self.handle_EOF() jpayne@69: return None jpayne@69: except AttributeError: jpayne@69: return None jpayne@69: seq, resq = message jpayne@69: how = resq[0] jpayne@69: self.debug("pollresponse:%d:myseq:%s" % (seq, myseq)) jpayne@69: # process or queue a request jpayne@69: if how in ("CALL", "QUEUE"): jpayne@69: self.debug("pollresponse:%d:localcall:call:" % seq) jpayne@69: response = self.localcall(seq, resq) jpayne@69: self.debug("pollresponse:%d:localcall:response:%s" jpayne@69: % (seq, response)) jpayne@69: if how == "CALL": jpayne@69: self.putmessage((seq, response)) jpayne@69: elif how == "QUEUE": jpayne@69: # don't acknowledge the 'queue' request! jpayne@69: pass jpayne@69: continue jpayne@69: # return if completed message transaction jpayne@69: elif seq == myseq: jpayne@69: return resq jpayne@69: # must be a response for a different thread: jpayne@69: else: jpayne@69: cv = self.cvars.get(seq, None) jpayne@69: # response involving unknown sequence number is discarded, jpayne@69: # probably intended for prior incarnation of server jpayne@69: if cv is not None: jpayne@69: cv.acquire() jpayne@69: self.responses[seq] = resq jpayne@69: cv.notify() jpayne@69: cv.release() jpayne@69: continue jpayne@69: jpayne@69: def handle_EOF(self): jpayne@69: "action taken upon link being closed by peer" jpayne@69: self.EOFhook() jpayne@69: self.debug("handle_EOF") jpayne@69: for key in self.cvars: jpayne@69: cv = self.cvars[key] jpayne@69: cv.acquire() jpayne@69: self.responses[key] = ('EOF', None) jpayne@69: cv.notify() jpayne@69: cv.release() jpayne@69: # call our (possibly overridden) exit function jpayne@69: self.exithook() jpayne@69: jpayne@69: def EOFhook(self): jpayne@69: "Classes using rpc client/server can override to augment EOF action" jpayne@69: pass jpayne@69: jpayne@69: #----------------- end class SocketIO -------------------- jpayne@69: jpayne@69: class RemoteObject(object): jpayne@69: # Token mix-in class jpayne@69: pass jpayne@69: jpayne@69: jpayne@69: def remoteref(obj): jpayne@69: oid = id(obj) jpayne@69: objecttable[oid] = obj jpayne@69: return RemoteProxy(oid) jpayne@69: jpayne@69: jpayne@69: class RemoteProxy(object): jpayne@69: jpayne@69: def __init__(self, oid): jpayne@69: self.oid = oid jpayne@69: jpayne@69: jpayne@69: class RPCHandler(socketserver.BaseRequestHandler, SocketIO): jpayne@69: jpayne@69: debugging = False jpayne@69: location = "#S" # Server jpayne@69: jpayne@69: def __init__(self, sock, addr, svr): jpayne@69: svr.current_handler = self ## cgt xxx jpayne@69: SocketIO.__init__(self, sock) jpayne@69: socketserver.BaseRequestHandler.__init__(self, sock, addr, svr) jpayne@69: jpayne@69: def handle(self): jpayne@69: "handle() method required by socketserver" jpayne@69: self.mainloop() jpayne@69: jpayne@69: def get_remote_proxy(self, oid): jpayne@69: return RPCProxy(self, oid) jpayne@69: jpayne@69: jpayne@69: class RPCClient(SocketIO): jpayne@69: jpayne@69: debugging = False jpayne@69: location = "#C" # Client jpayne@69: jpayne@69: nextseq = 1 # Requests coming from the client are odd numbered jpayne@69: jpayne@69: def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM): jpayne@69: self.listening_sock = socket.socket(family, type) jpayne@69: self.listening_sock.bind(address) jpayne@69: self.listening_sock.listen(1) jpayne@69: jpayne@69: def accept(self): jpayne@69: working_sock, address = self.listening_sock.accept() jpayne@69: if self.debugging: jpayne@69: print("****** Connection request from ", address, file=sys.__stderr__) jpayne@69: if address[0] == LOCALHOST: jpayne@69: SocketIO.__init__(self, working_sock) jpayne@69: else: jpayne@69: print("** Invalid host: ", address, file=sys.__stderr__) jpayne@69: raise OSError jpayne@69: jpayne@69: def get_remote_proxy(self, oid): jpayne@69: return RPCProxy(self, oid) jpayne@69: jpayne@69: jpayne@69: class RPCProxy(object): jpayne@69: jpayne@69: __methods = None jpayne@69: __attributes = None jpayne@69: jpayne@69: def __init__(self, sockio, oid): jpayne@69: self.sockio = sockio jpayne@69: self.oid = oid jpayne@69: jpayne@69: def __getattr__(self, name): jpayne@69: if self.__methods is None: jpayne@69: self.__getmethods() jpayne@69: if self.__methods.get(name): jpayne@69: return MethodProxy(self.sockio, self.oid, name) jpayne@69: if self.__attributes is None: jpayne@69: self.__getattributes() jpayne@69: if name in self.__attributes: jpayne@69: value = self.sockio.remotecall(self.oid, '__getattribute__', jpayne@69: (name,), {}) jpayne@69: return value jpayne@69: else: jpayne@69: raise AttributeError(name) jpayne@69: jpayne@69: def __getattributes(self): jpayne@69: self.__attributes = self.sockio.remotecall(self.oid, jpayne@69: "__attributes__", (), {}) jpayne@69: jpayne@69: def __getmethods(self): jpayne@69: self.__methods = self.sockio.remotecall(self.oid, jpayne@69: "__methods__", (), {}) jpayne@69: jpayne@69: def _getmethods(obj, methods): jpayne@69: # Helper to get a list of methods from an object jpayne@69: # Adds names to dictionary argument 'methods' jpayne@69: for name in dir(obj): jpayne@69: attr = getattr(obj, name) jpayne@69: if callable(attr): jpayne@69: methods[name] = 1 jpayne@69: if isinstance(obj, type): jpayne@69: for super in obj.__bases__: jpayne@69: _getmethods(super, methods) jpayne@69: jpayne@69: def _getattributes(obj, attributes): jpayne@69: for name in dir(obj): jpayne@69: attr = getattr(obj, name) jpayne@69: if not callable(attr): jpayne@69: attributes[name] = 1 jpayne@69: jpayne@69: jpayne@69: class MethodProxy(object): jpayne@69: jpayne@69: def __init__(self, sockio, oid, name): jpayne@69: self.sockio = sockio jpayne@69: self.oid = oid jpayne@69: self.name = name jpayne@69: jpayne@69: def __call__(self, /, *args, **kwargs): jpayne@69: value = self.sockio.remotecall(self.oid, self.name, args, kwargs) jpayne@69: return value jpayne@69: jpayne@69: jpayne@69: # XXX KBK 09Sep03 We need a proper unit test for this module. Previously jpayne@69: # existing test code was removed at Rev 1.27 (r34098). jpayne@69: jpayne@69: def displayhook(value): jpayne@69: """Override standard display hook to use non-locale encoding""" jpayne@69: if value is None: jpayne@69: return jpayne@69: # Set '_' to None to avoid recursion jpayne@69: builtins._ = None jpayne@69: text = repr(value) jpayne@69: try: jpayne@69: sys.stdout.write(text) jpayne@69: except UnicodeEncodeError: jpayne@69: # let's use ascii while utf8-bmp codec doesn't present jpayne@69: encoding = 'ascii' jpayne@69: bytes = text.encode(encoding, 'backslashreplace') jpayne@69: text = bytes.decode(encoding, 'strict') jpayne@69: sys.stdout.write(text) jpayne@69: sys.stdout.write("\n") jpayne@69: builtins._ = value jpayne@69: jpayne@69: jpayne@69: if __name__ == '__main__': jpayne@69: from unittest import main jpayne@69: main('idlelib.idle_test.test_rpc', verbosity=2,)