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

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

62 statements  

1"""Utilities for launching kernels""" 

2 

3# Copyright (c) Jupyter Development Team. 

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

5import os 

6import sys 

7import warnings 

8from subprocess import PIPE, Popen 

9from typing import Any 

10 

11from traitlets.log import get_logger 

12 

13 

14def launch_kernel( 

15 cmd: list[str], 

16 stdin: int | None = None, 

17 stdout: int | None = None, 

18 stderr: int | None = None, 

19 env: dict[str, str] | None = None, 

20 independent: bool = False, 

21 cwd: str | None = None, 

22 **kw: Any, 

23) -> Popen: 

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

25 

26 Parameters 

27 ---------- 

28 cmd : Popen list, 

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

30 

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

32 Standards streams, as defined in subprocess.Popen. 

33 

34 env: dict, optional 

35 Environment variables passed to the kernel 

36 

37 independent : bool, optional (default False) 

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

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

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

41 to kill kernels manually before exiting. 

42 

43 cwd : path, optional 

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

45 

46 **kw: optional 

47 Additional arguments for Popen 

48 

49 Returns 

50 ------- 

51 

52 Popen instance for the kernel subprocess 

53 """ 

54 

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

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

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

58 # pipes in certain important cases. 

59 

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

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

62 # place this one safe and always redirect. 

63 redirect_in = True 

64 _stdin = PIPE if stdin is None else stdin 

65 

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

67 # stderr are all invalid. 

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

69 _stdout: Any 

70 _stderr: Any 

71 if redirect_out: 

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

73 _stdout = blackhole if stdout is None else stdout 

74 _stderr = blackhole if stderr is None else stderr 

75 else: 

76 _stdout, _stderr = stdout, stderr 

77 

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

79 

80 kwargs = kw.copy() 

81 main_args = { 

82 "stdin": _stdin, 

83 "stdout": _stdout, 

84 "stderr": _stderr, 

85 "cwd": cwd, 

86 "env": env, 

87 } 

88 kwargs.update(main_args) 

89 

90 # Spawn a kernel. 

91 if sys.platform == "win32": 

92 if cwd: 

93 kwargs["cwd"] = cwd 

94 

95 from .win_interrupt import create_interrupt_event 

96 

97 # Create a Win32 event for interrupting the kernel 

98 # and store it in an environment variable. 

99 interrupt_event = create_interrupt_event() 

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

101 # deprecated old env name: 

102 env["IPY_INTERRUPT_EVENT"] = env["JPY_INTERRUPT_EVENT"] 

103 

104 try: 

105 from _winapi import ( 

106 CREATE_NEW_PROCESS_GROUP, 

107 DUPLICATE_SAME_ACCESS, 

108 DuplicateHandle, 

109 GetCurrentProcess, 

110 ) 

111 except: # noqa 

112 from _subprocess import ( 

113 CREATE_NEW_PROCESS_GROUP, 

114 DUPLICATE_SAME_ACCESS, 

115 DuplicateHandle, 

116 GetCurrentProcess, 

117 ) 

118 

119 # create a handle on the parent to be inherited 

120 if independent: 

121 kwargs["creationflags"] = CREATE_NEW_PROCESS_GROUP 

122 else: 

123 pid = GetCurrentProcess() 

124 handle = DuplicateHandle( 

125 pid, 

126 pid, 

127 pid, 

128 0, 

129 True, 

130 DUPLICATE_SAME_ACCESS, # Inheritable by new processes. 

131 ) 

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

133 

134 # Prevent creating new console window on pythonw 

135 if redirect_out: 

136 kwargs["creationflags"] = ( 

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

138 ) # CREATE_NO_WINDOW 

139 

140 # Avoid closing the above parent and interrupt handles. 

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

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

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

144 kwargs["close_fds"] = False 

145 else: 

146 # Create a new session. 

147 # This makes it easier to interrupt the kernel, 

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

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

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

151 kwargs["start_new_session"] = True 

152 if not independent: 

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

154 

155 try: 

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

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

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

159 except Exception as ex: 

160 try: 

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

162 # exclude environment variables, 

163 # which may contain access tokens and the like. 

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

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

166 get_logger().error(msg) 

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

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

169 warnings.warn( 

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

171 stacklevel=2, 

172 ) 

173 raise ex 

174 

175 if sys.platform == "win32": 

176 # Attach the interrupt event to the Popen object so it can be used later. 

177 proc.win32_interrupt_event = interrupt_event 

178 

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

180 if redirect_in and stdin is None: 

181 assert proc.stdin is not None 

182 proc.stdin.close() 

183 

184 return proc 

185 

186 

187__all__ = [ 

188 "launch_kernel", 

189]