more NEWS items
[buildbot.git] / buildbot / twcompat.py
blobb9bd823766066e8fe151358621213c6d3865e4f9
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 # are we using a version of Trial that allows setUp/testFoo/tearDown to
45 # return Deferreds?
46 oldtrial = version.startswith("1.3")
48 # use this at the end of setUp/testFoo/tearDown methods
49 def maybeWait(d, timeout="none"):
50 from twisted.python import failure
51 from twisted.trial import unittest
52 if oldtrial:
53 # this is required for oldtrial (twisted-1.3.0) compatibility. When we
54 # move to retrial (twisted-2.0.0), replace these with a simple 'return
55 # d'.
56 try:
57 if timeout == "none":
58 unittest.deferredResult(d)
59 else:
60 unittest.deferredResult(d, timeout)
61 except failure.Failure, f:
62 if f.check(unittest.SkipTest):
63 raise f.value
64 raise
65 return None
66 return d
68 # waitForDeferred and getProcessOutputAndValue are twisted-2.0 things. If
69 # we're running under 1.3, patch them into place. These versions are copied
70 # from twisted somewhat after 2.0.1 .
72 from twisted.internet import defer
73 if not hasattr(defer, 'waitForDeferred'):
74 Deferred = defer.Deferred
75 class waitForDeferred:
76 """
77 API Stability: semi-stable
79 Maintainer: U{Christopher Armstrong<mailto:radix@twistedmatrix.com>}
81 waitForDeferred and deferredGenerator help you write
82 Deferred-using code that looks like it's blocking (but isn't
83 really), with the help of generators.
85 There are two important functions involved: waitForDeferred, and
86 deferredGenerator.
88 def thingummy():
89 thing = waitForDeferred(makeSomeRequestResultingInDeferred())
90 yield thing
91 thing = thing.getResult()
92 print thing #the result! hoorj!
93 thingummy = deferredGenerator(thingummy)
95 waitForDeferred returns something that you should immediately yield;
96 when your generator is resumed, calling thing.getResult() will either
97 give you the result of the Deferred if it was a success, or raise an
98 exception if it was a failure.
100 deferredGenerator takes one of these waitForDeferred-using
101 generator functions and converts it into a function that returns a
102 Deferred. The result of the Deferred will be the last
103 value that your generator yielded (remember that 'return result' won't
104 work; use 'yield result; return' in place of that).
106 Note that not yielding anything from your generator will make the
107 Deferred result in None. Yielding a Deferred from your generator
108 is also an error condition; always yield waitForDeferred(d)
109 instead.
111 The Deferred returned from your deferred generator may also
112 errback if your generator raised an exception.
114 def thingummy():
115 thing = waitForDeferred(makeSomeRequestResultingInDeferred())
116 yield thing
117 thing = thing.getResult()
118 if thing == 'I love Twisted':
119 # will become the result of the Deferred
120 yield 'TWISTED IS GREAT!'
121 return
122 else:
123 # will trigger an errback
124 raise Exception('DESTROY ALL LIFE')
125 thingummy = deferredGenerator(thingummy)
127 Put succinctly, these functions connect deferred-using code with this
128 'fake blocking' style in both directions: waitForDeferred converts from
129 a Deferred to the 'blocking' style, and deferredGenerator converts from
130 the 'blocking' style to a Deferred.
132 def __init__(self, d):
133 if not isinstance(d, Deferred):
134 raise TypeError("You must give waitForDeferred a Deferred. You gave it %r." % (d,))
135 self.d = d
137 def getResult(self):
138 if hasattr(self, 'failure'):
139 self.failure.raiseException()
140 return self.result
142 def _deferGenerator(g, deferred=None, result=None):
144 See L{waitForDeferred}.
146 while 1:
147 if deferred is None:
148 deferred = defer.Deferred()
149 try:
150 result = g.next()
151 except StopIteration:
152 deferred.callback(result)
153 return deferred
154 except:
155 deferred.errback()
156 return deferred
158 # Deferred.callback(Deferred) raises an error; we catch this case
159 # early here and give a nicer error message to the user in case
160 # they yield a Deferred. Perhaps eventually these semantics may
161 # change.
162 if isinstance(result, defer.Deferred):
163 return defer.fail(TypeError("Yield waitForDeferred(d), not d!"))
165 if isinstance(result, waitForDeferred):
166 waiting=[True, None]
167 # Pass vars in so they don't get changed going around the loop
168 def gotResult(r, waiting=waiting, result=result):
169 result.result = r
170 if waiting[0]:
171 waiting[0] = False
172 waiting[1] = r
173 else:
174 _deferGenerator(g, deferred, r)
175 def gotError(f, waiting=waiting, result=result):
176 result.failure = f
177 if waiting[0]:
178 waiting[0] = False
179 waiting[1] = f
180 else:
181 _deferGenerator(g, deferred, f)
182 result.d.addCallbacks(gotResult, gotError)
183 if waiting[0]:
184 # Haven't called back yet, set flag so that we get reinvoked
185 # and return from the loop
186 waiting[0] = False
187 return deferred
188 else:
189 result = waiting[1]
191 def func_metamerge(f, g):
193 Merge function metadata from f -> g and return g
195 try:
196 g.__doc__ = f.__doc__
197 g.__dict__.update(f.__dict__)
198 g.__name__ = f.__name__
199 except (TypeError, AttributeError):
200 pass
201 return g
203 def deferredGenerator(f):
205 See L{waitForDeferred}.
207 def unwindGenerator(*args, **kwargs):
208 return _deferGenerator(f(*args, **kwargs))
209 return func_metamerge(f, unwindGenerator)
211 defer.waitForDeferred = waitForDeferred
212 defer.deferredGenerator = deferredGenerator
214 from twisted.internet import utils
215 if not hasattr(utils, "getProcessOutputAndValue"):
216 from twisted.internet import reactor, protocol
217 _callProtocolWithDeferred = utils._callProtocolWithDeferred
218 try:
219 import cStringIO
220 StringIO = cStringIO
221 except ImportError:
222 import StringIO
224 class _EverythingGetter(protocol.ProcessProtocol):
226 def __init__(self, deferred):
227 self.deferred = deferred
228 self.outBuf = StringIO.StringIO()
229 self.errBuf = StringIO.StringIO()
230 self.outReceived = self.outBuf.write
231 self.errReceived = self.errBuf.write
233 def processEnded(self, reason):
234 out = self.outBuf.getvalue()
235 err = self.errBuf.getvalue()
236 e = reason.value
237 code = e.exitCode
238 if e.signal:
239 self.deferred.errback((out, err, e.signal))
240 else:
241 self.deferred.callback((out, err, code))
243 def getProcessOutputAndValue(executable, args=(), env={}, path='.',
244 reactor=reactor):
245 """Spawn a process and returns a Deferred that will be called back
246 with its output (from stdout and stderr) and it's exit code as (out,
247 err, code) If a signal is raised, the Deferred will errback with the
248 stdout and stderr up to that point, along with the signal, as (out,
249 err, signalNum)
251 return _callProtocolWithDeferred(_EverythingGetter,
252 executable, args, env, path,
253 reactor)
254 utils.getProcessOutputAndValue = getProcessOutputAndValue
257 # copied from Twisted circa 2.2.0
258 def _which(name, flags=os.X_OK):
259 """Search PATH for executable files with the given name.
261 @type name: C{str}
262 @param name: The name for which to search.
264 @type flags: C{int}
265 @param flags: Arguments to L{os.access}.
267 @rtype: C{list}
268 @return: A list of the full paths to files found, in the
269 order in which they were found.
271 result = []
272 exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep))
273 for p in os.environ['PATH'].split(os.pathsep):
274 p = os.path.join(p, name)
275 if os.access(p, flags):
276 result.append(p)
277 for e in exts:
278 pext = p + e
279 if os.access(pext, flags):
280 result.append(pext)
281 return result
283 which = _which
284 try:
285 from twisted.python.procutils import which
286 except ImportError:
287 pass