utils.py 7.7 KB

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