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 if signal is not Any:
103 signal = str(signal)
104
105 if weak:
106 receiver = safeRef(receiver)
107
108 if sender is Any:
109 _log.debug('connecting (weak=%s): <any sender> ==%s==> %s', weak, signal, receiver)
110 else:
111 _log.debug('connecting (weak=%s): %s ==%s==> %s', weak, sender, signal, receiver)
112
113 sender_identity = id(sender)
114 signals = {}
115 if sender_identity in connections:
116 signals = connections[sender_identity]
117 else:
118 connections[sender_identity] = signals
119
120 if sender not in (None, Any):
121 def _remove4weakref(object, sender_identity=sender_identity):
122 _removeSender(sender_identity=sender_identity)
123
124
125 try:
126 weakSender = weakref.ref(sender, _remove4weakref)
127 senders[sender_identity] = weakSender
128 except:
129 pass
130 receivers = []
131 if signal in signals:
132 receivers = signals[signal]
133 else:
134 signals[signal] = receivers
135 try:
136 receivers.remove(receiver)
137 except ValueError:
138 pass
139 receivers.append(receiver)
140
141
143 """Disconnect receiver from sender for signal.
144
145 Disconnecting is not required. The use of disconnect is the same as for
146 connect, only in reverse. Think of it as undoing a previous connection."""
147 if signal not in known_signals:
148 _log.warning('unknown signal [%(sig)s]', {'sig': signal})
149
150 if signal is not Any:
151 signal = str(signal)
152 if weak: receiver = safeRef(receiver)
153 sender_identity = id(sender)
154 try:
155 receivers = connections[sender_identity][signal]
156 except KeyError:
157 _log.warning('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender})
158 print('DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender))
159 return
160 try:
161 receivers.remove(receiver)
162 except ValueError:
163 _log.warning('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender})
164 print("DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender))
165 _cleanupConnections(sender_identity, signal)
166
167
168 -def send(signal=None, sender=None, **kwds):
169 """Send signal from sender to all connected receivers.
170
171 Return a list of tuple pairs [(receiver, response), ... ].
172 If sender is None, signal is sent anonymously.
173 """
174 signal = str(signal)
175 sender_identity = id(sender)
176 identity_of_Any = id(Any)
177
178
179 receivers = []
180 try:
181 receivers.extend(connections[sender_identity][signal])
182 except KeyError:
183 pass
184
185
186 anyreceivers = []
187 try:
188 anyreceivers = connections[sender_identity][Any]
189 except KeyError:
190 pass
191 for receiver in anyreceivers:
192 if receivers.count(receiver) == 0:
193 receivers.append(receiver)
194
195
196 anyreceivers = []
197 try:
198 anyreceivers = connections[identity_of_Any][signal]
199 except KeyError:
200 pass
201 for receiver in anyreceivers:
202 if receivers.count(receiver) == 0:
203 receivers.append(receiver)
204
205
206 anyreceivers = []
207 try:
208 anyreceivers = connections[identity_of_Any][Any]
209 except KeyError:
210 pass
211 for receiver in anyreceivers:
212 if receivers.count(receiver) == 0:
213 receivers.append(receiver)
214
215
216
217 responses = []
218 for receiver in receivers:
219 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)):
220 _log.debug('dereferencing weak_ref receiver [%s]', receiver)
221
222 receiver = receiver()
223 _log.debug('dereferenced receiver is [%s]', receiver)
224 if receiver is None:
225
226 continue
227 try:
228 response = _call(receiver, signal=signal, sender=sender, **kwds)
229 responses += [(receiver, response)]
230 except Exception:
231 _log.exception('exception calling [%s]: (signal=%s, sender=%a, **kwds=%s)', receiver, signal, sender, str(kwds))
232
233 return responses
234
235
236
237
239 """Return a *safe* weak reference to a callable object."""
240 if hasattr(object, '__self__'):
241 if object.__self__ is not None:
242
243
244 selfkey = object.__self__
245 funckey = object.__func__
246 if selfkey not in _boundMethods:
247 _boundMethods[selfkey] = weakref.WeakKeyDictionary()
248 if funckey not in _boundMethods[selfkey]:
249 _boundMethods[selfkey][funckey] = BoundMethodWeakref(boundMethod=object)
250 return _boundMethods[selfkey][funckey]
251 return weakref.ref(object, _removeReceiver)
252
253
255 """BoundMethodWeakref class."""
256
258 """Return a weak-reference-like instance for a bound method."""
259 self.isDead = 0
260 def remove(object, self=self):
261 """Set self.isDead to true when method or instance is destroyed."""
262 self.isDead = 1
263 print('BoundMethodWeakref.__init__.remove(): _removeReceiver =', _removeReceiver)
264 print('BoundMethodWeakref.__init__.remove(): self =', self)
265 _removeReceiver(receiver=self)
266 self.weakSelf = weakref.ref(boundMethod.__self__, remove)
267 self.weakFunc = weakref.ref(boundMethod.__func__, remove)
268
270 """Return the closest representation."""
271 return repr(self.weakFunc)
272
274 """Return a strong reference to the bound method."""
275
276 if self.isDead:
277 return None
278
279 obj = self.weakSelf()
280 method = self.weakFunc().__name__
281 if not obj:
282 self.isDead = 1
283 _removeReceiver(receiver=self)
284 return None
285 try:
286 return getattr(obj, method)
287 except RuntimeError:
288 self.isDead = 1
289 _removeReceiver(receiver=self)
290 return None
291
292
293
294
295 -def _call(receiver, **kwds):
296 """Call receiver with only arguments it can accept."""
297
298
299
300
301
302
303
304
305
306 if hasattr(receiver, '__func__'):
307
308 func_code_def = receiver.__func__.__code__
309 acceptable_args = func_code_def.co_varnames[1:func_code_def.co_argcount]
310 elif hasattr(receiver, '__code__'):
311
312 func_code_def = receiver.__code__
313 acceptable_args = func_code_def.co_varnames[0:func_code_def.co_argcount]
314 else:
315 _log.error('<%s> must be instance, method or function, but is [%s]', str(receiver), type(receiver))
316 raise TypeError('DISPATCHER ERROR: _call(): <%s> must be instance, method or function, but is []' % (str(receiver), type(receiver)))
317
318
319 if not (func_code_def.co_flags & 0x08):
320
321
322 keys = list(kwds.keys())
323 for arg in keys:
324 if arg not in acceptable_args:
325 del kwds[arg]
326
327 if __execute_in_main_thread is None:
328 print('DISPATCHER problem: no main-thread executor available')
329 return receiver(**kwds)
330
331
332 return __execute_in_main_thread(receiver, **kwds)
333
334
336 """Remove receiver from connections."""
337 for sender_identity in connections.keys():
338 for signal in connections[sender_identity].keys():
339 receivers = connections[sender_identity][signal]
340 try:
341 receivers.remove(receiver)
342 except:
343 pass
344 _cleanupConnections(sender_identity, signal)
345
346
348 """Delete any empty signals for sender_identity. Delete sender_identity if empty."""
349 receivers = connections[sender_identity][signal]
350 if not receivers:
351
352 signals = connections[sender_identity]
353 del signals[signal]
354 if not signals:
355
356 _removeSender(sender_identity)
357
359 """Remove sender_identity from connections."""
360 del connections[sender_identity]
361
362
363 try: del senders[sender_identity]
364 except: pass
365
366
367