| Trees | Indices | Help |
|
|---|
|
|
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'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 ]
34
35 _log = logging.getLogger('gm.messaging')
36
37 connections = {}
38 senders = {}
39
40 _boundMethods = weakref.WeakKeyDictionary()
41 #=====================================================================
44
45 Any = _Any()
46
47 known_signals.append(Any)
48 #=====================================================================
52 #=====================================================================
53 # external API
54 #---------------------------------------------------------------------
56 """Connect receiver to sender for signal.
57
58 If sender is Any, receiver will receive signal from any sender.
59 If signal is Any, receiver will receive any signal from sender.
60 If sender is None, receiver will receive signal from anonymous.
61 If signal is Any and sender is None, receiver will receive any
62 signal from anonymous.
63 If signal is Any and sender is Any, receiver will receive any
64 signal from any sender.
65 If weak is true, weak references will be used.
66
67 ADDITIONAL gnumed specific documentation:
68 this dispatcher is not designed with a gui single threaded event
69 loop in mind.
70 when connecting to a receiver that may eventually make calls to gui objects such as wxWindows objects, it is highly recommended that any
71 such calls be wrapped in wxCallAfter() e.g.
72 def receiveSignal(self, **args):
73 self._callsThatDoNotTriggerGuiUpdates()
74 self.data = processArgs(args)
75 wxCallAfter( self._callsThatTriggerGuiUpdates() )
76
77 since it is likely data change occurs before the signalling,
78 it would probably look more simply like:
79
80 def receiveSignal(self, **args):
81 wxCallAfter(self._updateUI() )
82
83 def _updateUI(self):
84 # your code that reads data
85
86 Especially if the widget can get a reference to updated data through
87 a global reference, such as via gmCurrentPatient.
88 """
89 if receiver is None:
90 raise ValueError('gmDispatcher.connect(): must define <receiver>')
91
92 if signal not in known_signals:
93 _log.error('unknown signal [%(sig)s]', {'sig': signal})
94 print "DISPATCHER ERROR: connect(): unknown signal [%s]" % 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 #---------------------------------------------------------------------
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 print "DISPATCHER ERROR: disconnect(): unknown signal [%s]" % signal
135
136 if signal is not Any:
137 signal = str(signal)
138 if weak: receiver = safeRef(receiver)
139 senderkey = id(sender)
140 try:
141 receivers = connections[senderkey][signal]
142 except KeyError:
143 _log.error('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender})
144 print 'DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender)
145 return
146 try:
147 receivers.remove(receiver)
148 except ValueError:
149 _log.error('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender})
150 print "DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender)
151 _cleanupConnections(senderkey, signal)
152 #---------------------------------------------------------------------
154 """Send signal from sender to all connected receivers.
155
156 Return a list of tuple pairs [(receiver, response), ... ].
157 If sender is None, signal is sent anonymously.
158 """
159 if signal not in known_signals:
160 _log.error('unknown signal [%(sig)s]', {'sig': signal})
161 print "DISPATCHER ERROR: send(): unknown signal [%s]" % signal
162
163 signal = str(signal)
164 senderkey = id(sender)
165 anykey = id(Any)
166 # Get receivers that receive *this* signal from *this* sender.
167 receivers = []
168 try: receivers.extend(connections[senderkey][signal])
169 except KeyError: pass
170 # Add receivers that receive *any* signal from *this* sender.
171 anyreceivers = []
172 try: anyreceivers = connections[senderkey][Any]
173 except KeyError: pass
174 for receiver in anyreceivers:
175 if receivers.count(receiver) == 0:
176 receivers.append(receiver)
177 # Add receivers that receive *this* signal from *any* sender.
178 anyreceivers = []
179 try: anyreceivers = connections[anykey][signal]
180 except KeyError: pass
181 for receiver in anyreceivers:
182 if receivers.count(receiver) == 0:
183 receivers.append(receiver)
184 # Add receivers that receive *any* signal from *any* sender.
185 anyreceivers = []
186 try: anyreceivers = connections[anykey][Any]
187 except KeyError: pass
188 for receiver in anyreceivers:
189 if receivers.count(receiver) == 0:
190 receivers.append(receiver)
191 # Call each receiver with whatever arguments it can accept.
192 # Return a list of tuple pairs [(receiver, response), ... ].
193 responses = []
194 for receiver in receivers:
195 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)):
196 # Dereference the weak reference.
197 receiver = receiver()
198 if receiver is None:
199 # This receiver is dead, so skip it.
200 continue
201 try:
202 response = _call(receiver, signal=signal, sender=sender, **kwds)
203 responses += [(receiver, response)]
204 except:
205 # this seems such a fundamental error that it appears
206 # reasonable to print directly to the console
207 typ, val, tb = sys.exc_info()
208 _log.critical('%(t)s, <%(v)s>', {'t': typ, 'v': val})
209 _log.critical('calling <%(rx)s> failed', {'rx': str(receiver)})
210 traceback.print_tb(tb)
211 return responses
212 #---------------------------------------------------------------------
214 """Return a *safe* weak reference to a callable object."""
215 if hasattr(object, 'im_self'):
216 if object.im_self is not None:
217 # Turn a bound method into a BoundMethodWeakref instance.
218 # Keep track of these instances for lookup by disconnect().
219 selfkey = object.im_self
220 funckey = object.im_func
221 if not _boundMethods.has_key(selfkey):
222 _boundMethods[selfkey] = weakref.WeakKeyDictionary()
223 if not _boundMethods[selfkey].has_key(funckey):
224 _boundMethods[selfkey][funckey] = \
225 BoundMethodWeakref(boundMethod=object)
226 return _boundMethods[selfkey][funckey]
227 return weakref.ref(object, _removeReceiver)
228 #=====================================================================
230 """BoundMethodWeakref class."""
231
233 """Return a weak-reference-like instance for a bound method."""
234 self.isDead = 0
235 def remove(object, self=self):
236 """Set self.isDead to true when method or instance is destroyed."""
237 self.isDead = 1
238 _removeReceiver(receiver=self)
239 self.weakSelf = weakref.ref(boundMethod.im_self, remove)
240 self.weakFunc = weakref.ref(boundMethod.im_func, remove)
241 #------------------------------------------------------------------
245 #------------------------------------------------------------------
247 """Return a strong reference to the bound method."""
248
249 global wx_core_PyDeadObjectError
250 if wx_core_PyDeadObjectError is None:
251 from wx._core import PyDeadObjectError as wx_core_PyDeadObjectError
252
253 if self.isDead:
254 return None
255
256 object = self.weakSelf()
257 method = self.weakFunc().__name__
258 try:
259 return getattr(object, method)
260 except wx_core_PyDeadObjectError:
261 self.isDead = 1
262 _removeReceiver(receiver=self)
263 return None
264 #=====================================================================
265 # internal API
266 #---------------------------------------------------------------------
268 """Call receiver with only arguments it can accept."""
269 if type(receiver) is types.InstanceType:
270 # receiver is a class instance; assume it is callable.
271 # Reassign receiver to the actual method that will be called.
272 receiver = receiver.__call__
273 if hasattr(receiver, 'im_func'):
274 # receiver is a method. Drop the first argument, usually 'self'.
275 fc = receiver.im_func.func_code
276 acceptable_args = fc.co_varnames[1:fc.co_argcount]
277 elif hasattr(receiver, 'func_code'):
278 # receiver is a function.
279 fc = receiver.func_code
280 acceptable_args = fc.co_varnames[0:fc.co_argcount]
281 else:
282 _log.error('<%(rx)s> must be instance, method or function', {'rx': str(receiver)})
283 print 'DISPATCHER ERROR: _call(): <%s> must be instance, method or function' % str(receiver)
284 if not (fc.co_flags & 8):
285 # fc does not have a **kwds type parameter, therefore
286 # remove unacceptable arguments.
287 for arg in kwds.keys():
288 if arg not in acceptable_args:
289 del kwds[arg]
290 return receiver(**kwds)
291 #---------------------------------------------------------------------
293 """Remove receiver from connections."""
294 for senderkey in connections.keys():
295 for signal in connections[senderkey].keys():
296 receivers = connections[senderkey][signal]
297 try: receivers.remove(receiver)
298 except: pass
299 _cleanupConnections(senderkey, signal)
300 #---------------------------------------------------------------------
302 """Delete any empty signals for senderkey. Delete senderkey if empty."""
303 receivers = connections[senderkey][signal]
304 if not receivers:
305 # No more connected receivers. Therefore, remove the signal.
306 signals = connections[senderkey]
307 del signals[signal]
308 if not signals:
309 # No more signal connections. Therefore, remove the sender.
310 _removeSender(senderkey)
311 #---------------------------------------------------------------------
313 """Remove senderkey from connections."""
314 del connections[senderkey]
315 # Senderkey will only be in senders dictionary if sender
316 # could be weakly referenced.
317 try: del senders[senderkey]
318 except: pass
319
320 #=====================================================================
321 # $Log: gmDispatcher.py,v $
322 # Revision 1.24 2009/05/12 12:05:56 ncq
323 # - add missing signal
324 #
325 # Revision 1.23 2009/04/20 11:39:07 ncq
326 # - add signal clin_item_updated
327 #
328 # Revision 1.22 2009/04/13 10:51:18 ncq
329 # - add "current encounter switched"
330 #
331 # Revision 1.21 2009/02/05 21:07:50 ncq
332 # - add signal
333 #
334 # Revision 1.20 2009/01/02 11:37:19 ncq
335 # - new signal
336 #
337 # Revision 1.19 2008/12/01 12:12:06 ncq
338 # - lazy import of wx._core.PyDeadObjectError
339 #
340 # Revision 1.18 2008/10/22 12:07:43 ncq
341 # - spurious double :
342 #
343 # Revision 1.17 2008/09/09 20:16:35 ncq
344 # - don't crash if weak ref target is dead
345 #
346 # Revision 1.16 2008/08/08 13:29:56 ncq
347 # - add register_pre_exit_callback signal
348 #
349 # Revision 1.15 2008/06/28 22:33:57 ncq
350 # - remove obsolete signal
351 #
352 # Revision 1.14 2007/12/12 16:17:15 ncq
353 # - better logger names
354 #
355 # Revision 1.13 2007/12/11 15:35:46 ncq
356 # - log, don't print, but critical
357 #
358 # Revision 1.12 2007/12/11 14:19:27 ncq
359 # - stdlib logging
360 #
361 # Revision 1.11 2007/11/02 13:52:52 ncq
362 # - add two signals
363 #
364 # Revision 1.10 2007/10/25 12:19:18 ncq
365 # - allergy_updated is no more
366 # - by default know signal "Any"
367 #
368 # Revision 1.9 2007/08/11 23:55:07 ncq
369 # - register more signals
370 # - report unknown signals but still pass them on
371 #
372 # Revision 1.8 2006/09/06 10:26:52 shilbert
373 # - removed some weird EOL via dos2unix
374 #
375 # Revision 1.7 2005/10/10 18:10:33 ncq
376 # - ever so slightly beautify debugging
377 #
378 # Revision 1.6 2005/10/08 12:33:08 sjtan
379 # tree can be updated now without refetching entire cache; done by passing emr object to create_xxxx methods and calling emr.update_cache(key,obj);refresh_historical_tree non-destructively checks for changes and removes removed nodes and adds them if cache mismatch.
380 #
381 # Revision 1.5 2005/04/03 20:09:20 ncq
382 # - it's rather stupid to try to remove a signal that we just tested to not exist,
383 # hence refrain from doing so
384 #
385 # Revision 1.4 2005/03/23 19:02:27 ncq
386 # - improved error handling
387 #
388 # Revision 1.3 2005/03/17 12:59:16 ncq
389 # - if an event receiver fails we should not fail all other receivers, too
390 # - so report and continue
391 # - but do not make us depend on gmLog just because of one fundamental,
392 # low level failure - may be the wrong choice in the long term, however
393 #
394 # Revision 1.2 2004/06/21 17:05:20 ncq
395 # - whitespace cleanup
396 # - it's a bit harsh to throw an exception when trying
397 # to disconnect an unconnected signal, reporting and
398 # succeeding should do
399 #
400 # Revision 1.1 2004/02/25 09:30:13 ncq
401 # - moved here from python-common
402 #
403
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:36 2010 | http://epydoc.sourceforge.net |