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  ]
     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                  print "DISPATCHER WARNING: connect(): unknown signal [%s]" % signal
     96
     97          if signal is not Any:
     98                  signal = str(signal)
     99
    100          if weak:
    101                  receiver = safeRef(receiver)
    102          senderkey = id(sender)
    103          signals = {}
    104          if connections.has_key(senderkey):
    105                  signals = connections[senderkey]
    106          else:
    107                  connections[senderkey] = signals
    108                  # Keep track of senders for cleanup.
    109                  if sender not in (None, Any):
    110                          def remove(object, senderkey=senderkey):
    111                                  _removeSender(senderkey=senderkey)
    112                          # Skip objects that can not be weakly referenced, which means
    113                          # they won't be automatically cleaned up, but that's too bad.
    114                          try:
    115                                  weakSender = weakref.ref(sender, remove)
    116                                  senders[senderkey] = weakSender
    117                          except:
    118                                  pass
    119          receivers = []
    120          if signals.has_key(signal):
    121                  receivers = signals[signal]
    122          else:
    123                  signals[signal] = receivers
    124          try: receivers.remove(receiver)
    125          except ValueError: pass
    126          receivers.append(receiver)
    127  #---------------------------------------------------------------------
    128 -def disconnect(receiver, signal=Any, sender=Any, weak=1):
    129          """Disconnect receiver from sender for signal.
    130
    131          Disconnecting is not required. The use of disconnect is the same as for
    132          connect, only in reverse. Think of it as undoing a previous connection."""
    133          if signal not in known_signals:
    134                  _log.error('unknown signal [%(sig)s]', {'sig': signal})
    135                  print "DISPATCHER ERROR: disconnect(): unknown signal [%s]" % signal
    136
    137          if signal is not Any:
    138                  signal = str(signal)
    139          if weak: receiver = safeRef(receiver)
    140          senderkey = id(sender)
    141          try:
    142                  receivers = connections[senderkey][signal]
    143          except KeyError:
    144                  _log.error('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender})
    145                  print 'DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender)
    146                  return
    147          try:
    148                  receivers.remove(receiver)
    149          except ValueError:
    150                  _log.error('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender})
    151                  print "DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender)
    152          _cleanupConnections(senderkey, signal)
    153  #---------------------------------------------------------------------
    154 -def send(signal=None, sender=None, **kwds):
    155          """Send signal from sender to all connected receivers.
    156
    157          Return a list of tuple pairs [(receiver, response), ... ].
    158          If sender is None, signal is sent anonymously.
    159          """
    160          if signal not in known_signals:
    161                  _log.error('unknown signal [%(sig)s]', {'sig': signal})
    162                  print "DISPATCHER ERROR: send(): unknown signal [%s]" % signal
    163
    164          signal = str(signal)
    165          senderkey = id(sender)
    166          anykey = id(Any)
    167          # Get receivers that receive *this* signal from *this* sender.
    168          receivers = []
    169          try: receivers.extend(connections[senderkey][signal])
    170          except KeyError: pass
    171          # Add receivers that receive *any* signal from *this* sender.
    172          anyreceivers = []
    173          try: anyreceivers = connections[senderkey][Any]
    174          except KeyError: pass
    175          for receiver in anyreceivers:
    176                  if receivers.count(receiver) == 0:
    177                          receivers.append(receiver)
    178          # Add receivers that receive *this* signal from *any* sender.
    179          anyreceivers = []
    180          try: anyreceivers = connections[anykey][signal]
    181          except KeyError: pass
    182          for receiver in anyreceivers:
    183                  if receivers.count(receiver) == 0:
    184                          receivers.append(receiver)
    185          # Add receivers that receive *any* signal from *any* sender.
    186          anyreceivers = []
    187          try: anyreceivers = connections[anykey][Any]
    188          except KeyError: pass
    189          for receiver in anyreceivers:
    190                  if receivers.count(receiver) == 0:
    191                          receivers.append(receiver)
    192          # Call each receiver with whatever arguments it can accept.
    193          # Return a list of tuple pairs [(receiver, response), ... ].
    194          responses = []
    195          for receiver in receivers:
    196                  if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)):
    197                          # Dereference the weak reference.
    198                          receiver = receiver()
    199                          if receiver is None:
    200                                  # This receiver is dead, so skip it.
    201                                  continue
    202                  try:
    203                          response = _call(receiver, signal=signal, sender=sender, **kwds)
    204                          responses += [(receiver, response)]
    205                  except:
    206                          # this seems such a fundamental error that it appears
    207                          # reasonable to print directly to the console
    208                          typ, val, tb = sys.exc_info()
    209                          _log.critical('%(t)s, <%(v)s>', {'t': typ, 'v': val})
    210                          _log.critical('calling <%(rx)s> failed', {'rx': str(receiver)})
    211                          traceback.print_tb(tb)
    212          return responses
    213  #---------------------------------------------------------------------
    214 -def safeRef(object):
    215          """Return a *safe* weak reference to a callable object."""
    216          if hasattr(object, 'im_self'):
    217                  if object.im_self is not None:
    218                          # Turn a bound method into a BoundMethodWeakref instance.
    219                          # Keep track of these instances for lookup by disconnect().
    220                          selfkey = object.im_self
    221                          funckey = object.im_func
    222                          if not _boundMethods.has_key(selfkey):
    223                                  _boundMethods[selfkey] = weakref.WeakKeyDictionary()
    224                          if not _boundMethods[selfkey].has_key(funckey):
    225                                  _boundMethods[selfkey][funckey] = \
    226                                  BoundMethodWeakref(boundMethod=object)
    227                          return _boundMethods[selfkey][funckey]
    228          return weakref.ref(object, _removeReceiver)
    229  #=====================================================================
    230 -class BoundMethodWeakref:
    231          """BoundMethodWeakref class."""
    232
    233 -        def __init__(self, boundMethod):
    234                  """Return a weak-reference-like instance for a bound method."""
    235                  self.isDead = 0
    236                  def remove(object, self=self):
    237                          """Set self.isDead to true when method or instance is destroyed."""
    238                          self.isDead = 1
    239                          _removeReceiver(receiver=self)
    240                  self.weakSelf = weakref.ref(boundMethod.im_self, remove)
    241                  self.weakFunc = weakref.ref(boundMethod.im_func, remove)
    242          #------------------------------------------------------------------
    243 -        def __repr__(self):
    244                  """Return the closest representation."""
    245                  return repr(self.weakFunc)
    246          #------------------------------------------------------------------
    247 -        def __call__(self):
    248                  """Return a strong reference to the bound method."""
    249
    250                  global wx_core_PyDeadObjectError
    251                  if wx_core_PyDeadObjectError is None:
    252                          from wx._core import PyDeadObjectError as wx_core_PyDeadObjectError
    253
    254                  if self.isDead:
    255                          return None
    256
    257                  object = self.weakSelf()
    258                  method = self.weakFunc().__name__
    259                  try:
    260                          return getattr(object, method)
    261                  except wx_core_PyDeadObjectError:
    262                          self.isDead = 1
    263                          _removeReceiver(receiver=self)
    264                          return None
    265  #=====================================================================
    266  # internal API
    267  #---------------------------------------------------------------------
    268 -def _call(receiver, **kwds):
    269          """Call receiver with only arguments it can accept."""
    270          if type(receiver) is types.InstanceType:
    271                  # receiver is a class instance; assume it is callable.
    272                  # Reassign receiver to the actual method that will be called.
    273                  receiver = receiver.__call__
    274          if hasattr(receiver, 'im_func'):
    275                  # receiver is a method. Drop the first argument, usually 'self'.
    276                  fc = receiver.im_func.func_code
    277                  acceptable_args = fc.co_varnames[1:fc.co_argcount]
    278          elif hasattr(receiver, 'func_code'):
    279                  # receiver is a function.
    280                  fc = receiver.func_code
    281                  acceptable_args = fc.co_varnames[0:fc.co_argcount]
    282          else:
    283                  _log.error('<%(rx)s> must be instance, method or function', {'rx': str(receiver)})
    284                  print 'DISPATCHER ERROR: _call(): <%s> must be instance, method or function' % str(receiver)
    285          if not (fc.co_flags & 8):
    286                  # fc does not have a **kwds type parameter, therefore
    287                  # remove unacceptable arguments.
    288                  for arg in kwds.keys():
    289                          if arg not in acceptable_args:
    290                                  del kwds[arg]
    291          return receiver(**kwds)
    292  #---------------------------------------------------------------------
    293 -def _removeReceiver(receiver):
    294          """Remove receiver from connections."""
    295          for senderkey in connections.keys():
    296                  for signal in connections[senderkey].keys():
    297                          receivers = connections[senderkey][signal]
    298                          try: receivers.remove(receiver)
    299                          except: pass
    300                          _cleanupConnections(senderkey, signal)
    301  #---------------------------------------------------------------------
    302 -def _cleanupConnections(senderkey, signal):
    303          """Delete any empty signals for senderkey. Delete senderkey if empty."""
    304          receivers = connections[senderkey][signal]
    305          if not receivers:
    306                  # No more connected receivers. Therefore, remove the signal.
    307                  signals = connections[senderkey]
    308                  del signals[signal]
    309                  if not signals:
    310                          # No more signal connections. Therefore, remove the sender.
    311                          _removeSender(senderkey)
    312  #---------------------------------------------------------------------
    313 -def _removeSender(senderkey):
    314          """Remove senderkey from connections."""
    315          del connections[senderkey]
    316          # Senderkey will only be in senders dictionary if sender
    317          # could be weakly referenced.
    318          try: del senders[senderkey]
    319          except: pass
    320
    321  #=====================================================================
    322