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'import_document_from_files', 
 27          u'statustext',                                  # args: msg=message, beep=whether to beep or not 
 28          u'display_widget',                              # args: name=name of widget, other=widget specific (see receivers) 
 29          u'plugin_loaded',                               # args: name=name of plugin 
 30          u'application_closing', 
 31          u'request_user_attention', 
 32          u'clin_item_updated',                   # sent by SOAP importer 
 33          u'register_pre_exit_callback'   # args: callback = function to call 
 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 print "DISPATCHER WARNING: connect(): unknown signal [%s]" % signal 96 97 if signal is not Any: 98 signal = str(signal) 99 100 if weak: 101 receiver = safeRef(receiver) 102 senderkey = id(sender) 103 signals = {} 104 if connections.has_key(senderkey): 105 signals = connections[senderkey] 106 else: 107 connections[senderkey] = signals 108 # Keep track of senders for cleanup. 109 if sender not in (None, Any): 110 def remove(object, senderkey=senderkey): 111 _removeSender(senderkey=senderkey)
112 # Skip objects that can not be weakly referenced, which means 113 # they won't be automatically cleaned up, but that's too bad. 114 try: 115 weakSender = weakref.ref(sender, remove) 116 senders[senderkey] = weakSender 117 except: 118 pass 119 receivers = [] 120 if signals.has_key(signal): 121 receivers = signals[signal] 122 else: 123 signals[signal] = receivers 124 try: receivers.remove(receiver) 125 except ValueError: pass 126 receivers.append(receiver) 127 #---------------------------------------------------------------------
128 -def disconnect(receiver, signal=Any, sender=Any, weak=1):
129 """Disconnect receiver from sender for signal. 130 131 Disconnecting is not required. The use of disconnect is the same as for 132 connect, only in reverse. Think of it as undoing a previous connection.""" 133 if signal not in known_signals: 134 _log.error('unknown signal [%(sig)s]', {'sig': signal}) 135 print "DISPATCHER ERROR: disconnect(): unknown signal [%s]" % signal 136 137 if signal is not Any: 138 signal = str(signal) 139 if weak: receiver = safeRef(receiver) 140 senderkey = id(sender) 141 try: 142 receivers = connections[senderkey][signal] 143 except KeyError: 144 _log.error('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender}) 145 print 'DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender) 146 return 147 try: 148 receivers.remove(receiver) 149 except ValueError: 150 _log.error('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender}) 151 print "DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender) 152 _cleanupConnections(senderkey, signal)
153 #---------------------------------------------------------------------
154 -def send(signal=None, sender=None, **kwds):
155 """Send signal from sender to all connected receivers. 156 157 Return a list of tuple pairs [(receiver, response), ... ]. 158 If sender is None, signal is sent anonymously. 159 """ 160 if signal not in known_signals: 161 _log.error('unknown signal [%(sig)s]', {'sig': signal}) 162 print "DISPATCHER ERROR: send(): unknown signal [%s]" % signal 163 164 signal = str(signal) 165 senderkey = id(sender) 166 anykey = id(Any) 167 # Get receivers that receive *this* signal from *this* sender. 168 receivers = [] 169 try: receivers.extend(connections[senderkey][signal]) 170 except KeyError: pass 171 # Add receivers that receive *any* signal from *this* sender. 172 anyreceivers = [] 173 try: anyreceivers = connections[senderkey][Any] 174 except KeyError: pass 175 for receiver in anyreceivers: 176 if receivers.count(receiver) == 0: 177 receivers.append(receiver) 178 # Add receivers that receive *this* signal from *any* sender. 179 anyreceivers = [] 180 try: anyreceivers = connections[anykey][signal] 181 except KeyError: pass 182 for receiver in anyreceivers: 183 if receivers.count(receiver) == 0: 184 receivers.append(receiver) 185 # Add receivers that receive *any* signal from *any* sender. 186 anyreceivers = [] 187 try: anyreceivers = connections[anykey][Any] 188 except KeyError: pass 189 for receiver in anyreceivers: 190 if receivers.count(receiver) == 0: 191 receivers.append(receiver) 192 # Call each receiver with whatever arguments it can accept. 193 # Return a list of tuple pairs [(receiver, response), ... ]. 194 responses = [] 195 for receiver in receivers: 196 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)): 197 # Dereference the weak reference. 198 receiver = receiver() 199 if receiver is None: 200 # This receiver is dead, so skip it. 201 continue 202 try: 203 response = _call(receiver, signal=signal, sender=sender, **kwds) 204 responses += [(receiver, response)] 205 except: 206 # this seems such a fundamental error that it appears 207 # reasonable to print directly to the console 208 typ, val, tb = sys.exc_info() 209 _log.critical('%(t)s, <%(v)s>', {'t': typ, 'v': val}) 210 _log.critical('calling <%(rx)s> failed', {'rx': str(receiver)}) 211 traceback.print_tb(tb) 212 return responses
213 #---------------------------------------------------------------------
214 -def safeRef(object):
215 """Return a *safe* weak reference to a callable object.""" 216 if hasattr(object, 'im_self'): 217 if object.im_self is not None: 218 # Turn a bound method into a BoundMethodWeakref instance. 219 # Keep track of these instances for lookup by disconnect(). 220 selfkey = object.im_self 221 funckey = object.im_func 222 if not _boundMethods.has_key(selfkey): 223 _boundMethods[selfkey] = weakref.WeakKeyDictionary() 224 if not _boundMethods[selfkey].has_key(funckey): 225 _boundMethods[selfkey][funckey] = \ 226 BoundMethodWeakref(boundMethod=object) 227 return _boundMethods[selfkey][funckey] 228 return weakref.ref(object, _removeReceiver)
229 #=====================================================================
230 -class BoundMethodWeakref:
231 """BoundMethodWeakref class.""" 232
233 - def __init__(self, boundMethod):
234 """Return a weak-reference-like instance for a bound method.""" 235 self.isDead = 0 236 def remove(object, self=self): 237 """Set self.isDead to true when method or instance is destroyed.""" 238 self.isDead = 1 239 _removeReceiver(receiver=self)
240 self.weakSelf = weakref.ref(boundMethod.im_self, remove) 241 self.weakFunc = weakref.ref(boundMethod.im_func, remove)
242 #------------------------------------------------------------------
243 - def __repr__(self):
244 """Return the closest representation.""" 245 return repr(self.weakFunc)
246 #------------------------------------------------------------------
247 - def __call__(self):
248 """Return a strong reference to the bound method.""" 249 250 global wx_core_PyDeadObjectError 251 if wx_core_PyDeadObjectError is None: 252 from wx._core import PyDeadObjectError as wx_core_PyDeadObjectError 253 254 if self.isDead: 255 return None 256 257 object = self.weakSelf() 258 method = self.weakFunc().__name__ 259 try: 260 return getattr(object, method) 261 except wx_core_PyDeadObjectError: 262 self.isDead = 1 263 _removeReceiver(receiver=self) 264 return None
265 #===================================================================== 266 # internal API 267 #---------------------------------------------------------------------
268 -def _call(receiver, **kwds):
269 """Call receiver with only arguments it can accept.""" 270 if type(receiver) is types.InstanceType: 271 # receiver is a class instance; assume it is callable. 272 # Reassign receiver to the actual method that will be called. 273 receiver = receiver.__call__ 274 if hasattr(receiver, 'im_func'): 275 # receiver is a method. Drop the first argument, usually 'self'. 276 fc = receiver.im_func.func_code 277 acceptable_args = fc.co_varnames[1:fc.co_argcount] 278 elif hasattr(receiver, 'func_code'): 279 # receiver is a function. 280 fc = receiver.func_code 281 acceptable_args = fc.co_varnames[0:fc.co_argcount] 282 else: 283 _log.error('<%(rx)s> must be instance, method or function', {'rx': str(receiver)}) 284 print 'DISPATCHER ERROR: _call(): <%s> must be instance, method or function' % str(receiver) 285 if not (fc.co_flags & 8): 286 # fc does not have a **kwds type parameter, therefore 287 # remove unacceptable arguments. 288 for arg in kwds.keys(): 289 if arg not in acceptable_args: 290 del kwds[arg] 291 return receiver(**kwds)
292 #---------------------------------------------------------------------
293 -def _removeReceiver(receiver):
294 """Remove receiver from connections.""" 295 for senderkey in connections.keys(): 296 for signal in connections[senderkey].keys(): 297 receivers = connections[senderkey][signal] 298 try: receivers.remove(receiver) 299 except: pass 300 _cleanupConnections(senderkey, signal)
301 #---------------------------------------------------------------------
302 -def _cleanupConnections(senderkey, signal):
303 """Delete any empty signals for senderkey. Delete senderkey if empty.""" 304 receivers = connections[senderkey][signal] 305 if not receivers: 306 # No more connected receivers. Therefore, remove the signal. 307 signals = connections[senderkey] 308 del signals[signal] 309 if not signals: 310 # No more signal connections. Therefore, remove the sender. 311 _removeSender(senderkey)
312 #---------------------------------------------------------------------
313 -def _removeSender(senderkey):
314 """Remove senderkey from connections.""" 315 del connections[senderkey] 316 # Senderkey will only be in senders dictionary if sender 317 # could be weakly referenced. 318 try: del senders[senderkey] 319 except: pass
320 321 #===================================================================== 322