Package Gnumed :: Package pycommon :: Module gmDispatcher
[frames] | no frames]

Source Code for Module Gnumed.pycommon.gmDispatcher

  1  """GNUmed client internal signal handling. 
  2   
  3  # this code has been written by Patrick O'Brien <pobrien@orbtech.com> 
  4  # downloaded from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/87056 
  5  """ 
  6  import exceptions 
  7  import types 
  8  import sys 
  9  import weakref 
 10  import traceback 
 11  import logging 
 12   
 13   
 14  wx_core_PyDeadObjectError = None 
 15   
 16   
 17  known_signals = [ 
 18          u'current_encounter_modified',  # the current encounter was modified externally 
 19          u'current_encounter_switched',  # *another* encounter became the current one 
 20          u'pre_patient_selection', 
 21          u'post_patient_selection', 
 22          u'patient_locked', 
 23          u'patient_unlocked', 
 24          u'import_document_from_file', 
 25          u'import_document_from_files', 
 26          u'statustext',                                  # args: msg=message, beep=whether to beep or not 
 27          u'display_widget',                              # args: name=name of widget, other=widget specific (see receivers) 
 28          u'plugin_loaded',                               # args: name=name of plugin 
 29          u'application_closing', 
 30          u'request_user_attention', 
 31          u'clin_item_updated',                   # sent by SOAP importer 
 32          u'register_pre_exit_callback',  # args: callback = function to call 
 33          u'focus_patient_search',                # set focus to patient search box 
 34  ] 
 35   
 36  _log = logging.getLogger('gm.messaging') 
 37   
 38  connections = {} 
 39  senders = {} 
 40   
 41  _boundMethods = weakref.WeakKeyDictionary() 
 42  #===================================================================== 
43 -class _Any:
44 pass
45 46 Any = _Any() 47 48 known_signals.append(Any) 49 #=====================================================================
50 -class DispatcherError(exceptions.Exception):
51 - def __init__(self, args=None):
52 self.args = args
53 #===================================================================== 54 # external API 55 #---------------------------------------------------------------------
56 -def connect(receiver=None, signal=Any, sender=Any, weak=1):
57 """Connect receiver to sender for signal. 58 59 If sender is Any, receiver will receive signal from any sender. 60 If signal is Any, receiver will receive any signal from sender. 61 If sender is None, receiver will receive signal from anonymous. 62 If signal is Any and sender is None, receiver will receive any 63 signal from anonymous. 64 If signal is Any and sender is Any, receiver will receive any 65 signal from any sender. 66 If weak is true, weak references will be used. 67 68 ADDITIONAL gnumed specific documentation: 69 this dispatcher is not designed with a gui single threaded event 70 loop in mind. 71 when connecting to a receiver that may eventually make calls to gui objects such as wxWindows objects, it is highly recommended that any 72 such calls be wrapped in wxCallAfter() e.g. 73 def receiveSignal(self, **args): 74 self._callsThatDoNotTriggerGuiUpdates() 75 self.data = processArgs(args) 76 wxCallAfter( self._callsThatTriggerGuiUpdates() ) 77 78 since it is likely data change occurs before the signalling, 79 it would probably look more simply like: 80 81 def receiveSignal(self, **args): 82 wxCallAfter(self._updateUI() ) 83 84 def _updateUI(self): 85 # your code that reads data 86 87 Especially if the widget can get a reference to updated data through 88 a global reference, such as via gmCurrentPatient. 89 """ 90 if receiver is None: 91 raise ValueError('gmDispatcher.connect(): must define <receiver>') 92 93 if signal not in known_signals: 94 _log.error('unknown signal [%(sig)s]', {'sig': signal}) 95 96 if signal is not Any: 97 signal = str(signal) 98 99 if weak: 100 receiver = safeRef(receiver) 101 senderkey = id(sender) 102 signals = {} 103 if connections.has_key(senderkey): 104 signals = connections[senderkey] 105 else: 106 connections[senderkey] = signals 107 # Keep track of senders for cleanup. 108 if sender not in (None, Any): 109 def remove(object, senderkey=senderkey): 110 _removeSender(senderkey=senderkey)
111 # Skip objects that can not be weakly referenced, which means 112 # they won't be automatically cleaned up, but that's too bad. 113 try: 114 weakSender = weakref.ref(sender, remove) 115 senders[senderkey] = weakSender 116 except: 117 pass 118 receivers = [] 119 if signals.has_key(signal): 120 receivers = signals[signal] 121 else: 122 signals[signal] = receivers 123 try: receivers.remove(receiver) 124 except ValueError: pass 125 receivers.append(receiver) 126 #---------------------------------------------------------------------
127 -def disconnect(receiver, signal=Any, sender=Any, weak=1):
128 """Disconnect receiver from sender for signal. 129 130 Disconnecting is not required. The use of disconnect is the same as for 131 connect, only in reverse. Think of it as undoing a previous connection.""" 132 if signal not in known_signals: 133 _log.error('unknown signal [%(sig)s]', {'sig': signal}) 134 135 if signal is not Any: 136 signal = str(signal) 137 if weak: receiver = safeRef(receiver) 138 senderkey = id(sender) 139 try: 140 receivers = connections[senderkey][signal] 141 except KeyError: 142 _log.error('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender}) 143 print 'DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender) 144 return 145 try: 146 receivers.remove(receiver) 147 except ValueError: 148 _log.error('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender}) 149 print "DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender) 150 _cleanupConnections(senderkey, signal)
151 #---------------------------------------------------------------------
152 -def send(signal=None, sender=None, **kwds):
153 """Send signal from sender to all connected receivers. 154 155 Return a list of tuple pairs [(receiver, response), ... ]. 156 If sender is None, signal is sent anonymously. 157 """ 158 signal = str(signal) 159 senderkey = id(sender) 160 anykey = id(Any) 161 # Get receivers that receive *this* signal from *this* sender. 162 receivers = [] 163 try: receivers.extend(connections[senderkey][signal]) 164 except KeyError: pass 165 # Add receivers that receive *any* signal from *this* sender. 166 anyreceivers = [] 167 try: anyreceivers = connections[senderkey][Any] 168 except KeyError: pass 169 for receiver in anyreceivers: 170 if receivers.count(receiver) == 0: 171 receivers.append(receiver) 172 # Add receivers that receive *this* signal from *any* sender. 173 anyreceivers = [] 174 try: anyreceivers = connections[anykey][signal] 175 except KeyError: pass 176 for receiver in anyreceivers: 177 if receivers.count(receiver) == 0: 178 receivers.append(receiver) 179 # Add receivers that receive *any* signal from *any* sender. 180 anyreceivers = [] 181 try: anyreceivers = connections[anykey][Any] 182 except KeyError: pass 183 for receiver in anyreceivers: 184 if receivers.count(receiver) == 0: 185 receivers.append(receiver) 186 # Call each receiver with whatever arguments it can accept. 187 # Return a list of tuple pairs [(receiver, response), ... ]. 188 responses = [] 189 for receiver in receivers: 190 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)): 191 # Dereference the weak reference. 192 receiver = receiver() 193 if receiver is None: 194 # This receiver is dead, so skip it. 195 continue 196 try: 197 response = _call(receiver, signal=signal, sender=sender, **kwds) 198 responses += [(receiver, response)] 199 except: 200 # this seems such a fundamental error that it appears 201 # reasonable to print directly to the console 202 typ, val, tb = sys.exc_info() 203 _log.critical('%(t)s, <%(v)s>', {'t': typ, 'v': val}) 204 _log.critical('calling <%(rx)s> failed', {'rx': str(receiver)}) 205 traceback.print_tb(tb) 206 return responses
207 #---------------------------------------------------------------------
208 -def safeRef(object):
209 """Return a *safe* weak reference to a callable object.""" 210 if hasattr(object, 'im_self'): 211 if object.im_self is not None: 212 # Turn a bound method into a BoundMethodWeakref instance. 213 # Keep track of these instances for lookup by disconnect(). 214 selfkey = object.im_self 215 funckey = object.im_func 216 if not _boundMethods.has_key(selfkey): 217 _boundMethods[selfkey] = weakref.WeakKeyDictionary() 218 if not _boundMethods[selfkey].has_key(funckey): 219 _boundMethods[selfkey][funckey] = \ 220 BoundMethodWeakref(boundMethod=object) 221 return _boundMethods[selfkey][funckey] 222 return weakref.ref(object, _removeReceiver)
223 #=====================================================================
224 -class BoundMethodWeakref:
225 """BoundMethodWeakref class.""" 226
227 - def __init__(self, boundMethod):
228 """Return a weak-reference-like instance for a bound method.""" 229 self.isDead = 0 230 def remove(object, self=self): 231 """Set self.isDead to true when method or instance is destroyed.""" 232 self.isDead = 1 233 _removeReceiver(receiver=self)
234 self.weakSelf = weakref.ref(boundMethod.im_self, remove) 235 self.weakFunc = weakref.ref(boundMethod.im_func, remove)
236 #------------------------------------------------------------------
237 - def __repr__(self):
238 """Return the closest representation.""" 239 return repr(self.weakFunc)
240 #------------------------------------------------------------------
241 - def __call__(self):
242 """Return a strong reference to the bound method.""" 243 244 global wx_core_PyDeadObjectError 245 if wx_core_PyDeadObjectError is None: 246 from wx._core import PyDeadObjectError as wx_core_PyDeadObjectError 247 248 if self.isDead: 249 return None 250 251 object = self.weakSelf() 252 method = self.weakFunc().__name__ 253 try: 254 return getattr(object, method) 255 except wx_core_PyDeadObjectError: 256 self.isDead = 1 257 _removeReceiver(receiver=self) 258 return None
259 #===================================================================== 260 # internal API 261 #---------------------------------------------------------------------
262 -def _call(receiver, **kwds):
263 """Call receiver with only arguments it can accept.""" 264 if type(receiver) is types.InstanceType: 265 # receiver is a class instance; assume it is callable. 266 # Reassign receiver to the actual method that will be called. 267 receiver = receiver.__call__ 268 if hasattr(receiver, 'im_func'): 269 # receiver is a method. Drop the first argument, usually 'self'. 270 fc = receiver.im_func.func_code 271 acceptable_args = fc.co_varnames[1:fc.co_argcount] 272 elif hasattr(receiver, 'func_code'): 273 # receiver is a function. 274 fc = receiver.func_code 275 acceptable_args = fc.co_varnames[0:fc.co_argcount] 276 else: 277 _log.error('<%(rx)s> must be instance, method or function', {'rx': str(receiver)}) 278 print 'DISPATCHER ERROR: _call(): <%s> must be instance, method or function' % str(receiver) 279 if not (fc.co_flags & 8): 280 # fc does not have a **kwds type parameter, therefore 281 # remove unacceptable arguments. 282 for arg in kwds.keys(): 283 if arg not in acceptable_args: 284 del kwds[arg] 285 return receiver(**kwds)
286 #---------------------------------------------------------------------
287 -def _removeReceiver(receiver):
288 """Remove receiver from connections.""" 289 for senderkey in connections.keys(): 290 for signal in connections[senderkey].keys(): 291 receivers = connections[senderkey][signal] 292 try: receivers.remove(receiver) 293 except: pass 294 _cleanupConnections(senderkey, signal)
295 #---------------------------------------------------------------------
296 -def _cleanupConnections(senderkey, signal):
297 """Delete any empty signals for senderkey. Delete senderkey if empty.""" 298 receivers = connections[senderkey][signal] 299 if not receivers: 300 # No more connected receivers. Therefore, remove the signal. 301 signals = connections[senderkey] 302 del signals[signal] 303 if not signals: 304 # No more signal connections. Therefore, remove the sender. 305 _removeSender(senderkey)
306 #---------------------------------------------------------------------
307 -def _removeSender(senderkey):
308 """Remove senderkey from connections.""" 309 del connections[senderkey] 310 # Senderkey will only be in senders dictionary if sender 311 # could be weakly referenced. 312 try: del senders[senderkey] 313 except: pass
314 315 #===================================================================== 316