comparison tools/editor/scripts/events/signal.py @ 378:64738befdf3b

bringing in the changes from the build_system_rework branch in preparation for the 0.3.0 release. This commit will require the Jan2010 devkit. Clients will also need to be modified to the new way to import fife.
author vtchill@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 11 Jan 2010 23:34:52 +0000
parents
children fa1373b9fa16
comparison
equal deleted inserted replaced
377:fe6fb0e0ed23 378:64738befdf3b
1 """Multi-consumer multi-producer dispatching mechanism
2
3 Originally based on pydispatch (BSD) http://pypi.python.org/pypi/PyDispatcher/2.0.1
4 See license.txt for original license.
5
6 Heavily modified for Django's purposes.
7
8 Copied from django v1.1 beta 1
9 Changes:
10 * Receivers aren't needed to accept any arguments
11 * _live_receivers() now work on a copy of self.receivers, which fixes a bug when
12 connecting and disconnecting during send()
13 """
14
15 import weakref
16 import saferef
17 from fife.extensions import pychan
18
19 WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
20
21 debug = True
22
23 def _make_id(target):
24 if hasattr(target, 'im_func'):
25 return (id(target.im_self), id(target.im_func))
26 return id(target)
27
28 class Signal(object):
29 """Base class for all signals
30
31 Internal attributes:
32 receivers -- { receriverkey (id) : weakref(receiver) }
33 """
34
35 def __init__(self, providing_args=None):
36 """providing_args -- A list of the arguments this signal can pass along in
37 a send() call.
38 """
39 self.receivers = []
40 if providing_args is None:
41 providing_args = []
42 self.providing_args = set(providing_args)
43
44 def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
45 """Connect receiver to sender for signal
46
47 receiver -- a function or an instance method which is to
48 receive signals. Receivers must be
49 hashable objects.
50
51 if weak is True, then receiver must be weak-referencable
52 (more precisely saferef.safeRef() must be able to create
53 a reference to the receiver).
54
55 Receivers must be able to accept keyword arguments.
56
57 If receivers have a dispatch_uid attribute, the receiver will
58 not be added if another receiver already exists with that
59 dispatch_uid.
60
61 sender -- the sender to which the receiver should respond
62 Must either be of type Signal, or None to receive events
63 from any sender.
64
65 weak -- whether to use weak references to the receiver
66 By default, the module will attempt to use weak
67 references to the receiver objects. If this parameter
68 is false, then strong references will be used.
69
70 dispatch_uid -- an identifier used to uniquely identify a particular
71 instance of a receiver. This will usually be a string, though it
72 may be anything hashable.
73
74 returns None
75 """
76
77 # If DEBUG is on, check that we got a good receiver
78 if debug:
79 import inspect
80 assert callable(receiver), "Signal receivers must be callable."
81
82 # Check for **kwargs
83 # Not all callables are inspectable with getargspec, so we'll
84 # try a couple different ways but in the end fall back on assuming
85 # it is -- we don't want to prevent registration of valid but weird
86 # callables.
87 try:
88 argspec = inspect.getargspec(receiver)
89 except TypeError:
90 try:
91 argspec = inspect.getargspec(receiver.__call__)
92 except (TypeError, AttributeError):
93 argspec = None
94
95 if dispatch_uid:
96 lookup_key = (dispatch_uid, _make_id(sender))
97 else:
98 lookup_key = (_make_id(receiver), _make_id(sender))
99
100 if weak:
101 receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver)
102
103 for r_key, _ in self.receivers:
104 if r_key == lookup_key:
105 break
106 else:
107 self.receivers.append((lookup_key, receiver))
108
109 def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
110 """Disconnect receiver from sender for signal
111
112 receiver -- the registered receiver to disconnect. May be none if
113 dispatch_uid is specified.
114 sender -- the registered sender to disconnect
115 weak -- the weakref state to disconnect
116 dispatch_uid -- the unique identifier of the receiver to disconnect
117
118 disconnect reverses the process of connect.
119
120 If weak references are used, disconnect need not be called.
121 The receiver will be remove from dispatch automatically.
122
123 returns None
124 """
125
126 if dispatch_uid:
127 lookup_key = (dispatch_uid, _make_id(sender))
128 else:
129 lookup_key = (_make_id(receiver), _make_id(sender))
130
131 for idx, (r_key, _) in enumerate(self.receivers):
132 if r_key == lookup_key:
133 del self.receivers[idx]
134
135 def send(self, sender, **named):
136 """Send signal from sender to all connected receivers.
137
138 sender -- the sender of the signal
139 Either a specific object or None.
140
141 named -- named arguments which will be passed to receivers.
142
143 Returns a list of tuple pairs [(receiver, response), ... ].
144
145 If any receiver raises an error, the error propagates back
146 through send, terminating the dispatch loop, so it is quite
147 possible to not have all receivers called if a raises an
148 error.
149 """
150
151 responses = []
152 if not self.receivers:
153 return responses
154
155 for receiver in self._live_receivers(_make_id(sender)):
156 response = pychan.tools.applyOnlySuitable(receiver, signal=self, sender=sender, **named)
157 responses.append((receiver, response))
158 return responses
159
160 def send_robust(self, sender, **named):
161 """Send signal from sender to all connected receivers catching errors
162
163 sender -- the sender of the signal
164 Can be any python object (normally one registered with
165 a connect if you actually want something to occur).
166
167 named -- named arguments which will be passed to receivers.
168 These arguments must be a subset of the argument names
169 defined in providing_args.
170
171 Return a list of tuple pairs [(receiver, response), ... ],
172 may raise DispatcherKeyError
173
174 if any receiver raises an error (specifically any subclass of Exception),
175 the error instance is returned as the result for that receiver.
176 """
177
178 responses = []
179 if not self.receivers:
180 return responses
181
182 # Call each receiver with whatever arguments it can accept.
183 # Return a list of tuple pairs [(receiver, response), ... ].
184 for receiver in self._live_receivers(_make_id(sender)):
185 try:
186 response = pychan.tools.applyOnlySuitable(receiver, signal=self, sender=sender, **named)
187 except Exception, err:
188 responses.append((receiver, err))
189 else:
190 responses.append((receiver, response))
191 return responses
192
193 def _live_receivers(self, senderkey):
194 """Filter sequence of receivers to get resolved, live receivers
195
196 This checks for weak references
197 and resolves them, then returning only live
198 receivers.
199 """
200 none_senderkey = _make_id(None)
201
202 for (receiverkey, r_senderkey), receiver in self.receivers[:]:
203 if r_senderkey == none_senderkey or r_senderkey == senderkey:
204 if isinstance(receiver, WEAKREF_TYPES):
205 # Dereference the weak reference.
206 receiver = receiver()
207 if receiver is not None:
208 yield receiver
209 else:
210 yield receiver
211
212 def _remove_receiver(self, receiver):
213 """Remove dead receivers from connections."""
214
215 to_remove = []
216 for key, connected_receiver in self.receivers:
217 if connected_receiver == receiver:
218 to_remove.append(key)
219 for key in to_remove:
220 for idx, (r_key, _) in enumerate(self.receivers):
221 if r_key == key:
222 del self.receivers[idx]