| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 |
""" |
|---|
| 6 |
Utility methods. |
|---|
| 7 |
""" |
|---|
| 8 |
|
|---|
| 9 |
import sys, warnings |
|---|
| 10 |
|
|---|
| 11 |
from twisted.internet import protocol, defer |
|---|
| 12 |
from twisted.python import failure, util as tputil |
|---|
| 13 |
|
|---|
| 14 |
try: |
|---|
| 15 |
import cStringIO as StringIO |
|---|
| 16 |
except ImportError: |
|---|
| 17 |
import StringIO |
|---|
| 18 |
|
|---|
| 19 |
def _callProtocolWithDeferred(protocol, executable, args, env, path, reactor=None): |
|---|
| 20 |
if reactor is None: |
|---|
| 21 |
from twisted.internet import reactor |
|---|
| 22 |
|
|---|
| 23 |
d = defer.Deferred() |
|---|
| 24 |
p = protocol(d) |
|---|
| 25 |
reactor.spawnProcess(p, executable, (executable,)+tuple(args), env, path) |
|---|
| 26 |
return d |
|---|
| 27 |
|
|---|
| 28 |
|
|---|
| 29 |
|
|---|
| 30 |
class _UnexpectedErrorOutput(IOError): |
|---|
| 31 |
""" |
|---|
| 32 |
Standard error data was received where it was not expected. This is a |
|---|
| 33 |
subclass of L{IOError} to preserve backward compatibility with the previous |
|---|
| 34 |
error behavior of L{getProcessOutput}. |
|---|
| 35 |
|
|---|
| 36 |
@ivar processEnded: A L{Deferred} which will fire when the process which |
|---|
| 37 |
produced the data on stderr has ended (exited and all file descriptors |
|---|
| 38 |
closed). |
|---|
| 39 |
""" |
|---|
| 40 |
def __init__(self, text, processEnded): |
|---|
| 41 |
IOError.__init__(self, "got stderr: %r" % (text,)) |
|---|
| 42 |
self.processEnded = processEnded |
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
|
|---|
| 46 |
class _BackRelay(protocol.ProcessProtocol): |
|---|
| 47 |
""" |
|---|
| 48 |
Trivial protocol for communicating with a process and turning its output |
|---|
| 49 |
into the result of a L{Deferred}. |
|---|
| 50 |
|
|---|
| 51 |
@ivar deferred: A L{Deferred} which will be called back with all of stdout |
|---|
| 52 |
and, if C{errortoo} is true, all of stderr as well (mixed together in |
|---|
| 53 |
one string). If C{errortoo} is false and any bytes are received over |
|---|
| 54 |
stderr, this will fire with an L{_UnexpectedErrorOutput} instance and |
|---|
| 55 |
the attribute will be set to C{None}. |
|---|
| 56 |
|
|---|
| 57 |
@ivar onProcessEnded: If C{errortoo} is false and bytes are received over |
|---|
| 58 |
stderr, this attribute will refer to a L{Deferred} which will be called |
|---|
| 59 |
back when the process ends. This C{Deferred} is also associated with |
|---|
| 60 |
the L{_UnexpectedErrorOutput} which C{deferred} fires with earlier in |
|---|
| 61 |
this case so that users can determine when the process has actually |
|---|
| 62 |
ended, in addition to knowing when bytes have been received via stderr. |
|---|
| 63 |
""" |
|---|
| 64 |
|
|---|
| 65 |
def __init__(self, deferred, errortoo=0): |
|---|
| 66 |
self.deferred = deferred |
|---|
| 67 |
self.s = StringIO.StringIO() |
|---|
| 68 |
if errortoo: |
|---|
| 69 |
self.errReceived = self.errReceivedIsGood |
|---|
| 70 |
else: |
|---|
| 71 |
self.errReceived = self.errReceivedIsBad |
|---|
| 72 |
|
|---|
| 73 |
def errReceivedIsBad(self, text): |
|---|
| 74 |
if self.deferred is not None: |
|---|
| 75 |
self.onProcessEnded = defer.Deferred() |
|---|
| 76 |
err = _UnexpectedErrorOutput(text, self.onProcessEnded) |
|---|
| 77 |
self.deferred.errback(failure.Failure(err)) |
|---|
| 78 |
self.deferred = None |
|---|
| 79 |
self.transport.loseConnection() |
|---|
| 80 |
|
|---|
| 81 |
def errReceivedIsGood(self, text): |
|---|
| 82 |
self.s.write(text) |
|---|
| 83 |
|
|---|
| 84 |
def outReceived(self, text): |
|---|
| 85 |
self.s.write(text) |
|---|
| 86 |
|
|---|
| 87 |
def processEnded(self, reason): |
|---|
| 88 |
if self.deferred is not None: |
|---|
| 89 |
self.deferred.callback(self.s.getvalue()) |
|---|
| 90 |
elif self.onProcessEnded is not None: |
|---|
| 91 |
self.onProcessEnded.errback(reason) |
|---|
| 92 |
|
|---|
| 93 |
|
|---|
| 94 |
|
|---|
| 95 |
def getProcessOutput(executable, args=(), env={}, path=None, reactor=None, |
|---|
| 96 |
errortoo=0): |
|---|
| 97 |
""" |
|---|
| 98 |
Spawn a process and return its output as a deferred returning a string. |
|---|
| 99 |
|
|---|
| 100 |
@param executable: The file name to run and get the output of - the |
|---|
| 101 |
full path should be used. |
|---|
| 102 |
|
|---|
| 103 |
@param args: the command line arguments to pass to the process; a |
|---|
| 104 |
sequence of strings. The first string should *NOT* be the |
|---|
| 105 |
executable's name. |
|---|
| 106 |
|
|---|
| 107 |
@param env: the environment variables to pass to the processs; a |
|---|
| 108 |
dictionary of strings. |
|---|
| 109 |
|
|---|
| 110 |
@param path: the path to run the subprocess in - defaults to the |
|---|
| 111 |
current directory. |
|---|
| 112 |
|
|---|
| 113 |
@param reactor: the reactor to use - defaults to the default reactor |
|---|
| 114 |
|
|---|
| 115 |
@param errortoo: If true, include stderr in the result. If false, if |
|---|
| 116 |
stderr is received the returned L{Deferred} will errback with an |
|---|
| 117 |
L{IOError} instance with a C{processEnded} attribute. The |
|---|
| 118 |
C{processEnded} attribute refers to a L{Deferred} which fires when the |
|---|
| 119 |
executed process ends. |
|---|
| 120 |
""" |
|---|
| 121 |
return _callProtocolWithDeferred(lambda d: |
|---|
| 122 |
_BackRelay(d, errortoo=errortoo), |
|---|
| 123 |
executable, args, env, path, |
|---|
| 124 |
reactor) |
|---|
| 125 |
|
|---|
| 126 |
|
|---|
| 127 |
class _ValueGetter(protocol.ProcessProtocol): |
|---|
| 128 |
|
|---|
| 129 |
def __init__(self, deferred): |
|---|
| 130 |
self.deferred = deferred |
|---|
| 131 |
|
|---|
| 132 |
def processEnded(self, reason): |
|---|
| 133 |
self.deferred.callback(reason.value.exitCode) |
|---|
| 134 |
|
|---|
| 135 |
|
|---|
| 136 |
def getProcessValue(executable, args=(), env={}, path=None, reactor=None): |
|---|
| 137 |
"""Spawn a process and return its exit code as a Deferred.""" |
|---|
| 138 |
return _callProtocolWithDeferred(_ValueGetter, executable, args, env, path, |
|---|
| 139 |
reactor) |
|---|
| 140 |
|
|---|
| 141 |
|
|---|
| 142 |
class _EverythingGetter(protocol.ProcessProtocol): |
|---|
| 143 |
|
|---|
| 144 |
def __init__(self, deferred): |
|---|
| 145 |
self.deferred = deferred |
|---|
| 146 |
self.outBuf = StringIO.StringIO() |
|---|
| 147 |
self.errBuf = StringIO.StringIO() |
|---|
| 148 |
self.outReceived = self.outBuf.write |
|---|
| 149 |
self.errReceived = self.errBuf.write |
|---|
| 150 |
|
|---|
| 151 |
def processEnded(self, reason): |
|---|
| 152 |
out = self.outBuf.getvalue() |
|---|
| 153 |
err = self.errBuf.getvalue() |
|---|
| 154 |
e = reason.value |
|---|
| 155 |
code = e.exitCode |
|---|
| 156 |
if e.signal: |
|---|
| 157 |
self.deferred.errback((out, err, e.signal)) |
|---|
| 158 |
else: |
|---|
| 159 |
self.deferred.callback((out, err, code)) |
|---|
| 160 |
|
|---|
| 161 |
def getProcessOutputAndValue(executable, args=(), env={}, path=None, |
|---|
| 162 |
reactor=None): |
|---|
| 163 |
"""Spawn a process and returns a Deferred that will be called back with |
|---|
| 164 |
its output (from stdout and stderr) and it's exit code as (out, err, code) |
|---|
| 165 |
If a signal is raised, the Deferred will errback with the stdout and |
|---|
| 166 |
stderr up to that point, along with the signal, as (out, err, signalNum) |
|---|
| 167 |
""" |
|---|
| 168 |
return _callProtocolWithDeferred(_EverythingGetter, executable, args, env, path, |
|---|
| 169 |
reactor) |
|---|
| 170 |
|
|---|
| 171 |
def _resetWarningFilters(passthrough, addedFilters): |
|---|
| 172 |
for f in addedFilters: |
|---|
| 173 |
try: |
|---|
| 174 |
warnings.filters.remove(f) |
|---|
| 175 |
except ValueError: |
|---|
| 176 |
pass |
|---|
| 177 |
return passthrough |
|---|
| 178 |
|
|---|
| 179 |
|
|---|
| 180 |
def runWithWarningsSuppressed(suppressedWarnings, f, *a, **kw): |
|---|
| 181 |
"""Run the function C{f}, but with some warnings suppressed. |
|---|
| 182 |
|
|---|
| 183 |
@param suppressedWarnings: A list of arguments to pass to filterwarnings. |
|---|
| 184 |
Must be a sequence of 2-tuples (args, kwargs). |
|---|
| 185 |
@param f: A callable, followed by its arguments and keyword arguments |
|---|
| 186 |
""" |
|---|
| 187 |
for args, kwargs in suppressedWarnings: |
|---|
| 188 |
warnings.filterwarnings(*args, **kwargs) |
|---|
| 189 |
addedFilters = warnings.filters[:len(suppressedWarnings)] |
|---|
| 190 |
try: |
|---|
| 191 |
result = f(*a, **kw) |
|---|
| 192 |
except: |
|---|
| 193 |
exc_info = sys.exc_info() |
|---|
| 194 |
_resetWarningFilters(None, addedFilters) |
|---|
| 195 |
raise exc_info[0], exc_info[1], exc_info[2] |
|---|
| 196 |
else: |
|---|
| 197 |
if isinstance(result, defer.Deferred): |
|---|
| 198 |
result.addBoth(_resetWarningFilters, addedFilters) |
|---|
| 199 |
else: |
|---|
| 200 |
_resetWarningFilters(None, addedFilters) |
|---|
| 201 |
return result |
|---|
| 202 |
|
|---|
| 203 |
|
|---|
| 204 |
def suppressWarnings(f, *suppressedWarnings): |
|---|
| 205 |
""" |
|---|
| 206 |
Wrap C{f} in a callable which suppresses the indicated warnings before |
|---|
| 207 |
invoking C{f} and unsuppresses them afterwards. If f returns a Deferred, |
|---|
| 208 |
warnings will remain suppressed until the Deferred fires. |
|---|
| 209 |
""" |
|---|
| 210 |
def warningSuppressingWrapper(*a, **kw): |
|---|
| 211 |
return runWithWarningsSuppressed(suppressedWarnings, f, *a, **kw) |
|---|
| 212 |
return tputil.mergeFunctionMetadata(f, warningSuppressingWrapper) |
|---|
| 213 |
|
|---|
| 214 |
|
|---|
| 215 |
__all__ = [ |
|---|
| 216 |
"runWithWarningsSuppressed", "suppressWarnings", |
|---|
| 217 |
|
|---|
| 218 |
"getProcessOutput", "getProcessValue", "getProcessOutputAndValue", |
|---|
| 219 |
] |
|---|