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