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