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

Source Code for Module Gnumed.pycommon.gmDispatcher

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