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

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

75 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, List, Union 

22from collections.abc import Callable 

23 

24from IPython.utils import py3compat 

25 

26#----------------------------------------------------------------------------- 

27# Function definitions 

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

29 

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

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

32 

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

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

35 command from completing.""" 

36 import errno 

37 

38 try: 

39 return stream.read() 

40 except IOError as err: 

41 if err.errno != errno.EINTR: 

42 raise 

43 

44 

45def process_handler( 

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

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

48 stderr=subprocess.PIPE, 

49) -> int | str | bytes: 

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

51 

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

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

54 

55 Parameters 

56 ---------- 

57 cmd : str or list 

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

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

60 passed, it will be used directly as arguments. 

61 callback : callable 

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

63 stderr : file descriptor number, optional 

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

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

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

67 and stderr combined in the order they are generated. 

68 

69 Returns 

70 ------- 

71 The return value of the provided callback is returned. 

72 """ 

73 sys.stdout.flush() 

74 sys.stderr.flush() 

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

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

77 close_fds = False 

78 else: 

79 close_fds = True 

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

81 shell = isinstance(cmd, str) 

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

83 executable = None 

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

85 executable = os.environ['SHELL'] 

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

87 executable=executable, 

88 stdin=subprocess.PIPE, 

89 stdout=subprocess.PIPE, 

90 stderr=stderr, 

91 close_fds=close_fds) 

92 

93 try: 

94 out = callback(p) 

95 except KeyboardInterrupt: 

96 print('^C') 

97 sys.stdout.flush() 

98 sys.stderr.flush() 

99 out = None 

100 finally: 

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

102 # call above raises an exception 

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

104 # later depending on the path taken) 

105 if p.returncode is None: 

106 try: 

107 p.terminate() 

108 p.poll() 

109 except OSError: 

110 pass 

111 # One last try on our way out 

112 if p.returncode is None: 

113 try: 

114 p.kill() 

115 except OSError: 

116 pass 

117 

118 return out 

119 

120 

121def getoutput(cmd): 

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

123 

124 Parameters 

125 ---------- 

126 cmd : str or list 

127 A command to be executed in the system shell. 

128 

129 Returns 

130 ------- 

131 output : str 

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

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

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

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

136 """ 

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

138 if out is None: 

139 return '' 

140 assert isinstance(out, bytes) 

141 return py3compat.decode(out) 

142 

143 

144def getoutputerror(cmd): 

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

146 

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

148 

149 Parameters 

150 ---------- 

151 cmd : str or list 

152 A command to be executed in the system shell. 

153 

154 Returns 

155 ------- 

156 stdout : str 

157 stderr : str 

158 """ 

159 return get_output_error_code(cmd)[:2] 

160 

161def get_output_error_code(cmd): 

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

163 in a shell. 

164 

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

166 

167 Parameters 

168 ---------- 

169 cmd : str or list 

170 A command to be executed in the system shell. 

171 

172 Returns 

173 ------- 

174 stdout : str 

175 stderr : str 

176 returncode: int 

177 """ 

178 

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

180 if out_err is None: 

181 return '', '', p.returncode 

182 out, err = out_err 

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

184 

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

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

187 

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

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

190 in inputs are respected. 

191 

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

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

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

195 command-line args. 

196 """ 

197 

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

199 lex.whitespace_split = True 

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

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

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

203 # and it shouldn't raise an exception. 

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

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

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

207 tokens = [] 

208 while True: 

209 try: 

210 tokens.append(next(lex)) 

211 except StopIteration: 

212 break 

213 except ValueError: 

214 if strict: 

215 raise 

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

217 tokens.append(lex.token) 

218 break 

219 

220 return tokens