Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/utils/_process_common.py: 18%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

74 statements  

1"""Common utilities for the various process_* implementations. 

2 

3This file is only meant to be imported by the platform-specific implementations 

4of subprocess utilities, and it contains tools that are common to all of them. 

5""" 

6 

7#----------------------------------------------------------------------------- 

8# Copyright (C) 2010-2011 The IPython Development Team 

9# 

10# Distributed under the terms of the BSD License. The full license is in 

11# the file COPYING, distributed as part of this software. 

12#----------------------------------------------------------------------------- 

13 

14#----------------------------------------------------------------------------- 

15# Imports 

16#----------------------------------------------------------------------------- 

17import os 

18import shlex 

19import subprocess 

20import sys 

21from typing import IO, Any, Callable, List, Union 

22 

23from IPython.utils import py3compat 

24 

25#----------------------------------------------------------------------------- 

26# Function definitions 

27#----------------------------------------------------------------------------- 

28 

29def read_no_interrupt(stream: IO[Any]) -> bytes: 

30 """Read from a pipe ignoring EINTR errors. 

31 

32 This is necessary because when reading from pipes with GUI event loops 

33 running in the background, often interrupts are raised that stop the 

34 command from completing.""" 

35 import errno 

36 

37 try: 

38 return stream.read() 

39 except IOError as err: 

40 if err.errno != errno.EINTR: 

41 raise 

42 

43 

44def process_handler( 

45 cmd: Union[str, List[str]], 

46 callback: Callable[[subprocess.Popen], int | str | bytes], 

47 stderr=subprocess.PIPE, 

48) -> int | str | bytes: 

49 """Open a command in a shell subprocess and execute a callback. 

50 

51 This function provides common scaffolding for creating subprocess.Popen() 

52 calls. It creates a Popen object and then calls the callback with it. 

53 

54 Parameters 

55 ---------- 

56 cmd : str or list 

57 A command to be executed by the system, using :class:`subprocess.Popen`. 

58 If a string is passed, it will be run in the system shell. If a list is 

59 passed, it will be used directly as arguments. 

60 callback : callable 

61 A one-argument function that will be called with the Popen object. 

62 stderr : file descriptor number, optional 

63 By default this is set to ``subprocess.PIPE``, but you can also pass the 

64 value ``subprocess.STDOUT`` to force the subprocess' stderr to go into 

65 the same file descriptor as its stdout. This is useful to read stdout 

66 and stderr combined in the order they are generated. 

67 

68 Returns 

69 ------- 

70 The return value of the provided callback is returned. 

71 """ 

72 sys.stdout.flush() 

73 sys.stderr.flush() 

74 # On win32, close_fds can't be true when using pipes for stdin/out/err 

75 if sys.platform == "win32" and stderr != subprocess.PIPE: 

76 close_fds = False 

77 else: 

78 close_fds = True 

79 # Determine if cmd should be run with system shell. 

80 shell = isinstance(cmd, str) 

81 # On POSIX systems run shell commands with user-preferred shell. 

82 executable = None 

83 if shell and os.name == 'posix' and 'SHELL' in os.environ: 

84 executable = os.environ['SHELL'] 

85 p = subprocess.Popen(cmd, shell=shell, 

86 executable=executable, 

87 stdin=subprocess.PIPE, 

88 stdout=subprocess.PIPE, 

89 stderr=stderr, 

90 close_fds=close_fds) 

91 

92 try: 

93 out = callback(p) 

94 except KeyboardInterrupt: 

95 print('^C') 

96 sys.stdout.flush() 

97 sys.stderr.flush() 

98 out = None 

99 finally: 

100 # Make really sure that we don't leave processes behind, in case the 

101 # call above raises an exception 

102 # We start by assuming the subprocess finished (to avoid NameErrors 

103 # later depending on the path taken) 

104 if p.returncode is None: 

105 try: 

106 p.terminate() 

107 p.poll() 

108 except OSError: 

109 pass 

110 # One last try on our way out 

111 if p.returncode is None: 

112 try: 

113 p.kill() 

114 except OSError: 

115 pass 

116 

117 return out 

118 

119 

120def getoutput(cmd): 

121 """Run a command and return its stdout/stderr as a string. 

122 

123 Parameters 

124 ---------- 

125 cmd : str or list 

126 A command to be executed in the system shell. 

127 

128 Returns 

129 ------- 

130 output : str 

131 A string containing the combination of stdout and stderr from the 

132 subprocess, in whatever order the subprocess originally wrote to its 

133 file descriptors (so the order of the information in this string is the 

134 correct order as would be seen if running the command in a terminal). 

135 """ 

136 out = process_handler(cmd, lambda p: p.communicate()[0], subprocess.STDOUT) 

137 if out is None: 

138 return '' 

139 assert isinstance(out, bytes) 

140 return py3compat.decode(out) 

141 

142 

143def getoutputerror(cmd): 

144 """Return (standard output, standard error) of executing cmd in a shell. 

145 

146 Accepts the same arguments as os.system(). 

147 

148 Parameters 

149 ---------- 

150 cmd : str or list 

151 A command to be executed in the system shell. 

152 

153 Returns 

154 ------- 

155 stdout : str 

156 stderr : str 

157 """ 

158 return get_output_error_code(cmd)[:2] 

159 

160def get_output_error_code(cmd): 

161 """Return (standard output, standard error, return code) of executing cmd 

162 in a shell. 

163 

164 Accepts the same arguments as os.system(). 

165 

166 Parameters 

167 ---------- 

168 cmd : str or list 

169 A command to be executed in the system shell. 

170 

171 Returns 

172 ------- 

173 stdout : str 

174 stderr : str 

175 returncode: int 

176 """ 

177 

178 out_err, p = process_handler(cmd, lambda p: (p.communicate(), p)) 

179 if out_err is None: 

180 return '', '', p.returncode 

181 out, err = out_err 

182 return py3compat.decode(out), py3compat.decode(err), p.returncode 

183 

184def arg_split(s, posix=False, strict=True): 

185 """Split a command line's arguments in a shell-like manner. 

186 

187 This is a modified version of the standard library's shlex.split() 

188 function, but with a default of posix=False for splitting, so that quotes 

189 in inputs are respected. 

190 

191 if strict=False, then any errors shlex.split would raise will result in the 

192 unparsed remainder being the last element of the list, rather than raising. 

193 This is because we sometimes use arg_split to parse things other than 

194 command-line args. 

195 """ 

196 

197 lex = shlex.shlex(s, posix=posix) 

198 lex.whitespace_split = True 

199 # Extract tokens, ensuring that things like leaving open quotes 

200 # does not cause this to raise. This is important, because we 

201 # sometimes pass Python source through this (e.g. %timeit f(" ")), 

202 # and it shouldn't raise an exception. 

203 # It may be a bad idea to parse things that are not command-line args 

204 # through this function, but we do, so let's be safe about it. 

205 lex.commenters='' #fix for GH-1269 

206 tokens = [] 

207 while True: 

208 try: 

209 tokens.append(next(lex)) 

210 except StopIteration: 

211 break 

212 except ValueError: 

213 if strict: 

214 raise 

215 # couldn't parse, get remaining blob as last token 

216 tokens.append(lex.token) 

217 break 

218 

219 return tokens