Gnumed.pycommon.gmDispatcher

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
    261                  def remove(object, self=self):
    262                          """Set self.isDead to true when method or instance is destroyed."""
    263                          self.isDead = 1
    264                          print('BoundMethodWeakref.__init__.remove(): _removeReceiver =', _removeReceiver)
    265                          print('BoundMethodWeakref.__init__.remove(): self =', self)
    266                          if callable(_removeReceiver):
    267                                  _removeReceiver(receiver = self)
    268
    269                  self.weakSelf = weakref.ref(boundMethod.__self__, remove)
    270                  self.weakFunc = weakref.ref(boundMethod.__func__, remove)
    271          #------------------------------------------------------------------
    272 -        def __repr__(self):
    273                  """Return the closest representation."""
    274                  return repr(self.weakFunc)
    275          #------------------------------------------------------------------
    276 -        def __call__(self):
    277                  """Return a strong reference to the bound method."""
    278
    279                  if self.isDead:
    280                          return None
    281
    282                  obj = self.weakSelf()
    283                  method = self.weakFunc().__name__
    284                  if not obj:
    285                          self.isDead = 1
    286                          _removeReceiver(receiver=self)
    287                          return None
    288                  try:
    289                          return getattr(obj, method)
    290                  except RuntimeError:
    291                          self.isDead = 1
    292                          _removeReceiver(receiver=self)
    293                          return None
    294
    295  #=====================================================================
    296  # internal API
    297  #---------------------------------------------------------------------
    298 -def _call(receiver, **kwds):
    299          """Call receiver with only arguments it can accept."""
    300  #       # not used in GNUmed
    301  #       #if type(receiver) is types.InstanceType:
    302  #       #if isinstance(receiver, object):
    303  #       # if receiver is an instance -> get the "call" interface = the __init__() function
    304  #       if type(receiver) is object:
    305  #               # receiver is a class instance; assume it is callable.
    306  #               # Reassign receiver to the actual method that will be called.
    307  #               receiver = receiver.__call__
    308
    309          if hasattr(receiver, '__func__'):
    310                  # receiver is a method. Drop the first argument, usually 'self'.
    311                  func_code_def = receiver.__func__.__code__
    312                  acceptable_args = func_code_def.co_varnames[1:func_code_def.co_argcount]
    313          elif hasattr(receiver, '__code__'):
    314                  # receiver is a function.
    315                  func_code_def = receiver.__code__
    316                  acceptable_args = func_code_def.co_varnames[0:func_code_def.co_argcount]
    317          else:
    318                  _log.error('<%s> must be instance, method or function, but is [%s]', str(receiver), type(receiver))
    319                  raise TypeError('DISPATCHER ERROR: _call(): <%s> must be instance, method or function, but is []' % (str(receiver), type(receiver)))
    320
    321          # 0x08: bit for whether func uses **kwds syntax
    322          if not (func_code_def.co_flags & 0x08):
    323                  # func_code_def does not have a **kwds type parameter,
    324                  # therefore remove unacceptable arguments.
    325                  keys = list(kwds)
    326                  for arg in keys:
    327                          if arg not in acceptable_args:
    328                                  del kwds[arg]
    329
    330          if __execute_in_main_thread is None:
    331                  print('DISPATCHER problem: no main-thread executor available')
    332                  return receiver(**kwds)
    333
    334          # if a cross-thread executor is set
    335          return __execute_in_main_thread(receiver, **kwds)
    336
    337  #---------------------------------------------------------------------
    338 -def _removeReceiver(receiver):
    339          """Remove receiver from connections."""
    340          for sender_identity in connections:
    341                  for signal in connections[sender_identity]:
    342                          receivers = connections[sender_identity][signal]
    343                          try:
    344                                  receivers.remove(receiver)
    345                          except Exception:
    346                                  pass
    347                          _cleanupConnections(sender_identity, signal)
    348
    349  #---------------------------------------------------------------------
    350 -def _cleanupConnections(sender_identity, signal):
    351          """Delete any empty signals for sender_identity. Delete sender_identity if empty."""
    352          receivers = connections[sender_identity][signal]
    353          if not receivers:
    354                  # No more connected receivers. Therefore, remove the signal.
    355                  signals = connections[sender_identity]
    356                  del signals[signal]
    357                  if not signals:
    358                          # No more signal connections. Therefore, remove the sender.
    359                          _removeSender(sender_identity)
    360  #---------------------------------------------------------------------
    361 -def _removeSender(sender_identity):
    362          """Remove sender_identity from connections."""
    363          del connections[sender_identity]
    364          # sender_identity will only be in senders dictionary if sender
    365          # could be weakly referenced.
    366          try: del senders[sender_identity]
    367          except Exception: pass
    368
    369  #=====================================================================
    370