remove all uses of buildbot.twcompat.maybeWait, now that we don't need to maintain...
[buildbot.git] / buildbot / twcompat.py
blob69f436ecbb255e0d0867c30b2448bcf345fa0ac7
2 if 0:
3 print "hey python-mode, stop thinking I want 8-char indentation"
5 """
6 utilities to be compatible with both Twisted-1.3 and 2.0
8 implements. Use this like the following.
10 from buildbot.twcompat import implements
11 class Foo:
12 if implements:
13 implements(IFoo)
14 else:
15 __implements__ = IFoo,
17 Interface:
18 from buildbot.tcompat import Interface
19 class IFoo(Interface)
21 providedBy:
22 from buildbot.tcompat import providedBy
23 assert providedBy(obj, IFoo)
24 """
26 import os
28 from twisted.copyright import version
29 from twisted.python import components
31 # does our Twisted use zope.interface?
32 implements = None
33 if hasattr(components, "interface"):
34 # yes
35 from zope.interface import implements
36 from zope.interface import Interface
37 def providedBy(obj, iface):
38 return iface.providedBy(obj)
39 else:
40 # nope
41 from twisted.python.components import Interface
42 providedBy = components.implements
44 # waitForDeferred and getProcessOutputAndValue are twisted-2.0 things. If
45 # we're running under 1.3, patch them into place. These versions are copied
46 # from twisted somewhat after 2.0.1 .
48 from twisted.internet import defer
49 if not hasattr(defer, 'waitForDeferred'):
50 Deferred = defer.Deferred
51 class waitForDeferred:
52 """
53 API Stability: semi-stable
55 Maintainer: U{Christopher Armstrong<mailto:radix@twistedmatrix.com>}
57 waitForDeferred and deferredGenerator help you write
58 Deferred-using code that looks like it's blocking (but isn't
59 really), with the help of generators.
61 There are two important functions involved: waitForDeferred, and
62 deferredGenerator.
64 def thingummy():
65 thing = waitForDeferred(makeSomeRequestResultingInDeferred())
66 yield thing
67 thing = thing.getResult()
68 print thing #the result! hoorj!
69 thingummy = deferredGenerator(thingummy)
71 waitForDeferred returns something that you should immediately yield;
72 when your generator is resumed, calling thing.getResult() will either
73 give you the result of the Deferred if it was a success, or raise an
74 exception if it was a failure.
76 deferredGenerator takes one of these waitForDeferred-using
77 generator functions and converts it into a function that returns a
78 Deferred. The result of the Deferred will be the last
79 value that your generator yielded (remember that 'return result' won't
80 work; use 'yield result; return' in place of that).
82 Note that not yielding anything from your generator will make the
83 Deferred result in None. Yielding a Deferred from your generator
84 is also an error condition; always yield waitForDeferred(d)
85 instead.
87 The Deferred returned from your deferred generator may also
88 errback if your generator raised an exception.
90 def thingummy():
91 thing = waitForDeferred(makeSomeRequestResultingInDeferred())
92 yield thing
93 thing = thing.getResult()
94 if thing == 'I love Twisted':
95 # will become the result of the Deferred
96 yield 'TWISTED IS GREAT!'
97 return
98 else:
99 # will trigger an errback
100 raise Exception('DESTROY ALL LIFE')
101 thingummy = deferredGenerator(thingummy)
103 Put succinctly, these functions connect deferred-using code with this
104 'fake blocking' style in both directions: waitForDeferred converts from
105 a Deferred to the 'blocking' style, and deferredGenerator converts from
106 the 'blocking' style to a Deferred.
108 def __init__(self, d):
109 if not isinstance(d, Deferred):
110 raise TypeError("You must give waitForDeferred a Deferred. You gave it %r." % (d,))
111 self.d = d
113 def getResult(self):
114 if hasattr(self, 'failure'):
115 self.failure.raiseException()
116 return self.result
118 def _deferGenerator(g, deferred=None, result=None):
120 See L{waitForDeferred}.
122 while 1:
123 if deferred is None:
124 deferred = defer.Deferred()
125 try:
126 result = g.next()
127 except StopIteration:
128 deferred.callback(result)
129 return deferred
130 except:
131 deferred.errback()
132 return deferred
134 # Deferred.callback(Deferred) raises an error; we catch this case
135 # early here and give a nicer error message to the user in case
136 # they yield a Deferred. Perhaps eventually these semantics may
137 # change.
138 if isinstance(result, defer.Deferred):
139 return defer.fail(TypeError("Yield waitForDeferred(d), not d!"))
141 if isinstance(result, waitForDeferred):
142 waiting=[True, None]
143 # Pass vars in so they don't get changed going around the loop
144 def gotResult(r, waiting=waiting, result=result):
145 result.result = r
146 if waiting[0]:
147 waiting[0] = False
148 waiting[1] = r
149 else:
150 _deferGenerator(g, deferred, r)
151 def gotError(f, waiting=waiting, result=result):
152 result.failure = f
153 if waiting[0]:
154 waiting[0] = False
155 waiting[1] = f
156 else:
157 _deferGenerator(g, deferred, f)
158 result.d.addCallbacks(gotResult, gotError)
159 if waiting[0]:
160 # Haven't called back yet, set flag so that we get reinvoked
161 # and return from the loop
162 waiting[0] = False
163 return deferred
164 else:
165 result = waiting[1]
167 def func_metamerge(f, g):
169 Merge function metadata from f -> g and return g
171 try:
172 g.__doc__ = f.__doc__
173 g.__dict__.update(f.__dict__)
174 g.__name__ = f.__name__
175 except (TypeError, AttributeError):
176 pass
177 return g
179 def deferredGenerator(f):
181 See L{waitForDeferred}.
183 def unwindGenerator(*args, **kwargs):
184 return _deferGenerator(f(*args, **kwargs))
185 return func_metamerge(f, unwindGenerator)
187 defer.waitForDeferred = waitForDeferred
188 defer.deferredGenerator = deferredGenerator
190 from twisted.internet import utils
191 if not hasattr(utils, "getProcessOutputAndValue"):
192 from twisted.internet import reactor, protocol
193 _callProtocolWithDeferred = utils._callProtocolWithDeferred
194 try:
195 import cStringIO
196 StringIO = cStringIO
197 except ImportError:
198 import StringIO
200 class _EverythingGetter(protocol.ProcessProtocol):
202 def __init__(self, deferred):
203 self.deferred = deferred
204 self.outBuf = StringIO.StringIO()
205 self.errBuf = StringIO.StringIO()
206 self.outReceived = self.outBuf.write
207 self.errReceived = self.errBuf.write
209 def processEnded(self, reason):
210 out = self.outBuf.getvalue()
211 err = self.errBuf.getvalue()
212 e = reason.value
213 code = e.exitCode
214 if e.signal:
215 self.deferred.errback((out, err, e.signal))
216 else:
217 self.deferred.callback((out, err, code))
219 def getProcessOutputAndValue(executable, args=(), env={}, path='.',
220 reactor=reactor):
221 """Spawn a process and returns a Deferred that will be called back
222 with its output (from stdout and stderr) and it's exit code as (out,
223 err, code) If a signal is raised, the Deferred will errback with the
224 stdout and stderr up to that point, along with the signal, as (out,
225 err, signalNum)
227 return _callProtocolWithDeferred(_EverythingGetter,
228 executable, args, env, path,
229 reactor)
230 utils.getProcessOutputAndValue = getProcessOutputAndValue
233 # copied from Twisted circa 2.2.0
234 def _which(name, flags=os.X_OK):
235 """Search PATH for executable files with the given name.
237 @type name: C{str}
238 @param name: The name for which to search.
240 @type flags: C{int}
241 @param flags: Arguments to L{os.access}.
243 @rtype: C{list}
244 @return: A list of the full paths to files found, in the
245 order in which they were found.
247 result = []
248 exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep))
249 for p in os.environ['PATH'].split(os.pathsep):
250 p = os.path.join(p, name)
251 if os.access(p, flags):
252 result.append(p)
253 for e in exts:
254 pext = p + e
255 if os.access(pext, flags):
256 result.append(pext)
257 return result
259 which = _which
260 try:
261 from twisted.python.procutils import which
262 except ImportError:
263 pass