File: rpcMixin.py

package info (click to toggle)
wxpython3.0 3.0.1.1+dfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 481,208 kB
  • ctags: 520,541
  • sloc: cpp: 2,126,470; python: 293,214; makefile: 51,927; ansic: 19,032; sh: 3,011; xml: 1,629; perl: 17
file content (422 lines) | stat: -rw-r--r-- 11,987 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
#
#  This was modified from rpcMixin.py distributed with wxPython
#
#----------------------------------------------------------------------
# Name:        rpcMixin
# Version:     0.2.0
# Purpose:     provides xmlrpc server functionality for wxPython
#              applications via a mixin class
#
# Requires:    (1) Python with threading enabled.
#              (2) xmlrpclib from PythonWare
#                  (http://www.pythonware.com/products/xmlrpc/)
#                  the code was developed and tested using version 0.9.8
#
# Author:      greg Landrum (Landrum@RationalDiscovery.com)
#
# Copyright:   (c) 2000, 2001 by Greg Landrum and Rational Discovery LLC
# Licence:     wxWindows license
#----------------------------------------------------------------------
# 12/11/2003 - Jeff Grimmett (grimmtooth@softhome.net)
#
# o 2.5 compatability update.
# o xmlrpcserver not available.
#

"""provides xmlrpc server functionality for wxPython applications via a mixin class

**Some Notes:**

  1)  The xmlrpc server runs in a separate thread from the main GUI
      application, communication between the two threads using a custom
      event (see the Threads demo in the wxPython docs for more info).

  2)  Neither the server nor the client are particularly smart about
      checking method names.  So it's easy to shoot yourself in the foot
      by calling improper methods.  It would be pretty easy to add
      either a list of allowed methods or a list of forbidden methods.

  3)  Authentication of xmlrpc clients is *not* performed.  I think it
      would be pretty easy to do this in a hacky way, but I haven't done
      it yet.

  4)  See the bottom of this file for an example of using the class.

**Obligatory disclaimer:**
  This is my first crack at both using xmlrpc and multi-threaded
  programming, so there could be huge horrible bugs or design
  flaws. If you see one, I'd love to hear about them.

"""


""" ChangeLog
23 May 2001:  Version bumped to 0.2.0
  Numerous code and design changes

21 Mar. 2001:  Version bumped to 0.1.4
  Updated rpcMixin.OnExternal to support methods with further references
   (i.e. now you can do rpcClient.foo.bar() and have it work)
  This probably ain't super legal in xmlrpc land, but it works just fine here
   and we need it.

6  Mar. 2001:  Version bumped to 0.1.3
  Documentation changes to make this compatible with happydoc

21 Jan. 2001:  Version bumped to 0.1.2
  OnExternal() method in the mixin class now uses getattr() to check if
    a desired method is present.  It should have been done this way in
    the first place.
14 Dec. 2000:  Version bumped to 0.1.1
  rearranged locking code and made other changes so that multiple
    servers in one application are possible.

"""

import  new
import  SocketServer
import  sys
import  threading
import  xmlrpclib
import  xmlrpcserver

import  wx

rpcPENDING = 0
rpcDONE = 1
rpcEXCEPT = 2

class RPCRequest:
  """A wrapper to use for handling requests and their responses"""
  status = rpcPENDING
  result = None

# here's the ID for external events
wxEVT_EXTERNAL_EVENT = wx.NewEventType()
EVT_EXTERNAL_EVENT = wx.PyEventBinder(wxEVT_EXTERNAL_EVENT, 0)

class ExternalEvent(wx.PyEvent):
  """The custom event class used to pass xmlrpc calls from
     the server thread into the GUI thread

  """
  def __init__(self,method,args):
    wx.PyEvent.__init__(self)
    self.SetEventType(wxEVT_EXTERNAL_EVENT)
    self.method = method
    self.args = args
    self.rpcStatus = RPCRequest()
    self.rpcStatusLock = threading.Lock()
    self.rpcCondVar = threading.Condition()

  def Destroy(self):
    self.method=None
    self.args=None
    self.rpcStatus = None
    self.rpcStatusLock = None
    self.rpcondVar = None

class Handler(xmlrpcserver.RequestHandler):
  """The handler class that the xmlrpcserver actually calls
     when a request comes in.

  """
  def log_message(self,*args):
    """ causes the server to stop spewing messages every time a request comes in

    """
    pass
  def call(self,method,params):
    """When an xmlrpc request comes in, this is the method that
       gets called.

       **Arguments**

         - method: name of the method to be called

         - params: arguments to that method

    """
    if method == '_rpcPing':
      # we just acknowledge these without processing them
      return 'ack'

    # construct the event
    evt = ExternalEvent(method,params)

    # update the status variable
    evt.rpcStatusLock.acquire()
    evt.rpcStatus.status = rpcPENDING
    evt.rpcStatusLock.release()

    evt.rpcCondVar.acquire()
    # dispatch the event to the GUI
    wx.PostEvent(self._app,evt)

    # wait for the GUI to finish
    while evt.rpcStatus.status == rpcPENDING:
      evt.rpcCondVar.wait()
    evt.rpcCondVar.release()
    evt.rpcStatusLock.acquire()
    if evt.rpcStatus.status == rpcEXCEPT:
      # The GUI threw an exception, release the status lock
      #  and re-raise the exception
      evt.rpcStatusLock.release()
      raise evt.rpcStatus.result[0],evt.rpcStatus.result[1]
    else:
      # everything went through without problems
      s = evt.rpcStatus.result

      evt.rpcStatusLock.release()
      evt.Destroy()
      self._app = None
      return s

# this global Event is used to let the server thread
#  know when it should quit
stopEvent = threading.Event()
stopEvent.clear()

class _ServerThread(threading.Thread):
  """ this is the Thread class which actually runs the server

  """
  def __init__(self,server,verbose=0):
    self._xmlServ = server
    threading.Thread.__init__(self,verbose=verbose)

  def stop(self):
    stopEvent.set()

  def shouldStop(self):
    return stopEvent.isSet()

  def run(self):
    while not self.shouldStop():
      self._xmlServ.handle_request()
    self._xmlServ = None

class rpcMixin:
  """A mixin class to provide xmlrpc server functionality to wxPython
     frames/windows

     If you want to customize this, probably the best idea is to
     override the OnExternal method, which is what's invoked when an
     RPC is handled.

  """

  # we'll try a range of ports for the server, this is the size of the
  #  range to be scanned
  nPortsToTry=20
  if sys.platform == 'win32':
    defPort = 800
  else:
    defPort = 8023

  def __init__(self,host='',port=-1,verbose=0,portScan=1):
    """Constructor

      **Arguments**

        - host: (optional) the hostname for the server

        - port: (optional) the port the server will use

        - verbose: (optional) if set, the server thread will be launched
          in verbose mode

        - portScan: (optional) if set, we'll scan across a number of ports
          to find one which is avaiable

    """
    if port == -1:
      port = self.defPort
    self.verbose=verbose
    self.Bind(EVT_EXTERNAL_EVENT,self.OnExternal)
    if hasattr(self,'OnClose'):
      self._origOnClose = self.OnClose
      self.Disconnect(-1,-1,wx.EVT_CLOSE_WINDOW)
    else:
      self._origOnClose = None
    self.OnClose = self.RPCOnClose
    self.Bind(wx.EVT_CLOSE,self.RPCOnClose)

    tClass = new.classobj('Handler%d'%(port),(Handler,),{})
    tClass._app = self
    if portScan:
      self.rpcPort = -1
      for i in xrange(self.nPortsToTry):
        try:
          xmlServ = SocketServer.TCPServer((host,port+i),tClass)
        except:
          pass
        else:
          self.rpcPort = port+i
    else:
      self.rpcPort = port
      try:
        xmlServ = SocketServer.TCPServer((host,port),tClass)
      except:
        self.rpcPort = -1

    if self.rpcPort == -1:
      raise Exception, 'RPCMixinError: Cannot initialize server'
    self.servThread = _ServerThread(xmlServ,verbose=self.verbose)
    self.servThread.setName('XML-RPC Server')
    self.servThread.start()

  def RPCOnClose(self,event):
    """ callback for when the application is closed

       be sure to shutdown the server and the server thread before
       leaving

    """
    # by setting the global stopEvent we inform the server thread
    # that it's time to shut down.
    stopEvent.set()
    if event is not None:
      # if we came in here from a user event (as opposed to an RPC event),
      #  then we'll need to kick the server one last time in order
      #  to get that thread to terminate.  do so now
      s1 = xmlrpclib.Server('http://localhost:%d'%(self.rpcPort))
      try:
        s1._rpcPing()
      except:
        pass

    if self._origOnClose is not None:
      self._origOnClose(event)

  def RPCQuit(self):
    """ shuts down everything, including the rpc server

    """
    self.RPCOnClose(None)
  def OnExternal(self,event):
    """ this is the callback used to handle RPCs

      **Arguments**

        - event: an _ExternalEvent_ sent by the rpc server

      Exceptions are caught and returned in the global _rpcStatus
      structure.  This allows the xmlrpc server to report the
      exception to the client without mucking up any of the delicate
      thread stuff.

    """
    event.rpcStatusLock.acquire()
    doQuit = 0
    try:
      methsplit = event.method.split('.')
      meth = self
      for piece in methsplit:
        meth = getattr(meth,piece)
    except AttributeError,msg:
      event.rpcStatus.result = 'No Such Method',msg
      event.rpcStatus.status = rpcEXCEPT
    else:
      try:
        res = apply(meth,event.args)
      except:
        import traceback
        if self.verbose: traceback.print_exc()
        event.rpcStatus.result = sys.exc_info()[:2]
        event.rpcStatus.status = rpcEXCEPT
      else:
        if res is None:
          # returning None across the xmlrpc interface is problematic
          event.rpcStatus.result = []
        else:
          event.rpcStatus.result = res
        event.rpcStatus.status = rpcDONE

    event.rpcStatusLock.release()

    # broadcast (using the condition var) that we're done with the event
    event.rpcCondVar.acquire()
    event.rpcCondVar.notify()
    event.rpcCondVar.release()


if __name__ == '__main__':
  import time
  if sys.platform == 'win32':
    port = 800
  else:
    port = 8023

  class rpcFrame(wx.Frame,rpcMixin):
    """A simple wxFrame with the rpcMixin functionality added
    """
    def __init__(self,*args,**kwargs):
      """ rpcHost or rpcPort keyword arguments will be passed along to
          the xmlrpc server.
      """
      mixinArgs = {}
      if kwargs.has_key('rpcHost'):
        mixinArgs['host'] = kwargs['rpcHost']
        del kwargs['rpcHost']
      if kwargs.has_key('rpcPort'):
        mixinArgs['port'] = kwargs['rpcPort']
        del kwargs['rpcPort']
      if kwargs.has_key('rpcPortScan'):
        mixinArgs['portScan'] = kwargs['rpcPortScan']
        del kwargs['rpcPortScan']

      apply(wx.Frame.__init__,(self,)+args,kwargs)
      apply(rpcMixin.__init__,(self,),mixinArgs)

      self.Bind(wx.EVT_CHAR,self.OnChar)

    def TestFunc(self,args):
      """a demo method"""
      return args

    def OnChar(self,event):
      key = event.GetKeyCode()
      if key == ord('q'):
        self.OnQuit(event)

    def OnQuit(self,event):
      self.OnClose(event)

    def OnClose(self,event):
      self.Destroy()



  class MyApp(wx.App):
    def OnInit(self):
      self.frame = rpcFrame(None, -1, "wxPython RPCDemo", wx.DefaultPosition,
                            (300,300), rpcHost='localhost',rpcPort=port)
      self.frame.Show(True)
      return True


  def testcon(port):
    s1 = xmlrpclib.Server('http://localhost:%d'%(port))
    s1.SetTitle('Munged')
    s1._rpcPing()
    if doQuit:
      s1.RPCQuit()

  doQuit = 1
  if len(sys.argv)>1 and sys.argv[1] == '-q':
    doQuit = 0
  nT = threading.activeCount()
  app = MyApp(0)
  activePort = app.frame.rpcPort
  t = threading.Thread(target=lambda x=activePort:testcon(x),verbose=0)
  t.start()

  app.MainLoop()
  # give the threads time to shut down
  if threading.activeCount() > nT:
    print 'waiting for all threads to terminate'
    while threading.activeCount() > nT:
      time.sleep(0.5)