Gnumed.pycommon.gmDispatcher

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