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:
    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:
    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: pass
    365
    366  #=====================================================================
    367