utils.py 8.2 KB

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