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 not Any: 103 signal = str(signal) 104 105 if weak: 106 receiver = safeRef(receiver) 107 108 if sender is Any: 109 _log.debug('connecting (weak=%s): <any sender> ==%s==> %s', weak, signal, receiver) 110 else: 111 _log.debug('connecting (weak=%s): %s ==%s==> %s', weak, sender, signal, receiver) 112 113 sender_identity = id(sender) 114 signals = {} 115 if sender_identity in connections: 116 signals = connections[sender_identity] 117 else: 118 connections[sender_identity] = signals 119 # Keep track of senders for cleanup. 120 if sender not in (None, Any): 121 def _remove4weakref(object, sender_identity=sender_identity): 122 _removeSender(sender_identity=sender_identity)
123 # Skip objects that can not be weakly referenced, which means 124 # they won't be automatically cleaned up, but that's too bad. 125 try: 126 weakSender = weakref.ref(sender, _remove4weakref) 127 senders[sender_identity] = weakSender 128 except Exception: 129 pass 130 receivers = [] 131 if signal in signals: 132 receivers = signals[signal] 133 else: 134 signals[signal] = receivers 135 try: 136 receivers.remove(receiver) 137 except ValueError: 138 pass 139 receivers.append(receiver) 140 141 #---------------------------------------------------------------------
142 -def disconnect(receiver, signal=Any, sender=Any, weak=1):
143 """Disconnect receiver from sender for signal. 144 145 Disconnecting is not required. The use of disconnect is the same as for 146 connect, only in reverse. Think of it as undoing a previous connection.""" 147 if signal not in known_signals: 148 _log.warning('unknown signal [%(sig)s]', {'sig': signal}) 149 150 if signal is not Any: 151 signal = str(signal) 152 if weak: receiver = safeRef(receiver) 153 sender_identity = id(sender) 154 try: 155 receivers = connections[sender_identity][signal] 156 except KeyError: 157 _log.warning('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender}) 158 print('DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender)) 159 return 160 try: 161 receivers.remove(receiver) 162 except ValueError: 163 _log.warning('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender}) 164 print("DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender)) 165 _cleanupConnections(sender_identity, signal)
166 167 #---------------------------------------------------------------------
168 -def send(signal=None, sender=None, **kwds):
169 """Send signal from sender to all connected receivers. 170 171 Return a list of tuple pairs [(receiver, response), ... ]. 172 If sender is None, signal is sent anonymously. 173 """ 174 signal = str(signal) 175 sender_identity = id(sender) 176 identity_of_Any = id(Any) 177 178 # Get receivers that receive *this* signal from *this* sender. 179 receivers = [] 180 try: 181 receivers.extend(connections[sender_identity][signal]) 182 except KeyError: 183 pass 184 185 # Add receivers that receive *any* signal from *this* sender. 186 anyreceivers = [] 187 try: 188 anyreceivers = connections[sender_identity][Any] 189 except KeyError: 190 pass 191 for receiver in anyreceivers: 192 if receivers.count(receiver) == 0: 193 receivers.append(receiver) 194 195 # Add receivers that receive *this* signal from *any* sender. 196 anyreceivers = [] 197 try: 198 anyreceivers = connections[identity_of_Any][signal] 199 except KeyError: 200 pass 201 for receiver in anyreceivers: 202 if receivers.count(receiver) == 0: 203 receivers.append(receiver) 204 205 # Add receivers that receive *any* signal from *any* sender. 206 anyreceivers = [] 207 try: 208 anyreceivers = connections[identity_of_Any][Any] 209 except KeyError: 210 pass 211 for receiver in anyreceivers: 212 if receivers.count(receiver) == 0: 213 receivers.append(receiver) 214 215 # Call each receiver with whatever arguments it can accept. 216 # Return a list of tuple pairs [(receiver, response), ... ]. 217 responses = [] 218 for receiver in receivers: 219 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)): 220 _log.debug('dereferencing weak_ref receiver [%s]', receiver) 221 # Dereference the weak reference. 222 receiver = receiver() 223 _log.debug('dereferenced receiver is [%s]', receiver) 224 if receiver is None: 225 # This receiver is dead, so skip it. 226 continue 227 try: 228 response = _call(receiver, signal=signal, sender=sender, **kwds) 229 responses += [(receiver, response)] 230 except Exception: 231 _log.exception('exception calling [%s]: (signal=%s, sender=%a, **kwds=%s)', receiver, signal, sender, str(kwds)) 232 233 return responses
234 235 #--------------------------------------------------------------------- 236 # 237 #---------------------------------------------------------------------
238 -def safeRef(object):
239 """Return a *safe* weak reference to a callable object.""" 240 if hasattr(object, '__self__'): 241 if object.__self__ is not None: 242 # Turn a bound method into a BoundMethodWeakref instance. 243 # Keep track of these instances for lookup by disconnect(). 244 selfkey = object.__self__ 245 funckey = object.__func__ 246 if selfkey not in _boundMethods: 247 _boundMethods[selfkey] = weakref.WeakKeyDictionary() 248 if funckey not in _boundMethods[selfkey]: 249 _boundMethods[selfkey][funckey] = BoundMethodWeakref(boundMethod=object) 250 return _boundMethods[selfkey][funckey] 251 return weakref.ref(object, _removeReceiver)
252 253 #=====================================================================
254 -class BoundMethodWeakref:
255 """BoundMethodWeakref class.""" 256
257 - def __init__(self, boundMethod):
258 """Return a weak-reference-like instance for a bound method.""" 259 self.isDead = 0 260 def remove(object, self=self): 261 """Set self.isDead to true when method or instance is destroyed.""" 262 self.isDead = 1 263 print('BoundMethodWeakref.__init__.remove(): _removeReceiver =', _removeReceiver) 264 print('BoundMethodWeakref.__init__.remove(): self =', self) 265 _removeReceiver(receiver=self)
266 self.weakSelf = weakref.ref(boundMethod.__self__, remove) 267 self.weakFunc = weakref.ref(boundMethod.__func__, remove)
268 #------------------------------------------------------------------
269 - def __repr__(self):
270 """Return the closest representation.""" 271 return repr(self.weakFunc)
272 #------------------------------------------------------------------
273 - def __call__(self):
274 """Return a strong reference to the bound method.""" 275 276 if self.isDead: 277 return None 278 279 obj = self.weakSelf() 280 method = self.weakFunc().__name__ 281 if not obj: 282 self.isDead = 1 283 _removeReceiver(receiver=self) 284 return None 285 try: 286 return getattr(obj, method) 287 except RuntimeError: 288 self.isDead = 1 289 _removeReceiver(receiver=self) 290 return None
291 292 #===================================================================== 293 # internal API 294 #---------------------------------------------------------------------
295 -def _call(receiver, **kwds):
296 """Call receiver with only arguments it can accept.""" 297 # # not used in GNUmed 298 # #if type(receiver) is types.InstanceType: 299 # #if isinstance(receiver, object): 300 # # if receiver is an instance -> get the "call" interface = the __init__() function 301 # if type(receiver) is object: 302 # # receiver is a class instance; assume it is callable. 303 # # Reassign receiver to the actual method that will be called. 304 # receiver = receiver.__call__ 305 306 if hasattr(receiver, '__func__'): 307 # receiver is a method. Drop the first argument, usually 'self'. 308 func_code_def = receiver.__func__.__code__ 309 acceptable_args = func_code_def.co_varnames[1:func_code_def.co_argcount] 310 elif hasattr(receiver, '__code__'): 311 # receiver is a function. 312 func_code_def = receiver.__code__ 313 acceptable_args = func_code_def.co_varnames[0:func_code_def.co_argcount] 314 else: 315 _log.error('<%s> must be instance, method or function, but is [%s]', str(receiver), type(receiver)) 316 raise TypeError('DISPATCHER ERROR: _call(): <%s> must be instance, method or function, but is []' % (str(receiver), type(receiver))) 317 318 # 0x08: bit for whether func uses **kwds syntax 319 if not (func_code_def.co_flags & 0x08): 320 # func_code_def does not have a **kwds type parameter, 321 # therefore remove unacceptable arguments. 322 keys = list(kwds.keys()) 323 for arg in keys: 324 if arg not in acceptable_args: 325 del kwds[arg] 326 327 if __execute_in_main_thread is None: 328 print('DISPATCHER problem: no main-thread executor available') 329 return receiver(**kwds) 330 331 # if a cross-thread executor is set 332 return __execute_in_main_thread(receiver, **kwds)
333 334 #---------------------------------------------------------------------
335 -def _removeReceiver(receiver):
336 """Remove receiver from connections.""" 337 for sender_identity in connections.keys(): 338 for signal in connections[sender_identity].keys(): 339 receivers = connections[sender_identity][signal] 340 try: 341 receivers.remove(receiver) 342 except Exception: 343 pass 344 _cleanupConnections(sender_identity, signal)
345 346 #---------------------------------------------------------------------
347 -def _cleanupConnections(sender_identity, signal):
348 """Delete any empty signals for sender_identity. Delete sender_identity if empty.""" 349 receivers = connections[sender_identity][signal] 350 if not receivers: 351 # No more connected receivers. Therefore, remove the signal. 352 signals = connections[sender_identity] 353 del signals[signal] 354 if not signals: 355 # No more signal connections. Therefore, remove the sender. 356 _removeSender(sender_identity)
357 #---------------------------------------------------------------------
358 -def _removeSender(sender_identity):
359 """Remove sender_identity from connections.""" 360 del connections[sender_identity] 361 # sender_identity will only be in senders dictionary if sender 362 # could be weakly referenced. 363 try: del senders[sender_identity] 364 except Exception: pass
365 366 #===================================================================== 367