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'encounter_mod_db', 
 21          u'pre_patient_selection', 
 22          u'post_patient_selection', 
 23          u'patient_locked', 
 24          u'patient_unlocked', 
 25          u'import_document_from_file', 
 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  ] 
 34   
 35  _log = logging.getLogger('gm.messaging') 
 36   
 37  connections = {} 
 38  senders = {} 
 39   
 40  _boundMethods = weakref.WeakKeyDictionary() 
 41  #===================================================================== 
42 -class _Any:
43 pass
44 45 Any = _Any() 46 47 known_signals.append(Any) 48 #=====================================================================
49 -class DispatcherError(exceptions.Exception):
50 - def __init__(self, args=None):
51 self.args = args
52 #===================================================================== 53 # external API 54 #---------------------------------------------------------------------
55 -def connect(receiver=None, signal=Any, sender=Any, weak=1):
56 """Connect receiver to sender for signal. 57 58 If sender is Any, receiver will receive signal from any sender. 59 If signal is Any, receiver will receive any signal from sender. 60 If sender is None, receiver will receive signal from anonymous. 61 If signal is Any and sender is None, receiver will receive any 62 signal from anonymous. 63 If signal is Any and sender is Any, receiver will receive any 64 signal from any sender. 65 If weak is true, weak references will be used. 66 67 ADDITIONAL gnumed specific documentation: 68 this dispatcher is not designed with a gui single threaded event 69 loop in mind. 70 when connecting to a receiver that may eventually make calls to gui objects such as wxWindows objects, it is highly recommended that any 71 such calls be wrapped in wxCallAfter() e.g. 72 def receiveSignal(self, **args): 73 self._callsThatDoNotTriggerGuiUpdates() 74 self.data = processArgs(args) 75 wxCallAfter( self._callsThatTriggerGuiUpdates() ) 76 77 since it is likely data change occurs before the signalling, 78 it would probably look more simply like: 79 80 def receiveSignal(self, **args): 81 wxCallAfter(self._updateUI() ) 82 83 def _updateUI(self): 84 # your code that reads data 85 86 Especially if the widget can get a reference to updated data through 87 a global reference, such as via gmCurrentPatient. 88 """ 89 if receiver is None: 90 raise ValueError('gmDispatcher.connect(): must define <receiver>') 91 92 if signal not in known_signals: 93 _log.error('unknown signal [%(sig)s]', {'sig': signal}) 94 print "DISPATCHER ERROR: connect(): unknown signal [%s]" % 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 print "DISPATCHER ERROR: disconnect(): unknown signal [%s]" % signal 135 136 if signal is not Any: 137 signal = str(signal) 138 if weak: receiver = safeRef(receiver) 139 senderkey = id(sender) 140 try: 141 receivers = connections[senderkey][signal] 142 except KeyError: 143 _log.error('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender}) 144 print 'DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender) 145 return 146 try: 147 receivers.remove(receiver) 148 except ValueError: 149 _log.error('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender}) 150 print "DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender) 151 _cleanupConnections(senderkey, signal)
152 #---------------------------------------------------------------------
153 -def send(signal=None, sender=None, **kwds):
154 """Send signal from sender to all connected receivers. 155 156 Return a list of tuple pairs [(receiver, response), ... ]. 157 If sender is None, signal is sent anonymously. 158 """ 159 if signal not in known_signals: 160 _log.error('unknown signal [%(sig)s]', {'sig': signal}) 161 print "DISPATCHER ERROR: send(): unknown signal [%s]" % signal 162 163 signal = str(signal) 164 senderkey = id(sender) 165 anykey = id(Any) 166 # Get receivers that receive *this* signal from *this* sender. 167 receivers = [] 168 try: receivers.extend(connections[senderkey][signal]) 169 except KeyError: pass 170 # Add receivers that receive *any* signal from *this* sender. 171 anyreceivers = [] 172 try: anyreceivers = connections[senderkey][Any] 173 except KeyError: pass 174 for receiver in anyreceivers: 175 if receivers.count(receiver) == 0: 176 receivers.append(receiver) 177 # Add receivers that receive *this* signal from *any* sender. 178 anyreceivers = [] 179 try: anyreceivers = connections[anykey][signal] 180 except KeyError: pass 181 for receiver in anyreceivers: 182 if receivers.count(receiver) == 0: 183 receivers.append(receiver) 184 # Add receivers that receive *any* signal from *any* sender. 185 anyreceivers = [] 186 try: anyreceivers = connections[anykey][Any] 187 except KeyError: pass 188 for receiver in anyreceivers: 189 if receivers.count(receiver) == 0: 190 receivers.append(receiver) 191 # Call each receiver with whatever arguments it can accept. 192 # Return a list of tuple pairs [(receiver, response), ... ]. 193 responses = [] 194 for receiver in receivers: 195 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)): 196 # Dereference the weak reference. 197 receiver = receiver() 198 if receiver is None: 199 # This receiver is dead, so skip it. 200 continue 201 try: 202 response = _call(receiver, signal=signal, sender=sender, **kwds) 203 responses += [(receiver, response)] 204 except: 205 # this seems such a fundamental error that it appears 206 # reasonable to print directly to the console 207 typ, val, tb = sys.exc_info() 208 _log.critical('%(t)s, <%(v)s>', {'t': typ, 'v': val}) 209 _log.critical('calling <%(rx)s> failed', {'rx': str(receiver)}) 210 traceback.print_tb(tb) 211 return responses
212 #---------------------------------------------------------------------
213 -def safeRef(object):
214 """Return a *safe* weak reference to a callable object.""" 215 if hasattr(object, 'im_self'): 216 if object.im_self is not None: 217 # Turn a bound method into a BoundMethodWeakref instance. 218 # Keep track of these instances for lookup by disconnect(). 219 selfkey = object.im_self 220 funckey = object.im_func 221 if not _boundMethods.has_key(selfkey): 222 _boundMethods[selfkey] = weakref.WeakKeyDictionary() 223 if not _boundMethods[selfkey].has_key(funckey): 224 _boundMethods[selfkey][funckey] = \ 225 BoundMethodWeakref(boundMethod=object) 226 return _boundMethods[selfkey][funckey] 227 return weakref.ref(object, _removeReceiver)
228 #=====================================================================
229 -class BoundMethodWeakref:
230 """BoundMethodWeakref class.""" 231
232 - def __init__(self, boundMethod):
233 """Return a weak-reference-like instance for a bound method.""" 234 self.isDead = 0 235 def remove(object, self=self): 236 """Set self.isDead to true when method or instance is destroyed.""" 237 self.isDead = 1 238 _removeReceiver(receiver=self)
239 self.weakSelf = weakref.ref(boundMethod.im_self, remove) 240 self.weakFunc = weakref.ref(boundMethod.im_func, remove)
241 #------------------------------------------------------------------
242 - def __repr__(self):
243 """Return the closest representation.""" 244 return repr(self.weakFunc)
245 #------------------------------------------------------------------
246 - def __call__(self):
247 """Return a strong reference to the bound method.""" 248 249 global wx_core_PyDeadObjectError 250 if wx_core_PyDeadObjectError is None: 251 from wx._core import PyDeadObjectError as wx_core_PyDeadObjectError 252 253 if self.isDead: 254 return None 255 256 object = self.weakSelf() 257 method = self.weakFunc().__name__ 258 try: 259 return getattr(object, method) 260 except wx_core_PyDeadObjectError: 261 self.isDead = 1 262 _removeReceiver(receiver=self) 263 return None
264 #===================================================================== 265 # internal API 266 #---------------------------------------------------------------------
267 -def _call(receiver, **kwds):
268 """Call receiver with only arguments it can accept.""" 269 if type(receiver) is types.InstanceType: 270 # receiver is a class instance; assume it is callable. 271 # Reassign receiver to the actual method that will be called. 272 receiver = receiver.__call__ 273 if hasattr(receiver, 'im_func'): 274 # receiver is a method. Drop the first argument, usually 'self'. 275 fc = receiver.im_func.func_code 276 acceptable_args = fc.co_varnames[1:fc.co_argcount] 277 elif hasattr(receiver, 'func_code'): 278 # receiver is a function. 279 fc = receiver.func_code 280 acceptable_args = fc.co_varnames[0:fc.co_argcount] 281 else: 282 _log.error('<%(rx)s> must be instance, method or function', {'rx': str(receiver)}) 283 print 'DISPATCHER ERROR: _call(): <%s> must be instance, method or function' % str(receiver) 284 if not (fc.co_flags & 8): 285 # fc does not have a **kwds type parameter, therefore 286 # remove unacceptable arguments. 287 for arg in kwds.keys(): 288 if arg not in acceptable_args: 289 del kwds[arg] 290 return receiver(**kwds)
291 #---------------------------------------------------------------------
292 -def _removeReceiver(receiver):
293 """Remove receiver from connections.""" 294 for senderkey in connections.keys(): 295 for signal in connections[senderkey].keys(): 296 receivers = connections[senderkey][signal] 297 try: receivers.remove(receiver) 298 except: pass 299 _cleanupConnections(senderkey, signal)
300 #---------------------------------------------------------------------
301 -def _cleanupConnections(senderkey, signal):
302 """Delete any empty signals for senderkey. Delete senderkey if empty.""" 303 receivers = connections[senderkey][signal] 304 if not receivers: 305 # No more connected receivers. Therefore, remove the signal. 306 signals = connections[senderkey] 307 del signals[signal] 308 if not signals: 309 # No more signal connections. Therefore, remove the sender. 310 _removeSender(senderkey)
311 #---------------------------------------------------------------------
312 -def _removeSender(senderkey):
313 """Remove senderkey from connections.""" 314 del connections[senderkey] 315 # Senderkey will only be in senders dictionary if sender 316 # could be weakly referenced. 317 try: del senders[senderkey] 318 except: pass
319 320 #===================================================================== 321 # $Log: gmDispatcher.py,v $ 322 # Revision 1.24 2009/05/12 12:05:56 ncq 323 # - add missing signal 324 # 325 # Revision 1.23 2009/04/20 11:39:07 ncq 326 # - add signal clin_item_updated 327 # 328 # Revision 1.22 2009/04/13 10:51:18 ncq 329 # - add "current encounter switched" 330 # 331 # Revision 1.21 2009/02/05 21:07:50 ncq 332 # - add signal 333 # 334 # Revision 1.20 2009/01/02 11:37:19 ncq 335 # - new signal 336 # 337 # Revision 1.19 2008/12/01 12:12:06 ncq 338 # - lazy import of wx._core.PyDeadObjectError 339 # 340 # Revision 1.18 2008/10/22 12:07:43 ncq 341 # - spurious double : 342 # 343 # Revision 1.17 2008/09/09 20:16:35 ncq 344 # - don't crash if weak ref target is dead 345 # 346 # Revision 1.16 2008/08/08 13:29:56 ncq 347 # - add register_pre_exit_callback signal 348 # 349 # Revision 1.15 2008/06/28 22:33:57 ncq 350 # - remove obsolete signal 351 # 352 # Revision 1.14 2007/12/12 16:17:15 ncq 353 # - better logger names 354 # 355 # Revision 1.13 2007/12/11 15:35:46 ncq 356 # - log, don't print, but critical 357 # 358 # Revision 1.12 2007/12/11 14:19:27 ncq 359 # - stdlib logging 360 # 361 # Revision 1.11 2007/11/02 13:52:52 ncq 362 # - add two signals 363 # 364 # Revision 1.10 2007/10/25 12:19:18 ncq 365 # - allergy_updated is no more 366 # - by default know signal "Any" 367 # 368 # Revision 1.9 2007/08/11 23:55:07 ncq 369 # - register more signals 370 # - report unknown signals but still pass them on 371 # 372 # Revision 1.8 2006/09/06 10:26:52 shilbert 373 # - removed some weird EOL via dos2unix 374 # 375 # Revision 1.7 2005/10/10 18:10:33 ncq 376 # - ever so slightly beautify debugging 377 # 378 # Revision 1.6 2005/10/08 12:33:08 sjtan 379 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch. 380 # 381 # Revision 1.5 2005/04/03 20:09:20 ncq 382 # - it's rather stupid to try to remove a signal that we just tested to not exist, 383 # hence refrain from doing so 384 # 385 # Revision 1.4 2005/03/23 19:02:27 ncq 386 # - improved error handling 387 # 388 # Revision 1.3 2005/03/17 12:59:16 ncq 389 # - if an event receiver fails we should not fail all other receivers, too 390 # - so report and continue 391 # - but do not make us depend on gmLog just because of one fundamental, 392 # low level failure - may be the wrong choice in the long term, however 393 # 394 # Revision 1.2 2004/06/21 17:05:20 ncq 395 # - whitespace cleanup 396 # - it's a bit harsh to throw an exception when trying 397 # to disconnect an unconnected signal, reporting and 398 # succeeding should do 399 # 400 # Revision 1.1 2004/02/25 09:30:13 ncq 401 # - moved here from python-common 402 # 403