1 __doc__ = """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 types
7 import sys
8 import weakref
9 import traceback
10 import logging
11
12
13 known_signals = [
14 'current_encounter_modified',
15 'current_encounter_switched',
16 'pre_patient_unselection',
17 'post_patient_selection',
18 'patient_locked',
19 'patient_unlocked',
20 'import_document_from_file',
21 'import_document_from_files',
22 'statustext',
23 'display_widget',
24 'plugin_loaded',
25 'application_closing',
26 'request_user_attention',
27 'clin_item_updated',
28 'register_pre_exit_callback',
29 'focus_patient_search',
30 ]
31
32 _log = logging.getLogger('gm.messaging')
33
34 connections = {}
35 senders = {}
36
37 _boundMethods = weakref.WeakKeyDictionary()
38
41
42 Any = _Any()
43
44 known_signals.append(Any)
45
46
50
51
52
53
54 __execute_in_main_thread = None
55
57 if not callable(caller):
58 raise TypeError('caller [%s] is not callable' % caller)
59 global __execute_in_main_thread
60 __execute_in_main_thread = caller
61
62
64
65 """Connect receiver to sender for signal.
66
67 If sender is Any, receiver will receive signal from any sender.
68 If signal is Any, receiver will receive any signal from sender.
69 If sender is None, receiver will receive signal from anonymous.
70 If signal is Any and sender is None, receiver will receive any signal from anonymous.
71 If signal is Any and sender is Any, receiver will receive any signal from any sender.
72 If weak is true, weak references will be used.
73
74 ADDITIONAL gnumed specific documentation:
75
76 this dispatcher is not designed with a gui single threaded event loop in mind.
77
78 when connecting to a receiver that may eventually make
79 calls to gui objects such as wxWindows objects, it is
80 highly recommended that any such calls be wrapped in
81 wxCallAfter() e.g.
82
83 def receiveSignal(self, **args):
84 self._callsThatDoNotTriggerGuiUpdates()
85 self.data = processArgs(args)
86 wxCallAfter( self._callsThatTriggerGuiUpdates() )
87
88 since it is likely data change occurs before the signalling, it would probably look more simply like:
89
90 def receiveSignal(self, **args):
91 wxCallAfter(self._updateUI() )
92
93 def _updateUI(self):
94 # your code that reads data
95
96 Especially if the widget can get a reference to updated data through
97 a global reference, such as via gmCurrentPatient."""
98
99 if receiver is None:
100 raise ValueError('gmDispatcher.connect(): must define <receiver>')
101
102
103
104
105
106
107
108
109 if signal is not Any:
110 signal = str(signal)
111
112 if weak:
113 receiver = safeRef(receiver)
114
115 if sender is Any:
116 _log.debug('connecting (weak=%s): <any sender> ==%s==> %s', weak, signal, receiver)
117 else:
118 _log.debug('connecting (weak=%s): %s ==%s==> %s', weak, sender, signal, receiver)
119
120 sender_identity = id(sender)
121 signals = {}
122 if sender_identity in connections:
123 signals = connections[sender_identity]
124 else:
125 connections[sender_identity] = signals
126
127 if sender not in (None, Any):
128 def _remove4weakref(object, sender_identity=sender_identity):
129 _removeSender(sender_identity=sender_identity)
130
131
132 try:
133 weakSender = weakref.ref(sender, _remove4weakref)
134 senders[sender_identity] = weakSender
135 except:
136 pass
137 receivers = []
138 if signal in signals:
139 receivers = signals[signal]
140 else:
141 signals[signal] = receivers
142 try:
143 receivers.remove(receiver)
144 except ValueError:
145 pass
146 receivers.append(receiver)
147
148
150 """Disconnect receiver from sender for signal.
151
152 Disconnecting is not required. The use of disconnect is the same as for
153 connect, only in reverse. Think of it as undoing a previous connection."""
154 if signal not in known_signals:
155 _log.warning('unknown signal [%(sig)s]', {'sig': signal})
156
157 if signal is not Any:
158 signal = str(signal)
159 if weak: receiver = safeRef(receiver)
160 sender_identity = id(sender)
161 try:
162 receivers = connections[sender_identity][signal]
163 except KeyError:
164 _log.warning('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender})
165 print('DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender))
166 return
167 try:
168 receivers.remove(receiver)
169 except ValueError:
170 _log.warning('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender})
171 print("DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender))
172 _cleanupConnections(sender_identity, signal)
173
174
175 -def send(signal=None, sender=None, **kwds):
176 """Send signal from sender to all connected receivers.
177
178 Return a list of tuple pairs [(receiver, response), ... ].
179 If sender is None, signal is sent anonymously.
180 """
181 signal = str(signal)
182 sender_identity = id(sender)
183 identity_of_Any = id(Any)
184
185
186 receivers = []
187 try:
188 receivers.extend(connections[sender_identity][signal])
189 except KeyError:
190 pass
191
192
193 anyreceivers = []
194 try:
195 anyreceivers = connections[sender_identity][Any]
196 except KeyError:
197 pass
198 for receiver in anyreceivers:
199 if receivers.count(receiver) == 0:
200 receivers.append(receiver)
201
202
203 anyreceivers = []
204 try:
205 anyreceivers = connections[identity_of_Any][signal]
206 except KeyError:
207 pass
208 for receiver in anyreceivers:
209 if receivers.count(receiver) == 0:
210 receivers.append(receiver)
211
212
213 anyreceivers = []
214 try:
215 anyreceivers = connections[identity_of_Any][Any]
216 except KeyError:
217 pass
218 for receiver in anyreceivers:
219 if receivers.count(receiver) == 0:
220 receivers.append(receiver)
221
222
223
224 responses = []
225 for receiver in receivers:
226 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)):
227 _log.debug('dereferencing weak ref receiver [%s]', receiver)
228
229 receiver = receiver()
230 _log.debug('dereferenced receiver is [%s]', receiver)
231 if receiver is None:
232
233 continue
234 try:
235 response = _call(receiver, signal=signal, sender=sender, **kwds)
236 responses += [(receiver, response)]
237 except Exception:
238 _log.exception('exception calling [%s]: (signal=%s, sender=%a, **kwds=%s)', receiver, signal, sender, str(kwds))
239
240 return responses
241
242
243
244
246 """Return a *safe* weak reference to a callable object."""
247 if hasattr(object, '__self__'):
248 if object.__self__ is not None:
249
250
251 selfkey = object.__self__
252 funckey = object.__func__
253 if selfkey not in _boundMethods:
254 _boundMethods[selfkey] = weakref.WeakKeyDictionary()
255 if funckey not in _boundMethods[selfkey]:
256 _boundMethods[selfkey][funckey] = BoundMethodWeakref(boundMethod=object)
257 return _boundMethods[selfkey][funckey]
258 return weakref.ref(object, _removeReceiver)
259
260
262 """BoundMethodWeakref class."""
263
265 """Return a weak-reference-like instance for a bound method."""
266 self.isDead = 0
267 def remove(object, self=self):
268 """Set self.isDead to true when method or instance is destroyed."""
269 self.isDead = 1
270 _removeReceiver(receiver=self)
271 self.weakSelf = weakref.ref(boundMethod.__self__, remove)
272 self.weakFunc = weakref.ref(boundMethod.__func__, remove)
273
275 """Return the closest representation."""
276 return repr(self.weakFunc)
277
279 """Return a strong reference to the bound method."""
280
281 if self.isDead:
282 return None
283
284 obj = self.weakSelf()
285 method = self.weakFunc().__name__
286 if not obj:
287 self.isDead = 1
288 _removeReceiver(receiver=self)
289 return None
290 try:
291 return getattr(obj, method)
292 except RuntimeError:
293 self.isDead = 1
294 _removeReceiver(receiver=self)
295 return None
296
297
298
299
300 -def _call(receiver, **kwds):
301 """Call receiver with only arguments it can accept."""
302
303
304
305
306
307
308
309
310
311 if hasattr(receiver, '__func__'):
312
313 func_code_def = receiver.__func__.__code__
314 acceptable_args = func_code_def.co_varnames[1:func_code_def.co_argcount]
315 elif hasattr(receiver, '__code__'):
316
317 func_code_def = receiver.__code__
318 acceptable_args = func_code_def.co_varnames[0:func_code_def.co_argcount]
319 else:
320 _log.error('<%s> must be instance, method or function, but is [%s]', str(receiver), type(receiver))
321 raise TypeError('DISPATCHER ERROR: _call(): <%s> must be instance, method or function, but is []' % (str(receiver), type(receiver)))
322
323
324 if not (func_code_def.co_flags & 0x08):
325
326
327 keys = list(kwds.keys())
328 for arg in keys:
329 if arg not in acceptable_args:
330 del kwds[arg]
331
332 if __execute_in_main_thread is None:
333 print('DISPATCHER problem: no main-thread executor available')
334 return receiver(**kwds)
335
336
337 return __execute_in_main_thread(receiver, **kwds)
338
339
341 """Remove receiver from connections."""
342 for sender_identity in connections.keys():
343 for signal in connections[sender_identity].keys():
344 receivers = connections[sender_identity][signal]
345 try:
346 receivers.remove(receiver)
347 except:
348 pass
349 _cleanupConnections(sender_identity, signal)
350
351
353 """Delete any empty signals for sender_identity. Delete sender_identity if empty."""
354 receivers = connections[sender_identity][signal]
355 if not receivers:
356
357 signals = connections[sender_identity]
358 del signals[signal]
359 if not signals:
360
361 _removeSender(sender_identity)
362
364 """Remove sender_identity from connections."""
365 del connections[sender_identity]
366
367
368 try: del senders[sender_identity]
369 except: pass
370
371
372