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
« 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
10from traitlets.log import get_logger
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.
25 Parameters
26 ----------
27 cmd : Popen list,
28 A string of Python code that imports and executes a kernel entry point.
30 stdin, stdout, stderr : optional (default None)
31 Standards streams, as defined in subprocess.Popen.
33 env: dict, optional
34 Environment variables passed to the kernel
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.
42 cwd : path, optional
43 The working dir of the kernel process (default: cwd of this process).
45 **kw: optional
46 Additional arguments for Popen
48 Returns
49 -------
51 Popen instance for the kernel subprocess
52 """
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.
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
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
75 env = env if (env is not None) else os.environ.copy()
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)
87 # Spawn a kernel.
88 if sys.platform == "win32":
89 if cwd:
90 kwargs["cwd"] = cwd
92 from .win_interrupt import create_interrupt_event
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"]
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 )
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))
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
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())
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
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
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()
181 return proc
184__all__ = [
185 "launch_kernel",
186]