annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/idlelib/rpc.py @ 68:5028fdace37b

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 16:23:26 -0400
parents
children
rev   line source
jpayne@68 1 """RPC Implementation, originally written for the Python Idle IDE
jpayne@68 2
jpayne@68 3 For security reasons, GvR requested that Idle's Python execution server process
jpayne@68 4 connect to the Idle process, which listens for the connection. Since Idle has
jpayne@68 5 only one client per server, this was not a limitation.
jpayne@68 6
jpayne@68 7 +---------------------------------+ +-------------+
jpayne@68 8 | socketserver.BaseRequestHandler | | SocketIO |
jpayne@68 9 +---------------------------------+ +-------------+
jpayne@68 10 ^ | register() |
jpayne@68 11 | | unregister()|
jpayne@68 12 | +-------------+
jpayne@68 13 | ^ ^
jpayne@68 14 | | |
jpayne@68 15 | + -------------------+ |
jpayne@68 16 | | |
jpayne@68 17 +-------------------------+ +-----------------+
jpayne@68 18 | RPCHandler | | RPCClient |
jpayne@68 19 | [attribute of RPCServer]| | |
jpayne@68 20 +-------------------------+ +-----------------+
jpayne@68 21
jpayne@68 22 The RPCServer handler class is expected to provide register/unregister methods.
jpayne@68 23 RPCHandler inherits the mix-in class SocketIO, which provides these methods.
jpayne@68 24
jpayne@68 25 See the Idle run.main() docstring for further information on how this was
jpayne@68 26 accomplished in Idle.
jpayne@68 27
jpayne@68 28 """
jpayne@68 29 import builtins
jpayne@68 30 import copyreg
jpayne@68 31 import io
jpayne@68 32 import marshal
jpayne@68 33 import os
jpayne@68 34 import pickle
jpayne@68 35 import queue
jpayne@68 36 import select
jpayne@68 37 import socket
jpayne@68 38 import socketserver
jpayne@68 39 import struct
jpayne@68 40 import sys
jpayne@68 41 import threading
jpayne@68 42 import traceback
jpayne@68 43 import types
jpayne@68 44
jpayne@68 45 def unpickle_code(ms):
jpayne@68 46 "Return code object from marshal string ms."
jpayne@68 47 co = marshal.loads(ms)
jpayne@68 48 assert isinstance(co, types.CodeType)
jpayne@68 49 return co
jpayne@68 50
jpayne@68 51 def pickle_code(co):
jpayne@68 52 "Return unpickle function and tuple with marshalled co code object."
jpayne@68 53 assert isinstance(co, types.CodeType)
jpayne@68 54 ms = marshal.dumps(co)
jpayne@68 55 return unpickle_code, (ms,)
jpayne@68 56
jpayne@68 57 def dumps(obj, protocol=None):
jpayne@68 58 "Return pickled (or marshalled) string for obj."
jpayne@68 59 # IDLE passes 'None' to select pickle.DEFAULT_PROTOCOL.
jpayne@68 60 f = io.BytesIO()
jpayne@68 61 p = CodePickler(f, protocol)
jpayne@68 62 p.dump(obj)
jpayne@68 63 return f.getvalue()
jpayne@68 64
jpayne@68 65
jpayne@68 66 class CodePickler(pickle.Pickler):
jpayne@68 67 dispatch_table = {types.CodeType: pickle_code, **copyreg.dispatch_table}
jpayne@68 68
jpayne@68 69
jpayne@68 70 BUFSIZE = 8*1024
jpayne@68 71 LOCALHOST = '127.0.0.1'
jpayne@68 72
jpayne@68 73 class RPCServer(socketserver.TCPServer):
jpayne@68 74
jpayne@68 75 def __init__(self, addr, handlerclass=None):
jpayne@68 76 if handlerclass is None:
jpayne@68 77 handlerclass = RPCHandler
jpayne@68 78 socketserver.TCPServer.__init__(self, addr, handlerclass)
jpayne@68 79
jpayne@68 80 def server_bind(self):
jpayne@68 81 "Override TCPServer method, no bind() phase for connecting entity"
jpayne@68 82 pass
jpayne@68 83
jpayne@68 84 def server_activate(self):
jpayne@68 85 """Override TCPServer method, connect() instead of listen()
jpayne@68 86
jpayne@68 87 Due to the reversed connection, self.server_address is actually the
jpayne@68 88 address of the Idle Client to which we are connecting.
jpayne@68 89
jpayne@68 90 """
jpayne@68 91 self.socket.connect(self.server_address)
jpayne@68 92
jpayne@68 93 def get_request(self):
jpayne@68 94 "Override TCPServer method, return already connected socket"
jpayne@68 95 return self.socket, self.server_address
jpayne@68 96
jpayne@68 97 def handle_error(self, request, client_address):
jpayne@68 98 """Override TCPServer method
jpayne@68 99
jpayne@68 100 Error message goes to __stderr__. No error message if exiting
jpayne@68 101 normally or socket raised EOF. Other exceptions not handled in
jpayne@68 102 server code will cause os._exit.
jpayne@68 103
jpayne@68 104 """
jpayne@68 105 try:
jpayne@68 106 raise
jpayne@68 107 except SystemExit:
jpayne@68 108 raise
jpayne@68 109 except:
jpayne@68 110 erf = sys.__stderr__
jpayne@68 111 print('\n' + '-'*40, file=erf)
jpayne@68 112 print('Unhandled server exception!', file=erf)
jpayne@68 113 print('Thread: %s' % threading.current_thread().name, file=erf)
jpayne@68 114 print('Client Address: ', client_address, file=erf)
jpayne@68 115 print('Request: ', repr(request), file=erf)
jpayne@68 116 traceback.print_exc(file=erf)
jpayne@68 117 print('\n*** Unrecoverable, server exiting!', file=erf)
jpayne@68 118 print('-'*40, file=erf)
jpayne@68 119 os._exit(0)
jpayne@68 120
jpayne@68 121 #----------------- end class RPCServer --------------------
jpayne@68 122
jpayne@68 123 objecttable = {}
jpayne@68 124 request_queue = queue.Queue(0)
jpayne@68 125 response_queue = queue.Queue(0)
jpayne@68 126
jpayne@68 127
jpayne@68 128 class SocketIO(object):
jpayne@68 129
jpayne@68 130 nextseq = 0
jpayne@68 131
jpayne@68 132 def __init__(self, sock, objtable=None, debugging=None):
jpayne@68 133 self.sockthread = threading.current_thread()
jpayne@68 134 if debugging is not None:
jpayne@68 135 self.debugging = debugging
jpayne@68 136 self.sock = sock
jpayne@68 137 if objtable is None:
jpayne@68 138 objtable = objecttable
jpayne@68 139 self.objtable = objtable
jpayne@68 140 self.responses = {}
jpayne@68 141 self.cvars = {}
jpayne@68 142
jpayne@68 143 def close(self):
jpayne@68 144 sock = self.sock
jpayne@68 145 self.sock = None
jpayne@68 146 if sock is not None:
jpayne@68 147 sock.close()
jpayne@68 148
jpayne@68 149 def exithook(self):
jpayne@68 150 "override for specific exit action"
jpayne@68 151 os._exit(0)
jpayne@68 152
jpayne@68 153 def debug(self, *args):
jpayne@68 154 if not self.debugging:
jpayne@68 155 return
jpayne@68 156 s = self.location + " " + str(threading.current_thread().name)
jpayne@68 157 for a in args:
jpayne@68 158 s = s + " " + str(a)
jpayne@68 159 print(s, file=sys.__stderr__)
jpayne@68 160
jpayne@68 161 def register(self, oid, object):
jpayne@68 162 self.objtable[oid] = object
jpayne@68 163
jpayne@68 164 def unregister(self, oid):
jpayne@68 165 try:
jpayne@68 166 del self.objtable[oid]
jpayne@68 167 except KeyError:
jpayne@68 168 pass
jpayne@68 169
jpayne@68 170 def localcall(self, seq, request):
jpayne@68 171 self.debug("localcall:", request)
jpayne@68 172 try:
jpayne@68 173 how, (oid, methodname, args, kwargs) = request
jpayne@68 174 except TypeError:
jpayne@68 175 return ("ERROR", "Bad request format")
jpayne@68 176 if oid not in self.objtable:
jpayne@68 177 return ("ERROR", "Unknown object id: %r" % (oid,))
jpayne@68 178 obj = self.objtable[oid]
jpayne@68 179 if methodname == "__methods__":
jpayne@68 180 methods = {}
jpayne@68 181 _getmethods(obj, methods)
jpayne@68 182 return ("OK", methods)
jpayne@68 183 if methodname == "__attributes__":
jpayne@68 184 attributes = {}
jpayne@68 185 _getattributes(obj, attributes)
jpayne@68 186 return ("OK", attributes)
jpayne@68 187 if not hasattr(obj, methodname):
jpayne@68 188 return ("ERROR", "Unsupported method name: %r" % (methodname,))
jpayne@68 189 method = getattr(obj, methodname)
jpayne@68 190 try:
jpayne@68 191 if how == 'CALL':
jpayne@68 192 ret = method(*args, **kwargs)
jpayne@68 193 if isinstance(ret, RemoteObject):
jpayne@68 194 ret = remoteref(ret)
jpayne@68 195 return ("OK", ret)
jpayne@68 196 elif how == 'QUEUE':
jpayne@68 197 request_queue.put((seq, (method, args, kwargs)))
jpayne@68 198 return("QUEUED", None)
jpayne@68 199 else:
jpayne@68 200 return ("ERROR", "Unsupported message type: %s" % how)
jpayne@68 201 except SystemExit:
jpayne@68 202 raise
jpayne@68 203 except KeyboardInterrupt:
jpayne@68 204 raise
jpayne@68 205 except OSError:
jpayne@68 206 raise
jpayne@68 207 except Exception as ex:
jpayne@68 208 return ("CALLEXC", ex)
jpayne@68 209 except:
jpayne@68 210 msg = "*** Internal Error: rpc.py:SocketIO.localcall()\n\n"\
jpayne@68 211 " Object: %s \n Method: %s \n Args: %s\n"
jpayne@68 212 print(msg % (oid, method, args), file=sys.__stderr__)
jpayne@68 213 traceback.print_exc(file=sys.__stderr__)
jpayne@68 214 return ("EXCEPTION", None)
jpayne@68 215
jpayne@68 216 def remotecall(self, oid, methodname, args, kwargs):
jpayne@68 217 self.debug("remotecall:asynccall: ", oid, methodname)
jpayne@68 218 seq = self.asynccall(oid, methodname, args, kwargs)
jpayne@68 219 return self.asyncreturn(seq)
jpayne@68 220
jpayne@68 221 def remotequeue(self, oid, methodname, args, kwargs):
jpayne@68 222 self.debug("remotequeue:asyncqueue: ", oid, methodname)
jpayne@68 223 seq = self.asyncqueue(oid, methodname, args, kwargs)
jpayne@68 224 return self.asyncreturn(seq)
jpayne@68 225
jpayne@68 226 def asynccall(self, oid, methodname, args, kwargs):
jpayne@68 227 request = ("CALL", (oid, methodname, args, kwargs))
jpayne@68 228 seq = self.newseq()
jpayne@68 229 if threading.current_thread() != self.sockthread:
jpayne@68 230 cvar = threading.Condition()
jpayne@68 231 self.cvars[seq] = cvar
jpayne@68 232 self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
jpayne@68 233 self.putmessage((seq, request))
jpayne@68 234 return seq
jpayne@68 235
jpayne@68 236 def asyncqueue(self, oid, methodname, args, kwargs):
jpayne@68 237 request = ("QUEUE", (oid, methodname, args, kwargs))
jpayne@68 238 seq = self.newseq()
jpayne@68 239 if threading.current_thread() != self.sockthread:
jpayne@68 240 cvar = threading.Condition()
jpayne@68 241 self.cvars[seq] = cvar
jpayne@68 242 self.debug(("asyncqueue:%d:" % seq), oid, methodname, args, kwargs)
jpayne@68 243 self.putmessage((seq, request))
jpayne@68 244 return seq
jpayne@68 245
jpayne@68 246 def asyncreturn(self, seq):
jpayne@68 247 self.debug("asyncreturn:%d:call getresponse(): " % seq)
jpayne@68 248 response = self.getresponse(seq, wait=0.05)
jpayne@68 249 self.debug(("asyncreturn:%d:response: " % seq), response)
jpayne@68 250 return self.decoderesponse(response)
jpayne@68 251
jpayne@68 252 def decoderesponse(self, response):
jpayne@68 253 how, what = response
jpayne@68 254 if how == "OK":
jpayne@68 255 return what
jpayne@68 256 if how == "QUEUED":
jpayne@68 257 return None
jpayne@68 258 if how == "EXCEPTION":
jpayne@68 259 self.debug("decoderesponse: EXCEPTION")
jpayne@68 260 return None
jpayne@68 261 if how == "EOF":
jpayne@68 262 self.debug("decoderesponse: EOF")
jpayne@68 263 self.decode_interrupthook()
jpayne@68 264 return None
jpayne@68 265 if how == "ERROR":
jpayne@68 266 self.debug("decoderesponse: Internal ERROR:", what)
jpayne@68 267 raise RuntimeError(what)
jpayne@68 268 if how == "CALLEXC":
jpayne@68 269 self.debug("decoderesponse: Call Exception:", what)
jpayne@68 270 raise what
jpayne@68 271 raise SystemError(how, what)
jpayne@68 272
jpayne@68 273 def decode_interrupthook(self):
jpayne@68 274 ""
jpayne@68 275 raise EOFError
jpayne@68 276
jpayne@68 277 def mainloop(self):
jpayne@68 278 """Listen on socket until I/O not ready or EOF
jpayne@68 279
jpayne@68 280 pollresponse() will loop looking for seq number None, which
jpayne@68 281 never comes, and exit on EOFError.
jpayne@68 282
jpayne@68 283 """
jpayne@68 284 try:
jpayne@68 285 self.getresponse(myseq=None, wait=0.05)
jpayne@68 286 except EOFError:
jpayne@68 287 self.debug("mainloop:return")
jpayne@68 288 return
jpayne@68 289
jpayne@68 290 def getresponse(self, myseq, wait):
jpayne@68 291 response = self._getresponse(myseq, wait)
jpayne@68 292 if response is not None:
jpayne@68 293 how, what = response
jpayne@68 294 if how == "OK":
jpayne@68 295 response = how, self._proxify(what)
jpayne@68 296 return response
jpayne@68 297
jpayne@68 298 def _proxify(self, obj):
jpayne@68 299 if isinstance(obj, RemoteProxy):
jpayne@68 300 return RPCProxy(self, obj.oid)
jpayne@68 301 if isinstance(obj, list):
jpayne@68 302 return list(map(self._proxify, obj))
jpayne@68 303 # XXX Check for other types -- not currently needed
jpayne@68 304 return obj
jpayne@68 305
jpayne@68 306 def _getresponse(self, myseq, wait):
jpayne@68 307 self.debug("_getresponse:myseq:", myseq)
jpayne@68 308 if threading.current_thread() is self.sockthread:
jpayne@68 309 # this thread does all reading of requests or responses
jpayne@68 310 while 1:
jpayne@68 311 response = self.pollresponse(myseq, wait)
jpayne@68 312 if response is not None:
jpayne@68 313 return response
jpayne@68 314 else:
jpayne@68 315 # wait for notification from socket handling thread
jpayne@68 316 cvar = self.cvars[myseq]
jpayne@68 317 cvar.acquire()
jpayne@68 318 while myseq not in self.responses:
jpayne@68 319 cvar.wait()
jpayne@68 320 response = self.responses[myseq]
jpayne@68 321 self.debug("_getresponse:%s: thread woke up: response: %s" %
jpayne@68 322 (myseq, response))
jpayne@68 323 del self.responses[myseq]
jpayne@68 324 del self.cvars[myseq]
jpayne@68 325 cvar.release()
jpayne@68 326 return response
jpayne@68 327
jpayne@68 328 def newseq(self):
jpayne@68 329 self.nextseq = seq = self.nextseq + 2
jpayne@68 330 return seq
jpayne@68 331
jpayne@68 332 def putmessage(self, message):
jpayne@68 333 self.debug("putmessage:%d:" % message[0])
jpayne@68 334 try:
jpayne@68 335 s = dumps(message)
jpayne@68 336 except pickle.PicklingError:
jpayne@68 337 print("Cannot pickle:", repr(message), file=sys.__stderr__)
jpayne@68 338 raise
jpayne@68 339 s = struct.pack("<i", len(s)) + s
jpayne@68 340 while len(s) > 0:
jpayne@68 341 try:
jpayne@68 342 r, w, x = select.select([], [self.sock], [])
jpayne@68 343 n = self.sock.send(s[:BUFSIZE])
jpayne@68 344 except (AttributeError, TypeError):
jpayne@68 345 raise OSError("socket no longer exists")
jpayne@68 346 s = s[n:]
jpayne@68 347
jpayne@68 348 buff = b''
jpayne@68 349 bufneed = 4
jpayne@68 350 bufstate = 0 # meaning: 0 => reading count; 1 => reading data
jpayne@68 351
jpayne@68 352 def pollpacket(self, wait):
jpayne@68 353 self._stage0()
jpayne@68 354 if len(self.buff) < self.bufneed:
jpayne@68 355 r, w, x = select.select([self.sock.fileno()], [], [], wait)
jpayne@68 356 if len(r) == 0:
jpayne@68 357 return None
jpayne@68 358 try:
jpayne@68 359 s = self.sock.recv(BUFSIZE)
jpayne@68 360 except OSError:
jpayne@68 361 raise EOFError
jpayne@68 362 if len(s) == 0:
jpayne@68 363 raise EOFError
jpayne@68 364 self.buff += s
jpayne@68 365 self._stage0()
jpayne@68 366 return self._stage1()
jpayne@68 367
jpayne@68 368 def _stage0(self):
jpayne@68 369 if self.bufstate == 0 and len(self.buff) >= 4:
jpayne@68 370 s = self.buff[:4]
jpayne@68 371 self.buff = self.buff[4:]
jpayne@68 372 self.bufneed = struct.unpack("<i", s)[0]
jpayne@68 373 self.bufstate = 1
jpayne@68 374
jpayne@68 375 def _stage1(self):
jpayne@68 376 if self.bufstate == 1 and len(self.buff) >= self.bufneed:
jpayne@68 377 packet = self.buff[:self.bufneed]
jpayne@68 378 self.buff = self.buff[self.bufneed:]
jpayne@68 379 self.bufneed = 4
jpayne@68 380 self.bufstate = 0
jpayne@68 381 return packet
jpayne@68 382
jpayne@68 383 def pollmessage(self, wait):
jpayne@68 384 packet = self.pollpacket(wait)
jpayne@68 385 if packet is None:
jpayne@68 386 return None
jpayne@68 387 try:
jpayne@68 388 message = pickle.loads(packet)
jpayne@68 389 except pickle.UnpicklingError:
jpayne@68 390 print("-----------------------", file=sys.__stderr__)
jpayne@68 391 print("cannot unpickle packet:", repr(packet), file=sys.__stderr__)
jpayne@68 392 traceback.print_stack(file=sys.__stderr__)
jpayne@68 393 print("-----------------------", file=sys.__stderr__)
jpayne@68 394 raise
jpayne@68 395 return message
jpayne@68 396
jpayne@68 397 def pollresponse(self, myseq, wait):
jpayne@68 398 """Handle messages received on the socket.
jpayne@68 399
jpayne@68 400 Some messages received may be asynchronous 'call' or 'queue' requests,
jpayne@68 401 and some may be responses for other threads.
jpayne@68 402
jpayne@68 403 'call' requests are passed to self.localcall() with the expectation of
jpayne@68 404 immediate execution, during which time the socket is not serviced.
jpayne@68 405
jpayne@68 406 'queue' requests are used for tasks (which may block or hang) to be
jpayne@68 407 processed in a different thread. These requests are fed into
jpayne@68 408 request_queue by self.localcall(). Responses to queued requests are
jpayne@68 409 taken from response_queue and sent across the link with the associated
jpayne@68 410 sequence numbers. Messages in the queues are (sequence_number,
jpayne@68 411 request/response) tuples and code using this module removing messages
jpayne@68 412 from the request_queue is responsible for returning the correct
jpayne@68 413 sequence number in the response_queue.
jpayne@68 414
jpayne@68 415 pollresponse() will loop until a response message with the myseq
jpayne@68 416 sequence number is received, and will save other responses in
jpayne@68 417 self.responses and notify the owning thread.
jpayne@68 418
jpayne@68 419 """
jpayne@68 420 while 1:
jpayne@68 421 # send queued response if there is one available
jpayne@68 422 try:
jpayne@68 423 qmsg = response_queue.get(0)
jpayne@68 424 except queue.Empty:
jpayne@68 425 pass
jpayne@68 426 else:
jpayne@68 427 seq, response = qmsg
jpayne@68 428 message = (seq, ('OK', response))
jpayne@68 429 self.putmessage(message)
jpayne@68 430 # poll for message on link
jpayne@68 431 try:
jpayne@68 432 message = self.pollmessage(wait)
jpayne@68 433 if message is None: # socket not ready
jpayne@68 434 return None
jpayne@68 435 except EOFError:
jpayne@68 436 self.handle_EOF()
jpayne@68 437 return None
jpayne@68 438 except AttributeError:
jpayne@68 439 return None
jpayne@68 440 seq, resq = message
jpayne@68 441 how = resq[0]
jpayne@68 442 self.debug("pollresponse:%d:myseq:%s" % (seq, myseq))
jpayne@68 443 # process or queue a request
jpayne@68 444 if how in ("CALL", "QUEUE"):
jpayne@68 445 self.debug("pollresponse:%d:localcall:call:" % seq)
jpayne@68 446 response = self.localcall(seq, resq)
jpayne@68 447 self.debug("pollresponse:%d:localcall:response:%s"
jpayne@68 448 % (seq, response))
jpayne@68 449 if how == "CALL":
jpayne@68 450 self.putmessage((seq, response))
jpayne@68 451 elif how == "QUEUE":
jpayne@68 452 # don't acknowledge the 'queue' request!
jpayne@68 453 pass
jpayne@68 454 continue
jpayne@68 455 # return if completed message transaction
jpayne@68 456 elif seq == myseq:
jpayne@68 457 return resq
jpayne@68 458 # must be a response for a different thread:
jpayne@68 459 else:
jpayne@68 460 cv = self.cvars.get(seq, None)
jpayne@68 461 # response involving unknown sequence number is discarded,
jpayne@68 462 # probably intended for prior incarnation of server
jpayne@68 463 if cv is not None:
jpayne@68 464 cv.acquire()
jpayne@68 465 self.responses[seq] = resq
jpayne@68 466 cv.notify()
jpayne@68 467 cv.release()
jpayne@68 468 continue
jpayne@68 469
jpayne@68 470 def handle_EOF(self):
jpayne@68 471 "action taken upon link being closed by peer"
jpayne@68 472 self.EOFhook()
jpayne@68 473 self.debug("handle_EOF")
jpayne@68 474 for key in self.cvars:
jpayne@68 475 cv = self.cvars[key]
jpayne@68 476 cv.acquire()
jpayne@68 477 self.responses[key] = ('EOF', None)
jpayne@68 478 cv.notify()
jpayne@68 479 cv.release()
jpayne@68 480 # call our (possibly overridden) exit function
jpayne@68 481 self.exithook()
jpayne@68 482
jpayne@68 483 def EOFhook(self):
jpayne@68 484 "Classes using rpc client/server can override to augment EOF action"
jpayne@68 485 pass
jpayne@68 486
jpayne@68 487 #----------------- end class SocketIO --------------------
jpayne@68 488
jpayne@68 489 class RemoteObject(object):
jpayne@68 490 # Token mix-in class
jpayne@68 491 pass
jpayne@68 492
jpayne@68 493
jpayne@68 494 def remoteref(obj):
jpayne@68 495 oid = id(obj)
jpayne@68 496 objecttable[oid] = obj
jpayne@68 497 return RemoteProxy(oid)
jpayne@68 498
jpayne@68 499
jpayne@68 500 class RemoteProxy(object):
jpayne@68 501
jpayne@68 502 def __init__(self, oid):
jpayne@68 503 self.oid = oid
jpayne@68 504
jpayne@68 505
jpayne@68 506 class RPCHandler(socketserver.BaseRequestHandler, SocketIO):
jpayne@68 507
jpayne@68 508 debugging = False
jpayne@68 509 location = "#S" # Server
jpayne@68 510
jpayne@68 511 def __init__(self, sock, addr, svr):
jpayne@68 512 svr.current_handler = self ## cgt xxx
jpayne@68 513 SocketIO.__init__(self, sock)
jpayne@68 514 socketserver.BaseRequestHandler.__init__(self, sock, addr, svr)
jpayne@68 515
jpayne@68 516 def handle(self):
jpayne@68 517 "handle() method required by socketserver"
jpayne@68 518 self.mainloop()
jpayne@68 519
jpayne@68 520 def get_remote_proxy(self, oid):
jpayne@68 521 return RPCProxy(self, oid)
jpayne@68 522
jpayne@68 523
jpayne@68 524 class RPCClient(SocketIO):
jpayne@68 525
jpayne@68 526 debugging = False
jpayne@68 527 location = "#C" # Client
jpayne@68 528
jpayne@68 529 nextseq = 1 # Requests coming from the client are odd numbered
jpayne@68 530
jpayne@68 531 def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
jpayne@68 532 self.listening_sock = socket.socket(family, type)
jpayne@68 533 self.listening_sock.bind(address)
jpayne@68 534 self.listening_sock.listen(1)
jpayne@68 535
jpayne@68 536 def accept(self):
jpayne@68 537 working_sock, address = self.listening_sock.accept()
jpayne@68 538 if self.debugging:
jpayne@68 539 print("****** Connection request from ", address, file=sys.__stderr__)
jpayne@68 540 if address[0] == LOCALHOST:
jpayne@68 541 SocketIO.__init__(self, working_sock)
jpayne@68 542 else:
jpayne@68 543 print("** Invalid host: ", address, file=sys.__stderr__)
jpayne@68 544 raise OSError
jpayne@68 545
jpayne@68 546 def get_remote_proxy(self, oid):
jpayne@68 547 return RPCProxy(self, oid)
jpayne@68 548
jpayne@68 549
jpayne@68 550 class RPCProxy(object):
jpayne@68 551
jpayne@68 552 __methods = None
jpayne@68 553 __attributes = None
jpayne@68 554
jpayne@68 555 def __init__(self, sockio, oid):
jpayne@68 556 self.sockio = sockio
jpayne@68 557 self.oid = oid
jpayne@68 558
jpayne@68 559 def __getattr__(self, name):
jpayne@68 560 if self.__methods is None:
jpayne@68 561 self.__getmethods()
jpayne@68 562 if self.__methods.get(name):
jpayne@68 563 return MethodProxy(self.sockio, self.oid, name)
jpayne@68 564 if self.__attributes is None:
jpayne@68 565 self.__getattributes()
jpayne@68 566 if name in self.__attributes:
jpayne@68 567 value = self.sockio.remotecall(self.oid, '__getattribute__',
jpayne@68 568 (name,), {})
jpayne@68 569 return value
jpayne@68 570 else:
jpayne@68 571 raise AttributeError(name)
jpayne@68 572
jpayne@68 573 def __getattributes(self):
jpayne@68 574 self.__attributes = self.sockio.remotecall(self.oid,
jpayne@68 575 "__attributes__", (), {})
jpayne@68 576
jpayne@68 577 def __getmethods(self):
jpayne@68 578 self.__methods = self.sockio.remotecall(self.oid,
jpayne@68 579 "__methods__", (), {})
jpayne@68 580
jpayne@68 581 def _getmethods(obj, methods):
jpayne@68 582 # Helper to get a list of methods from an object
jpayne@68 583 # Adds names to dictionary argument 'methods'
jpayne@68 584 for name in dir(obj):
jpayne@68 585 attr = getattr(obj, name)
jpayne@68 586 if callable(attr):
jpayne@68 587 methods[name] = 1
jpayne@68 588 if isinstance(obj, type):
jpayne@68 589 for super in obj.__bases__:
jpayne@68 590 _getmethods(super, methods)
jpayne@68 591
jpayne@68 592 def _getattributes(obj, attributes):
jpayne@68 593 for name in dir(obj):
jpayne@68 594 attr = getattr(obj, name)
jpayne@68 595 if not callable(attr):
jpayne@68 596 attributes[name] = 1
jpayne@68 597
jpayne@68 598
jpayne@68 599 class MethodProxy(object):
jpayne@68 600
jpayne@68 601 def __init__(self, sockio, oid, name):
jpayne@68 602 self.sockio = sockio
jpayne@68 603 self.oid = oid
jpayne@68 604 self.name = name
jpayne@68 605
jpayne@68 606 def __call__(self, /, *args, **kwargs):
jpayne@68 607 value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
jpayne@68 608 return value
jpayne@68 609
jpayne@68 610
jpayne@68 611 # XXX KBK 09Sep03 We need a proper unit test for this module. Previously
jpayne@68 612 # existing test code was removed at Rev 1.27 (r34098).
jpayne@68 613
jpayne@68 614 def displayhook(value):
jpayne@68 615 """Override standard display hook to use non-locale encoding"""
jpayne@68 616 if value is None:
jpayne@68 617 return
jpayne@68 618 # Set '_' to None to avoid recursion
jpayne@68 619 builtins._ = None
jpayne@68 620 text = repr(value)
jpayne@68 621 try:
jpayne@68 622 sys.stdout.write(text)
jpayne@68 623 except UnicodeEncodeError:
jpayne@68 624 # let's use ascii while utf8-bmp codec doesn't present
jpayne@68 625 encoding = 'ascii'
jpayne@68 626 bytes = text.encode(encoding, 'backslashreplace')
jpayne@68 627 text = bytes.decode(encoding, 'strict')
jpayne@68 628 sys.stdout.write(text)
jpayne@68 629 sys.stdout.write("\n")
jpayne@68 630 builtins._ = value
jpayne@68 631
jpayne@68 632
jpayne@68 633 if __name__ == '__main__':
jpayne@68 634 from unittest import main
jpayne@68 635 main('idlelib.idle_test.test_rpc', verbosity=2,)