Mercurial > fife-parpg
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] |