go_subprocess.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. # ------------------------------------------------
  2. # help
  3. # ------------------------------------------------
  4. """
  5. GO SubProcess - tools to call a subprocess in combination with logging
  6. Routines
  7. p = call( *popenargs )
  8. Call subprocess without logging.
  9. Returns an object with fields:
  10. retcode (integer)
  11. stdout (list of strings)
  12. stderr (list of strings)
  13. Raises a CallingError or StatusError on failure.
  14. p = log_call( *popenargs, logger=None, loglevel=0 )
  15. Call subprocess. After process is terminated, send the standard output
  16. and standard error to the logger. If no logger instance is applied,
  17. a standard logger to created. The standard output is written with
  18. loglevel logging.INFO or the level specified as argument ;
  19. the standard error is written with loggin.DEBUG.
  20. Returns an object with fields:
  21. retcode (integer)
  22. stdout (list of strings)
  23. stderr (list of strings)
  24. Raises a CallingError or StatusError on failure.
  25. p = watch_call( *popenargs, logger=None, loglevel=0 )
  26. Call subprocess and send standard output to logger while running.
  27. Standard error is redirected to standard output to maintain the
  28. order in which lines are written as good as possible.
  29. See arguments of 'log_call' for the logging arguments.
  30. Returns an object with fields:
  31. retcode (integer)
  32. stdout (list of strings)
  33. stderr (list of strings; empty)
  34. Raises a CallingError or StatusError on failure.
  35. Exceptions
  36. CallingError
  37. Raised when a command could not be started at all.
  38. Attribute: command
  39. StatusError
  40. Raised when a non-zero exit status is received from a command.
  41. Attributes: command, returncode, stdout, stderr
  42. Example
  43. # external:
  44. import logging
  45. # run command, log outputs:
  46. p = log_call( ['/bin/ls','-l'] )
  47. # idem, but log outside call:
  48. try :
  49. p = call( ['/bin/ls','-l'] )
  50. except CallingError as err :
  51. logging.error( err )
  52. raise Exception
  53. except StatusError as err :
  54. for line in err.stderr : logging.error(line)
  55. logging.error( err )
  56. raise Exception
  57. except Exception as err :
  58. logging.error( 'unknown error : %s' % err )
  59. raise Exception
  60. #endtry
  61. # display output:
  62. for line in p.stdout : logging.info(line)
  63. for line in p.stderr : logging.error(line)
  64. """
  65. # ------------------------------------------------
  66. # exceptions
  67. # ------------------------------------------------
  68. class Error( Exception ):
  69. """
  70. Base class for exceptions in this module.
  71. """
  72. pass
  73. # *
  74. class CallingError( Error ) :
  75. """
  76. This exception is raised when an attempt to call a command fails.
  77. Attributes:
  78. command # str or str list
  79. """
  80. def __init__( self, command, strerror ) :
  81. """
  82. Store exception attributes.
  83. """
  84. # store:
  85. self.command = command
  86. self.strerror = strerror
  87. def __str__( self ) :
  88. """
  89. Format error message.
  90. """
  91. # error message:
  92. return 'Error from calling "%s" : %s' % (self.command,self.strerror)
  93. # *
  94. class StatusError( Error ) :
  95. """
  96. This exception is raised when a called command returns a non-zero exit status.
  97. Attributes:
  98. command # str or str list
  99. returncode # integer return status
  100. stdout # str list
  101. stderr # str list
  102. """
  103. def __init__( self, command, returncode, stdout, stderr ) :
  104. """
  105. Store exception attributes.
  106. """
  107. # store:
  108. self.command = command
  109. self.returncode = returncode
  110. self.stdout = stdout
  111. self.stderr = stderr
  112. def __str__( self ) :
  113. """
  114. Format error message.
  115. """
  116. # error message:
  117. return 'Call "%s" returned non-zero status %i' % (str(self.command),self.returncode)
  118. # ------------------------------------------------
  119. # routines
  120. # ------------------------------------------------
  121. class call( object ) :
  122. def __init__( self, command, **kwargs ) :
  123. """
  124. Call subprocess and return object with returncode, output, and error.
  125. """
  126. # external:
  127. import subprocess
  128. # call suprocess, catch calling errors:
  129. try:
  130. p = subprocess.Popen( command,
  131. stdout=subprocess.PIPE,
  132. stderr=subprocess.PIPE,
  133. universal_newlines=True,
  134. **kwargs )
  135. except Exception as err:
  136. raise CallingError( command, err )
  137. # wait for process to terminate, receive standard output and error:
  138. stdout_str,stderr_str = p.communicate()
  139. # store outputs as lists of lines
  140. self.stdout = stdout_str.strip('\n').split('\n')
  141. self.stderr = stderr_str.strip('\n').split('\n')
  142. # store return code:
  143. self.returncode = p.returncode
  144. # error ?
  145. if self.returncode != 0 : raise StatusError(command,self.returncode,self.stdout,self.stderr)
  146. # ok
  147. return
  148. # *
  149. class log_call( object ) :
  150. def __init__( self, command, logger=None, loglevel=None, **kwargs ) :
  151. """
  152. Call subprocess and send standard output and error to logger.
  153. """
  154. # external:
  155. import logging
  156. import sys
  157. import subprocess
  158. # tools:
  159. import go_logging
  160. # setup logger to standard output if not provided as argument:
  161. if logger == None : logger = go_logging.defaultLogger()
  162. # logging levels:
  163. errlevel = logging.ERROR
  164. outlevel = logging.INFO
  165. if loglevel != None : outlevel = loglevel
  166. # init outputs:
  167. self.stdout = []
  168. self.stderr = []
  169. # call suprocess, catch calling errors:
  170. try:
  171. p = subprocess.Popen( command,
  172. stdout=subprocess.PIPE,
  173. stderr=subprocess.PIPE,
  174. **kwargs )
  175. except Exception as err:
  176. logger.error( str(err) )
  177. raise CallingError( command, err )
  178. # wait for process to terminate, receive standard output and error:
  179. stdout_str,stderr_str = p.communicate()
  180. # store outputs as lists of lines
  181. self.stdout = stdout_str.strip('\n').split('\n')
  182. self.stderr = stderr_str.strip('\n').split('\n')
  183. # write standard output and error to logger:
  184. for line in self.stdout : logger.log( outlevel, line )
  185. for line in self.stderr : logger.log( errlevel, line )
  186. # store return code:
  187. self.returncode = p.returncode
  188. # error ?
  189. if self.returncode != 0 : raise StatusError(command,self.returncode,self.stdout,self.stderr)
  190. # ok
  191. return
  192. # *
  193. class watch_call( object ) :
  194. def __init__( self, command, logger=None, loglevel=0, **kwargs ) :
  195. """
  196. Call subprocess and send standard output to logger while running.
  197. Standard error is redirected to standard output to maintain the
  198. order in which lines are written as good as possible.
  199. """
  200. # external:
  201. import logging
  202. import sys
  203. import subprocess
  204. # tools:
  205. import go_logging
  206. # setup logger to standard output if not provided as argument:
  207. if logger == None : logger = go_logging.defaultLogger()
  208. # logging levels:
  209. errlevel = logging.ERROR
  210. outlevel = logging.INFO
  211. if loglevel != 0 : outlevel = loglevel
  212. # init outputs:
  213. self.stdout = []
  214. self.stderr = []
  215. # call suprocess, catch calling errors:
  216. try :
  217. p = subprocess.Popen( command,
  218. stdout=subprocess.PIPE,
  219. stderr=subprocess.STDOUT,
  220. universal_newlines=True,
  221. **kwargs )
  222. except Exception as err:
  223. logger.error( str(err) )
  224. raise CallingError( command, err )
  225. # wait for process to terminate:
  226. while p.poll() == None :
  227. line = p.stdout.readline() # read next line from standard output
  228. if line.strip():
  229. self.stderr.append(line.strip('\n'))
  230. logger.log( outlevel, line.strip('\n') )
  231. # read remaining lines:
  232. for line in p.stdout.readlines() :
  233. if line.strip():
  234. self.stderr.append(line.strip('\n')) # store
  235. logger.log( outlevel, line.strip('\n') ) # write
  236. # store return code:
  237. self.returncode = p.returncode
  238. # error ?
  239. if self.returncode != 0 : raise StatusError(command,self.returncode,self.stdout,self.stderr)
  240. # ok
  241. return
  242. # ------------------------------------------------
  243. # end
  244. # ------------------------------------------------