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

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

76 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, List, TypeVar, Union 

22from collections.abc import Callable 

23 

24_T = TypeVar("_T") 

25 

26from IPython.utils import py3compat 

27 

28#----------------------------------------------------------------------------- 

29# Function definitions 

30#----------------------------------------------------------------------------- 

31 

32def read_no_interrupt(stream: IO[bytes]) -> bytes | None: 

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

34 

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

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

37 command from completing.""" 

38 import errno 

39 

40 try: 

41 return stream.read() 

42 except IOError as err: 

43 if err.errno != errno.EINTR: 

44 raise 

45 return None 

46 

47 

48def process_handler( 

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

50 callback: Callable[[subprocess.Popen[bytes]], _T], 

51 stderr: int = subprocess.PIPE, 

52) -> _T | None: 

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

54 

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

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

57 

58 Parameters 

59 ---------- 

60 cmd : str or list 

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

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

63 passed, it will be used directly as arguments. 

64 callback : callable 

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

66 stderr : file descriptor number, optional 

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

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

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

70 and stderr combined in the order they are generated. 

71 

72 Returns 

73 ------- 

74 The return value of the provided callback is returned. 

75 """ 

76 sys.stdout.flush() 

77 sys.stderr.flush() 

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

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

80 close_fds = False 

81 else: 

82 close_fds = True 

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

84 shell = isinstance(cmd, str) 

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

86 executable = None 

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

88 executable = os.environ['SHELL'] 

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

90 executable=executable, 

91 stdin=subprocess.PIPE, 

92 stdout=subprocess.PIPE, 

93 stderr=stderr, 

94 close_fds=close_fds) 

95 

96 try: 

97 out = callback(p) 

98 except KeyboardInterrupt: 

99 print('^C') 

100 sys.stdout.flush() 

101 sys.stderr.flush() 

102 out = None 

103 finally: 

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

105 # call above raises an exception 

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

107 # later depending on the path taken) 

108 if p.returncode is None: 

109 try: 

110 p.terminate() 

111 p.poll() 

112 except OSError: 

113 pass 

114 # One last try on our way out 

115 if p.returncode is None: 

116 try: 

117 p.kill() 

118 except OSError: 

119 pass 

120 

121 return out 

122 

123 

124def getoutput(cmd: str | list[str]) -> str: 

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

126 

127 Parameters 

128 ---------- 

129 cmd : str or list 

130 A command to be executed in the system shell. 

131 

132 Returns 

133 ------- 

134 output : str 

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

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

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

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

139 """ 

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

141 if out is None: 

142 return '' 

143 return py3compat.decode(out) 

144 

145 

146def getoutputerror(cmd: str | list[str]) -> tuple[str, str]: 

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

148 

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

150 

151 Parameters 

152 ---------- 

153 cmd : str or list 

154 A command to be executed in the system shell. 

155 

156 Returns 

157 ------- 

158 stdout : str 

159 stderr : str 

160 """ 

161 return get_output_error_code(cmd)[:2] 

162 

163 

164def get_output_error_code(cmd: str | list[str]) -> tuple[str, str, int | None]: 

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

166 in a shell. 

167 

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

169 

170 Parameters 

171 ---------- 

172 cmd : str or list 

173 A command to be executed in the system shell. 

174 

175 Returns 

176 ------- 

177 stdout : str 

178 stderr : str 

179 returncode: int 

180 """ 

181 

182 result = process_handler(cmd, lambda p: (p.communicate(), p)) 

183 if result is None: 

184 return '', '', None 

185 (out, err), p = result 

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

187 

188def arg_split(commandline: str, posix: bool = False, strict: bool = True) -> list[str]: 

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

190 

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

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

193 in inputs are respected. 

194 

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

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

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

198 command-line args. 

199 """ 

200 

201 lex = shlex.shlex(commandline, posix=posix) 

202 lex.whitespace_split = True 

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

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

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

206 # and it shouldn't raise an exception. 

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

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

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

210 tokens = [] 

211 while True: 

212 try: 

213 tokens.append(next(lex)) 

214 except StopIteration: 

215 break 

216 except ValueError: 

217 if strict: 

218 raise 

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

220 tokens.append(lex.token) 

221 break 

222 

223 return tokens