Ticket #411: defer.py

File defer.py, 4.4 KB (added by etrepum, 11 years ago)
Line 
1#
2#  This interface is heavily inspired by twisted.internet.defer.
3#  It's a little less extensible, but it won't let you blow the stack!
4#
5
6from microfailure import Failure
7
8__all__ = ['AlreadyCalledError', 'succeed', 'passthrough', 'Deferred']
9
10class AlreadyCalledError(Exception):
11    pass
12
13class _nothing(object):
14    pass
15
16def passthrough(arg):
17    return arg
18
19class Deferred(object):
20    called = False
21    inprocess = False
22    waiting = []
23    paused = 0
24    count = 0
25   
26    def __init__(self):
27        Deferred.count += 1
28        self.count = Deferred.count
29        self.callbacks = []
30        self.waiting = False
31           
32    def addCallbacks(self, callback=passthrough, errback=passthrough,
33        callbackArgs=(), callbackKeywords={},
34        errbackArgs=(), errbackKeywords={}):
35        self.callbacks.append(((callback, callbackArgs, callbackKeywords),
36            (errback, errbackArgs, errbackKeywords)))
37        if self.called:
38            self._runCallbacks()
39        return self
40
41    def addCallback(self, callback, *args, **kwargs):
42        return self.addCallbacks(
43            callback=callback, callbackArgs=args, callbackKeywords=kwargs)
44
45    def addErrback(self, errback, *args, **kwargs):
46        return self.addCallbacks(
47            errback=errback, errbackArgs=args, errbackKeywords=kwargs)
48
49    def addBoth(self, both, *args, **kwargs):
50        return self.addCallbacks(
51            callback=both, errback=both, callbackArgs=args,
52            callbackKeywords=kwargs, errbackArgs=args, errbackKeywords=kwargs)
53
54    def fork(self):
55        return succeed(self)
56
57    def pause(self):
58        self.paused += 1
59
60    def unpause(self):
61        self.paused -= 1
62        if not self.paused and self.called:
63            self._runCallbacks()
64
65    def _continue(self, result):
66        self.result = result
67        self.unpause()
68
69    def callback(self, result):
70        self._startRunCallbacks(result)
71        return self
72
73    def errback(self, fail=None):
74        if not isinstance(fail, Failure):
75            fail = Failure(fail)
76        self._startRunCallbacks(fail)
77        return self
78
79    def _startRunCallbacks(self, result):
80        if self.called:
81            raise AlreadyCalledError
82        self.called = True
83        self.result = result
84        self._runCallbacks()
85
86    def _runCallbacks(self):
87        # wow is this scary :)
88        waiting = Deferred.waiting
89        if not self.waiting:
90            waiting.append(self)
91        if Deferred.inprocess:
92            return
93        Deferred.inprocess = True
94        while waiting:
95            self = waiting.pop()
96            self.waiting = False
97            # implicit callback chaining
98            if isinstance(self.result, Deferred):
99                self.pause()
100                self.result.addBoth(self._continue)
101            elif not self.paused:
102                cb = self.callbacks
103                while cb:
104                    fn, args, kwargs = cb.pop(0)[isinstance(self.result, Failure)]
105                    try:
106                        self.result = fn(self.result, *args, **kwargs)
107                        if isinstance(self.result, Deferred):
108                            self.pause()
109                            self.result.addBoth(self._continue)
110                            break
111                    except:
112                        self.result = Failure()
113            if isinstance(self.result, Failure):
114                self.result.cleanFailure()
115        Deferred.inprocess = False
116
117    def __str__(self):
118        if hasattr(self, 'result'):
119            res = self.result
120            if isinstance(res, Deferred):
121                return "<Deferred #%s current: [Deferred #%s]>" % (
122                    self.count, res.count)
123            return "<Deferred #%s current: %r>" % (
124                self.count, res)
125        return "<Deferred #%s>" % (self.count,)
126    __repr__ = __str__
127       
128class succeed(Deferred):
129    called = True
130    def __init__(self, result):
131        Deferred.__init__(self)
132        self.result = result
133   
134class fail(Deferred):
135    called = True
136    def __init__(self, result=_nothing):
137        Deferred.__init__(self)
138        self.result = result is _nothing and Failure() or result
139 
140if __name__ == '__main__':
141    end = 10000
142    values = iter(range(end+1))
143    def print_(v):
144        print v
145    def next(v):
146        if v < end:
147            return succeed(values.next()).addCallback(next)
148        return v
149    rv = succeed(values.next()).addCallback(next).addCallback(print_)