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