go_subprocess.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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, err :
  51. logging.error( err )
  52. raise Exception
  53. except StatusError, err :
  54. for line in err.stderr : logging.error(line)
  55. logging.error( err )
  56. raise Exception
  57. except Exception, 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. **kwargs )
  134. except Exception, err :
  135. raise CallingError( command, err )
  136. # wait for process to terminate, receive standard output and error:
  137. stdout_str,stderr_str = p.communicate()
  138. # store outputs as lists of lines
  139. self.stdout = stdout_str.strip('\n').split('\n')
  140. self.stderr = stderr_str.strip('\n').split('\n')
  141. # store return code:
  142. self.returncode = p.returncode
  143. # error ?
  144. if self.returncode != 0 : raise StatusError(command,self.returncode,self.stdout,self.stderr)
  145. # ok
  146. return
  147. # *
  148. class log_call( object ) :
  149. def __init__( self, command, logger=None, loglevel=None, **kwargs ) :
  150. """
  151. Call subprocess and send standard output and error to logger.
  152. """
  153. # external:
  154. import logging
  155. import sys
  156. import subprocess
  157. # tools:
  158. import go_logging
  159. # setup logger to standard output if not provided as argument:
  160. if logger == None : logger = go_logging.defaultLogger()
  161. # logging levels:
  162. errlevel = logging.ERROR
  163. outlevel = logging.INFO
  164. if loglevel != None : outlevel = loglevel
  165. # init outputs:
  166. self.stdout = []
  167. self.stderr = []
  168. # call suprocess, catch calling errors:
  169. try:
  170. p = subprocess.Popen( command,
  171. stdout=subprocess.PIPE,
  172. stderr=subprocess.PIPE,
  173. **kwargs )
  174. except Exception, err :
  175. logger.error( str(err) )
  176. raise CallingError( command, err )
  177. # wait for process to terminate, receive standard output and error:
  178. stdout_str,stderr_str = p.communicate()
  179. # store outputs as lists of lines
  180. self.stdout = stdout_str.strip('\n').split('\n')
  181. self.stderr = stderr_str.strip('\n').split('\n')
  182. # write standard output and error to logger:
  183. for line in self.stdout : logger.log( outlevel, line )
  184. for line in self.stderr : logger.log( errlevel, line )
  185. # store return code:
  186. self.returncode = p.returncode
  187. # error ?
  188. if self.returncode != 0 : raise StatusError(command,self.returncode,self.stdout,self.stderr)
  189. # ok
  190. return
  191. # *
  192. class watch_call( object ) :
  193. def __init__( self, command, logger=None, loglevel=0, **kwargs ) :
  194. """
  195. Call subprocess and send standard output to logger while running.
  196. Standard error is redirected to standard output to maintain the
  197. order in which lines are written as good as possible.
  198. """
  199. # external:
  200. import logging
  201. import sys
  202. import subprocess
  203. # tools:
  204. import go_logging
  205. # setup logger to standard output if not provided as argument:
  206. if logger == None : logger = go_logging.defaultLogger()
  207. # logging levels:
  208. errlevel = logging.ERROR
  209. outlevel = logging.INFO
  210. if loglevel != 0 : outlevel = loglevel
  211. # init outputs:
  212. self.stdout = []
  213. self.stderr = []
  214. # call suprocess, catch calling errors:
  215. try :
  216. p = subprocess.Popen( command,
  217. stdout=subprocess.PIPE,
  218. stderr=subprocess.STDOUT,
  219. **kwargs )
  220. except Exception, err :
  221. logger.error( str(err) )
  222. raise CallingError( command, err )
  223. # wait for process to terminate:
  224. while p.poll() == None :
  225. line = p.stdout.readline() # read next line from standard output
  226. if line.strip():
  227. self.stderr.append(line.strip('\n'))
  228. logger.log( outlevel, line.strip('\n') )
  229. # read remaining lines:
  230. for line in p.stdout.readlines() :
  231. if line.strip():
  232. self.stderr.append(line.strip('\n')) # store
  233. logger.log( outlevel, line.strip('\n') ) # write
  234. # store return code:
  235. self.returncode = p.returncode
  236. # error ?
  237. if self.returncode != 0 : raise StatusError(command,self.returncode,self.stdout,self.stderr)
  238. # ok
  239. return
  240. # ------------------------------------------------
  241. # end
  242. # ------------------------------------------------