| 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 | |
|---|
| 6 | from microfailure import Failure |
|---|
| 7 | |
|---|
| 8 | __all__ = ['AlreadyCalledError', 'succeed', 'passthrough', 'Deferred'] |
|---|
| 9 | |
|---|
| 10 | class AlreadyCalledError(Exception): |
|---|
| 11 | pass |
|---|
| 12 | |
|---|
| 13 | class _nothing(object): |
|---|
| 14 | pass |
|---|
| 15 | |
|---|
| 16 | def passthrough(arg): |
|---|
| 17 | return arg |
|---|
| 18 | |
|---|
| 19 | class 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 | |
|---|
| 128 | class succeed(Deferred): |
|---|
| 129 | called = True |
|---|
| 130 | def __init__(self, result): |
|---|
| 131 | Deferred.__init__(self) |
|---|
| 132 | self.result = result |
|---|
| 133 | |
|---|
| 134 | class 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 | |
|---|
| 140 | if __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_) |
|---|