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'pre_patient_selection',
21 u'post_patient_selection',
22 u'patient_locked',
23 u'patient_unlocked',
24 u'import_document_from_file',
25 u'import_document_from_files',
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 u'focus_patient_search',
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
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
135 if signal is not Any:
136 signal = str(signal)
137 if weak: receiver = safeRef(receiver)
138 senderkey = id(sender)
139 try:
140 receivers = connections[senderkey][signal]
141 except KeyError:
142 _log.error('no receivers for signal %(sig)s from sender %(sender)s', {'sig': repr(signal), 'sender': sender})
143 print 'DISPATCHER ERROR: no receivers for signal %s from sender %s' % (repr(signal), sender)
144 return
145 try:
146 receivers.remove(receiver)
147 except ValueError:
148 _log.error('receiver [%(rx)s] not connected to signal [%(sig)s] from [%(sender)s]', {'rx': receiver, 'sig': repr(signal), 'sender': sender})
149 print "DISPATCHER ERROR: receiver [%s] not connected to signal [%s] from [%s]" % (receiver, repr(signal), sender)
150 _cleanupConnections(senderkey, signal)
151
152 -def send(signal=None, sender=None, **kwds):
153 """Send signal from sender to all connected receivers.
154
155 Return a list of tuple pairs [(receiver, response), ... ].
156 If sender is None, signal is sent anonymously.
157 """
158 signal = str(signal)
159 senderkey = id(sender)
160 anykey = id(Any)
161
162 receivers = []
163 try: receivers.extend(connections[senderkey][signal])
164 except KeyError: pass
165
166 anyreceivers = []
167 try: anyreceivers = connections[senderkey][Any]
168 except KeyError: pass
169 for receiver in anyreceivers:
170 if receivers.count(receiver) == 0:
171 receivers.append(receiver)
172
173 anyreceivers = []
174 try: anyreceivers = connections[anykey][signal]
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][Any]
182 except KeyError: pass
183 for receiver in anyreceivers:
184 if receivers.count(receiver) == 0:
185 receivers.append(receiver)
186
187
188 responses = []
189 for receiver in receivers:
190 if (type(receiver) is weakref.ReferenceType) or (isinstance(receiver, BoundMethodWeakref)):
191
192 receiver = receiver()
193 if receiver is None:
194
195 continue
196 try:
197 response = _call(receiver, signal=signal, sender=sender, **kwds)
198 responses += [(receiver, response)]
199 except:
200
201
202 typ, val, tb = sys.exc_info()
203 _log.critical('%(t)s, <%(v)s>', {'t': typ, 'v': val})
204 _log.critical('calling <%(rx)s> failed', {'rx': str(receiver)})
205 traceback.print_tb(tb)
206 return responses
207
209 """Return a *safe* weak reference to a callable object."""
210 if hasattr(object, 'im_self'):
211 if object.im_self is not None:
212
213
214 selfkey = object.im_self
215 funckey = object.im_func
216 if not _boundMethods.has_key(selfkey):
217 _boundMethods[selfkey] = weakref.WeakKeyDictionary()
218 if not _boundMethods[selfkey].has_key(funckey):
219 _boundMethods[selfkey][funckey] = \
220 BoundMethodWeakref(boundMethod=object)
221 return _boundMethods[selfkey][funckey]
222 return weakref.ref(object, _removeReceiver)
223
225 """BoundMethodWeakref class."""
226
228 """Return a weak-reference-like instance for a bound method."""
229 self.isDead = 0
230 def remove(object, self=self):
231 """Set self.isDead to true when method or instance is destroyed."""
232 self.isDead = 1
233 _removeReceiver(receiver=self)
234 self.weakSelf = weakref.ref(boundMethod.im_self, remove)
235 self.weakFunc = weakref.ref(boundMethod.im_func, remove)
236
238 """Return the closest representation."""
239 return repr(self.weakFunc)
240
259
260
261
262 -def _call(receiver, **kwds):
263 """Call receiver with only arguments it can accept."""
264 if type(receiver) is types.InstanceType:
265
266
267 receiver = receiver.__call__
268 if hasattr(receiver, 'im_func'):
269
270 fc = receiver.im_func.func_code
271 acceptable_args = fc.co_varnames[1:fc.co_argcount]
272 elif hasattr(receiver, 'func_code'):
273
274 fc = receiver.func_code
275 acceptable_args = fc.co_varnames[0:fc.co_argcount]
276 else:
277 _log.error('<%(rx)s> must be instance, method or function', {'rx': str(receiver)})
278 print 'DISPATCHER ERROR: _call(): <%s> must be instance, method or function' % str(receiver)
279 if not (fc.co_flags & 8):
280
281
282 for arg in kwds.keys():
283 if arg not in acceptable_args:
284 del kwds[arg]
285 return receiver(**kwds)
286
288 """Remove receiver from connections."""
289 for senderkey in connections.keys():
290 for signal in connections[senderkey].keys():
291 receivers = connections[senderkey][signal]
292 try: receivers.remove(receiver)
293 except: pass
294 _cleanupConnections(senderkey, signal)
295
297 """Delete any empty signals for senderkey. Delete senderkey if empty."""
298 receivers = connections[senderkey][signal]
299 if not receivers:
300
301 signals = connections[senderkey]
302 del signals[signal]
303 if not signals:
304
305 _removeSender(senderkey)
306
308 """Remove senderkey from connections."""
309 del connections[senderkey]
310
311
312 try: del senders[senderkey]
313 except: pass
314
315
316