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'statustext',
27 u'display_widget',
28 u'plugin_loaded',
29 u'application_closing',
30 u'request_user_attention',
31 u'clin_item_updated',
32 u'register_pre_exit_callback'
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
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 WARNING: 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
108 if sender not in (None, Any):
109 def remove(object, senderkey=senderkey):
110 _removeSender(senderkey=senderkey)
111
112
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
153 -def send(signal=None, sender=None, **kwds):
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
167 receivers = []
168 try: receivers.extend(connections[senderkey][signal])
169 except KeyError: pass
170
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
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
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
192
193 responses = []
194 for receiver in receivers:
195 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)):
196
197 receiver = receiver()
198 if receiver is None:
199
200 continue
201 try:
202 response = _call(receiver, signal=signal, sender=sender, **kwds)
203 responses += [(receiver, response)]
204 except:
205
206
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
218
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
243 """Return the closest representation."""
244 return repr(self.weakFunc)
245
264
265
266
267 -def _call(receiver, **kwds):
268 """Call receiver with only arguments it can accept."""
269 if type(receiver) is types.InstanceType:
270
271
272 receiver = receiver.__call__
273 if hasattr(receiver, 'im_func'):
274
275 fc = receiver.im_func.func_code
276 acceptable_args = fc.co_varnames[1:fc.co_argcount]
277 elif hasattr(receiver, 'func_code'):
278
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
286
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
306 signals = connections[senderkey]
307 del signals[signal]
308 if not signals:
309
310 _removeSender(senderkey)
311
313 """Remove senderkey from connections."""
314 del connections[senderkey]
315
316
317 try: del senders[senderkey]
318 except: pass
319
320
321