1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
"""Promise implementation."""
from __future__ import absolute_import, unicode_literals
import sys
from collections import deque
import inspect
from weakref import ref
try:
from weakref import WeakMethod
except ImportError:
from vine.backports.weakref_backports import WeakMethod
from .abstract import Thenable
from .five import python_2_unicode_compatible, reraise
__all__ = ['promise']
@Thenable.register
@python_2_unicode_compatible
class promise(object):
"""Promise of future evaluation.
This is a special implementation of promises in that it can
be used both for "promise of a value" and lazy evaluation.
The biggest upside for this is that everything in a promise can also be
a promise, e.g. filters, callbacks and errbacks can all be promises.
Usage examples:
.. code-block:: python
>>> from __future__ import print_statement # noqa
>>> p = promise()
>>> p.then(promise(print, ('OK',))) # noqa
>>> p.on_error = promise(print, ('ERROR',)) # noqa
>>> p(20)
OK, 20
>>> p.then(promise(print, ('hello',))) # noqa
hello, 20
>>> p.throw(KeyError('foo'))
ERROR, KeyError('foo')
>>> p2 = promise()
>>> p2.then(print) # noqa
>>> p2.cancel()
>>> p(30)
Example:
.. code-block:: python
from vine import promise, wrap
class Protocol(object):
def __init__(self):
self.buffer = []
def receive_message(self):
return self.read_header().then(
self.read_body).then(
wrap(self.prepare_body))
def read(self, size, callback=None):
callback = callback or promise()
tell_eventloop_to_read(size, callback)
return callback
def read_header(self, callback=None):
return self.read(4, callback)
def read_body(self, header, callback=None):
body_size, = unpack('>L', header)
return self.read(body_size, callback)
def prepare_body(self, value):
self.buffer.append(value)
"""
if not hasattr(sys, 'pypy_version_info'): # pragma: no cover
__slots__ = (
'fun', 'args', 'kwargs', 'ready', 'failed',
'value', 'ignore_result', 'reason', '_svpending', '_lvpending',
'on_error', 'cancelled', 'weak', '__weakref__',
)
def __init__(self, fun=None, args=None, kwargs=None,
callback=None, on_error=None, weak=False,
ignore_result=False):
self.weak = weak
self.ignore_result = ignore_result
self.fun = self._get_fun_or_weakref(fun=fun, weak=weak)
self.args = args or ()
self.kwargs = kwargs or {}
self.ready = False
self.failed = False
self.value = None
self.reason = None
# Optimization
# Most promises will only have one callback, so we optimize for this
# case by using a list only when there are multiple callbacks.
# s(calar) pending / l(ist) pending
self._svpending = None
self._lvpending = None
self.on_error = on_error
self.cancelled = False
if callback is not None:
self.then(callback)
if self.fun:
assert self.fun and callable(fun)
@staticmethod
def _get_fun_or_weakref(fun, weak):
"""Return the callable or a weak reference.
Handles both bound and unbound methods.
"""
if not weak:
return fun
if inspect.ismethod(fun):
return WeakMethod(fun)
else:
return ref(fun)
def __repr__(self):
return ('<{0} --> {1!r}>' if self.fun else '<{0}>').format(
'{0}@0x{1:x}'.format(type(self).__name__, id(self)), self.fun,
)
def cancel(self):
self.cancelled = True
try:
if self._svpending is not None:
self._svpending.cancel()
if self._lvpending is not None:
for pending in self._lvpending:
pending.cancel()
if isinstance(self.on_error, Thenable):
self.on_error.cancel()
finally:
self._svpending = self._lvpending = self.on_error = None
def __call__(self, *args, **kwargs):
retval = None
if self.cancelled:
return
final_args = self.args + args if args else self.args
final_kwargs = dict(self.kwargs, **kwargs) if kwargs else self.kwargs
# self.fun may be a weakref
fun = self._fun_is_alive(self.fun)
if fun is not None:
try:
if self.ignore_result:
fun(*final_args, **final_kwargs)
ca = ()
ck = {}
else:
retval = fun(*final_args, **final_kwargs)
self.value = (ca, ck) = (retval,), {}
except Exception:
return self.throw()
else:
self.value = (ca, ck) = final_args, final_kwargs
self.ready = True
svpending = self._svpending
if svpending is not None:
try:
svpending(*ca, **ck)
finally:
self._svpending = None
else:
lvpending = self._lvpending
try:
while lvpending:
p = lvpending.popleft()
p(*ca, **ck)
finally:
self._lvpending = None
return retval
def _fun_is_alive(self, fun):
return fun() if self.weak else self.fun
def then(self, callback, on_error=None):
if not isinstance(callback, Thenable):
callback = promise(callback, on_error=on_error)
if self.cancelled:
callback.cancel()
return callback
if self.failed:
callback.throw(self.reason)
elif self.ready:
args, kwargs = self.value
callback(*args, **kwargs)
if self._lvpending is None:
svpending = self._svpending
if svpending is not None:
self._svpending, self._lvpending = None, deque([svpending])
else:
self._svpending = callback
return callback
self._lvpending.append(callback)
return callback
def throw1(self, exc=None):
if not self.cancelled:
exc = exc if exc is not None else sys.exc_info()[1]
self.failed, self.reason = True, exc
if self.on_error:
self.on_error(*self.args + (exc,), **self.kwargs)
def throw(self, exc=None, tb=None, propagate=True):
if not self.cancelled:
current_exc = sys.exc_info()[1]
exc = exc if exc is not None else current_exc
try:
self.throw1(exc)
svpending = self._svpending
if svpending is not None:
try:
svpending.throw1(exc)
finally:
self._svpending = None
else:
lvpending = self._lvpending
try:
while lvpending:
lvpending.popleft().throw1(exc)
finally:
self._lvpending = None
finally:
if self.on_error is None and propagate:
if tb is None and (exc is None or exc is current_exc):
raise
reraise(type(exc), exc, tb)
@property
def listeners(self):
if self._lvpending:
return self._lvpending
return [self._svpending]