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