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',
19 u'current_encounter_switched',
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',
28 u'display_widget',
29 u'plugin_loaded',
30 u'application_closing',
31 u'request_user_attention',
32 u'clin_item_updated',
33 u'register_pre_exit_callback',
34 u'focus_patient_search',
35 ]
36
37 _log = logging.getLogger('gm.messaging')
38
39 connections = {}
40 senders = {}
41
42 _boundMethods = weakref.WeakKeyDictionary()
43
46
47 Any = _Any()
48
49 known_signals.append(Any)
50
54
55
56
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
110 if sender not in (None, Any):
111 def remove(object, senderkey=senderkey):
112 _removeSender(senderkey=senderkey)
113
114
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
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
169 receivers = []
170 try: receivers.extend(connections[senderkey][signal])
171 except KeyError: pass
172
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
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
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
194
195 responses = []
196 for receiver in receivers:
197 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)):
198
199 receiver = receiver()
200 if receiver is None:
201
202 continue
203 try:
204 response = _call(receiver, signal=signal, sender=sender, **kwds)
205 responses += [(receiver, response)]
206 except:
207
208
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
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
220
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
232 """BoundMethodWeakref class."""
233
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
245 """Return the closest representation."""
246 return repr(self.weakFunc)
247
266
267
268
269 -def _call(receiver, **kwds):
270 """Call receiver with only arguments it can accept."""
271 if type(receiver) is types.InstanceType:
272
273
274 receiver = receiver.__call__
275 if hasattr(receiver, 'im_func'):
276
277 fc = receiver.im_func.func_code
278 acceptable_args = fc.co_varnames[1:fc.co_argcount]
279 elif hasattr(receiver, 'func_code'):
280
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
288
289 for arg in kwds.keys():
290 if arg not in acceptable_args:
291 del kwds[arg]
292 return receiver(**kwds)
293
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
304 """Delete any empty signals for senderkey. Delete senderkey if empty."""
305 receivers = connections[senderkey][signal]
306 if not receivers:
307
308 signals = connections[senderkey]
309 del signals[signal]
310 if not signals:
311
312 _removeSender(senderkey)
313
315 """Remove senderkey from connections."""
316 del connections[senderkey]
317
318
319 try: del senders[senderkey]
320 except: pass
321
322
323