Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/jupyter_client/launcher.py: 13%

61 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-01 06:54 +0000

1"""Utilities for launching kernels""" 

2# Copyright (c) Jupyter Development Team. 

3# Distributed under the terms of the Modified BSD License. 

4import os 

5import sys 

6import warnings 

7from subprocess import PIPE, Popen 

8from typing import Any, Dict, List, Optional 

9 

10from traitlets.log import get_logger 

11 

12 

13def launch_kernel( 

14 cmd: List[str], 

15 stdin: Optional[int] = None, 

16 stdout: Optional[int] = None, 

17 stderr: Optional[int] = None, 

18 env: Optional[Dict[str, str]] = None, 

19 independent: bool = False, 

20 cwd: Optional[str] = None, 

21 **kw: Any, 

22) -> Popen: 

23 """Launches a localhost kernel, binding to the specified ports. 

24 

25 Parameters 

26 ---------- 

27 cmd : Popen list, 

28 A string of Python code that imports and executes a kernel entry point. 

29 

30 stdin, stdout, stderr : optional (default None) 

31 Standards streams, as defined in subprocess.Popen. 

32 

33 env: dict, optional 

34 Environment variables passed to the kernel 

35 

36 independent : bool, optional (default False) 

37 If set, the kernel process is guaranteed to survive if this process 

38 dies. If not set, an effort is made to ensure that the kernel is killed 

39 when this process dies. Note that in this case it is still good practice 

40 to kill kernels manually before exiting. 

41 

42 cwd : path, optional 

43 The working dir of the kernel process (default: cwd of this process). 

44 

45 **kw: optional 

46 Additional arguments for Popen 

47 

48 Returns 

49 ------- 

50 

51 Popen instance for the kernel subprocess 

52 """ 

53 

54 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr 

55 # are invalid. Unfortunately, there is in general no way to detect whether 

56 # they are valid. The following two blocks redirect them to (temporary) 

57 # pipes in certain important cases. 

58 

59 # If this process has been backgrounded, our stdin is invalid. Since there 

60 # is no compelling reason for the kernel to inherit our stdin anyway, we'll 

61 # place this one safe and always redirect. 

62 redirect_in = True 

63 _stdin = PIPE if stdin is None else stdin 

64 

65 # If this process in running on pythonw, we know that stdin, stdout, and 

66 # stderr are all invalid. 

67 redirect_out = sys.executable.endswith("pythonw.exe") 

68 if redirect_out: 

69 blackhole = open(os.devnull, "w") # noqa 

70 _stdout = blackhole if stdout is None else stdout 

71 _stderr = blackhole if stderr is None else stderr 

72 else: 

73 _stdout, _stderr = stdout, stderr 

74 

75 env = env if (env is not None) else os.environ.copy() 

76 

77 kwargs = kw.copy() 

78 main_args = { 

79 "stdin": _stdin, 

80 "stdout": _stdout, 

81 "stderr": _stderr, 

82 "cwd": cwd, 

83 "env": env, 

84 } 

85 kwargs.update(main_args) 

86 

87 # Spawn a kernel. 

88 if sys.platform == "win32": 

89 if cwd: 

90 kwargs["cwd"] = cwd 

91 

92 from .win_interrupt import create_interrupt_event 

93 

94 # Create a Win32 event for interrupting the kernel 

95 # and store it in an environment variable. 

96 interrupt_event = create_interrupt_event() 

97 env["JPY_INTERRUPT_EVENT"] = str(interrupt_event) 

98 # deprecated old env name: 

99 env["IPY_INTERRUPT_EVENT"] = env["JPY_INTERRUPT_EVENT"] 

100 

101 try: 

102 from _winapi import ( 

103 CREATE_NEW_PROCESS_GROUP, 

104 DUPLICATE_SAME_ACCESS, 

105 DuplicateHandle, 

106 GetCurrentProcess, 

107 ) 

108 except: # noqa 

109 from _subprocess import ( 

110 CREATE_NEW_PROCESS_GROUP, 

111 DUPLICATE_SAME_ACCESS, 

112 DuplicateHandle, 

113 GetCurrentProcess, 

114 ) 

115 

116 # create a handle on the parent to be inherited 

117 if independent: 

118 kwargs["creationflags"] = CREATE_NEW_PROCESS_GROUP 

119 else: 

120 pid = GetCurrentProcess() 

121 handle = DuplicateHandle( 

122 pid, 

123 pid, 

124 pid, 

125 0, 

126 True, 

127 DUPLICATE_SAME_ACCESS, # Inheritable by new processes. 

128 ) 

129 env["JPY_PARENT_PID"] = str(int(handle)) 

130 

131 # Prevent creating new console window on pythonw 

132 if redirect_out: 

133 kwargs["creationflags"] = ( 

134 kwargs.setdefault("creationflags", 0) | 0x08000000 

135 ) # CREATE_NO_WINDOW 

136 

137 # Avoid closing the above parent and interrupt handles. 

138 # close_fds is True by default on Python >=3.7 

139 # or when no stream is captured on Python <3.7 

140 # (we always capture stdin, so this is already False by default on <3.7) 

141 kwargs["close_fds"] = False 

142 else: 

143 # Create a new session. 

144 # This makes it easier to interrupt the kernel, 

145 # because we want to interrupt the whole process group. 

146 # We don't use setpgrp, which is known to cause problems for kernels starting 

147 # certain interactive subprocesses, such as bash -i. 

148 kwargs["start_new_session"] = True 

149 if not independent: 

150 env["JPY_PARENT_PID"] = str(os.getpid()) 

151 

152 try: 

153 # Allow to use ~/ in the command or its arguments 

154 cmd = [os.path.expanduser(s) for s in cmd] 

155 proc = Popen(cmd, **kwargs) # noqa 

156 except Exception as ex: 

157 try: 

158 msg = "Failed to run command:\n{}\n PATH={!r}\n with kwargs:\n{!r}\n" 

159 # exclude environment variables, 

160 # which may contain access tokens and the like. 

161 without_env = {key: value for key, value in kwargs.items() if key != "env"} 

162 msg = msg.format(cmd, env.get("PATH", os.defpath), without_env) 

163 get_logger().error(msg) 

164 except Exception as ex2: # Don't let a formatting/logger issue lead to the wrong exception 

165 warnings.warn(f"Failed to run command: '{cmd}' due to exception: {ex}", stacklevel=2) 

166 warnings.warn( 

167 f"The following exception occurred handling the previous failure: {ex2}", 

168 stacklevel=2, 

169 ) 

170 raise ex 

171 

172 if sys.platform == "win32": 

173 # Attach the interrupt event to the Popen objet so it can be used later. 

174 proc.win32_interrupt_event = interrupt_event 

175 

176 # Clean up pipes created to work around Popen bug. 

177 if redirect_in and stdin is None: 

178 assert proc.stdin is not None 

179 proc.stdin.close() 

180 

181 return proc 

182 

183 

184__all__ = [ 

185 "launch_kernel", 

186]