##// END OF EJS Templates
add comment about argparse abbreviations
MinRK -
Show More
@@ -1,266 +1,269 b''
1 """Utilities for launching kernels
1 """Utilities for launching kernels
2
2
3 Authors:
3 Authors:
4
4
5 * Min Ragan-Kelley
5 * Min Ragan-Kelley
6
6
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2013 The IPython Development Team
10 # Copyright (C) 2013 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import os
20 import os
21 import sys
21 import sys
22 from subprocess import Popen, PIPE
22 from subprocess import Popen, PIPE
23
23
24 from IPython.utils.py3compat import cast_bytes_py2
24 from IPython.utils.py3compat import cast_bytes_py2
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Launching Kernels
27 # Launching Kernels
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 def swallow_argv(argv, aliases=None, flags=None):
30 def swallow_argv(argv, aliases=None, flags=None):
31 """strip frontend-specific aliases and flags from an argument list
31 """strip frontend-specific aliases and flags from an argument list
32
32
33 For use primarily in frontend apps that want to pass a subset of command-line
33 For use primarily in frontend apps that want to pass a subset of command-line
34 arguments through to a subprocess, where frontend-specific flags and aliases
34 arguments through to a subprocess, where frontend-specific flags and aliases
35 should be removed from the list.
35 should be removed from the list.
36
36
37 Parameters
37 Parameters
38 ----------
38 ----------
39
39
40 argv : list(str)
40 argv : list(str)
41 The starting argv, to be filtered
41 The starting argv, to be filtered
42 aliases : container of aliases (dict, list, set, etc.)
42 aliases : container of aliases (dict, list, set, etc.)
43 The frontend-specific aliases to be removed
43 The frontend-specific aliases to be removed
44 flags : container of flags (dict, list, set, etc.)
44 flags : container of flags (dict, list, set, etc.)
45 The frontend-specific flags to be removed
45 The frontend-specific flags to be removed
46
46
47 Returns
47 Returns
48 -------
48 -------
49
49
50 argv : list(str)
50 argv : list(str)
51 The argv list, excluding flags and aliases that have been stripped
51 The argv list, excluding flags and aliases that have been stripped
52 """
52 """
53
53
54 if aliases is None:
54 if aliases is None:
55 aliases = set()
55 aliases = set()
56 if flags is None:
56 if flags is None:
57 flags = set()
57 flags = set()
58
58
59 stripped = list(argv) # copy
59 stripped = list(argv) # copy
60
60
61 swallow_next = False
61 swallow_next = False
62 was_flag = False
62 was_flag = False
63 for a in argv:
63 for a in argv:
64 if a == '--':
64 if a == '--':
65 break
65 break
66 if swallow_next:
66 if swallow_next:
67 swallow_next = False
67 swallow_next = False
68 # last arg was an alias, remove the next one
68 # last arg was an alias, remove the next one
69 # *unless* the last alias has a no-arg flag version, in which
69 # *unless* the last alias has a no-arg flag version, in which
70 # case, don't swallow the next arg if it's also a flag:
70 # case, don't swallow the next arg if it's also a flag:
71 if not (was_flag and a.startswith('-')):
71 if not (was_flag and a.startswith('-')):
72 stripped.remove(a)
72 stripped.remove(a)
73 continue
73 continue
74 if a.startswith('-'):
74 if a.startswith('-'):
75 split = a.lstrip('-').split('=')
75 split = a.lstrip('-').split('=')
76 name = split[0]
76 name = split[0]
77 # we use startswith because argparse accepts any arg to be specified
78 # by any leading section, as long as it is unique,
79 # so `--no-br` means `--no-browser` in the notebook, etc.
77 if any(alias.startswith(name) for alias in aliases):
80 if any(alias.startswith(name) for alias in aliases):
78 stripped.remove(a)
81 stripped.remove(a)
79 if len(split) == 1:
82 if len(split) == 1:
80 # alias passed with arg via space
83 # alias passed with arg via space
81 swallow_next = True
84 swallow_next = True
82 # could have been a flag that matches an alias, e.g. `existing`
85 # could have been a flag that matches an alias, e.g. `existing`
83 # in which case, we might not swallow the next arg
86 # in which case, we might not swallow the next arg
84 was_flag = name in flags
87 was_flag = name in flags
85 elif len(split) == 1 and any(flag.startswith(name) for flag in flags):
88 elif len(split) == 1 and any(flag.startswith(name) for flag in flags):
86 # strip flag, but don't swallow next, as flags don't take args
89 # strip flag, but don't swallow next, as flags don't take args
87 stripped.remove(a)
90 stripped.remove(a)
88
91
89 # return shortened list
92 # return shortened list
90 return stripped
93 return stripped
91
94
92
95
93 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
96 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
94 """Build Popen command list for launching an IPython kernel.
97 """Build Popen command list for launching an IPython kernel.
95
98
96 Parameters
99 Parameters
97 ----------
100 ----------
98 code : str,
101 code : str,
99 A string of Python code that imports and executes a kernel entry point.
102 A string of Python code that imports and executes a kernel entry point.
100
103
101 executable : str, optional (default sys.executable)
104 executable : str, optional (default sys.executable)
102 The Python executable to use for the kernel process.
105 The Python executable to use for the kernel process.
103
106
104 extra_arguments : list, optional
107 extra_arguments : list, optional
105 A list of extra arguments to pass when executing the launch code.
108 A list of extra arguments to pass when executing the launch code.
106
109
107 Returns
110 Returns
108 -------
111 -------
109
112
110 A Popen command list
113 A Popen command list
111 """
114 """
112
115
113 # Build the kernel launch command.
116 # Build the kernel launch command.
114 if executable is None:
117 if executable is None:
115 executable = sys.executable
118 executable = sys.executable
116 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
119 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
117 arguments.extend(extra_arguments)
120 arguments.extend(extra_arguments)
118
121
119 # Spawn a kernel.
122 # Spawn a kernel.
120 if sys.platform == 'win32':
123 if sys.platform == 'win32':
121
124
122 # If the kernel is running on pythonw and stdout/stderr are not been
125 # If the kernel is running on pythonw and stdout/stderr are not been
123 # re-directed, it will crash when more than 4KB of data is written to
126 # re-directed, it will crash when more than 4KB of data is written to
124 # stdout or stderr. This is a bug that has been with Python for a very
127 # stdout or stderr. This is a bug that has been with Python for a very
125 # long time; see http://bugs.python.org/issue706263.
128 # long time; see http://bugs.python.org/issue706263.
126 # A cleaner solution to this problem would be to pass os.devnull to
129 # A cleaner solution to this problem would be to pass os.devnull to
127 # Popen directly. Unfortunately, that does not work.
130 # Popen directly. Unfortunately, that does not work.
128 if executable.endswith('pythonw.exe'):
131 if executable.endswith('pythonw.exe'):
129 arguments.append('--no-stdout')
132 arguments.append('--no-stdout')
130 arguments.append('--no-stderr')
133 arguments.append('--no-stderr')
131
134
132 return arguments
135 return arguments
133
136
134
137
135 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
138 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
136 independent=False,
139 independent=False,
137 cwd=None, ipython_kernel=True,
140 cwd=None, ipython_kernel=True,
138 **kw
141 **kw
139 ):
142 ):
140 """ Launches a localhost kernel, binding to the specified ports.
143 """ Launches a localhost kernel, binding to the specified ports.
141
144
142 Parameters
145 Parameters
143 ----------
146 ----------
144 cmd : Popen list,
147 cmd : Popen list,
145 A string of Python code that imports and executes a kernel entry point.
148 A string of Python code that imports and executes a kernel entry point.
146
149
147 stdin, stdout, stderr : optional (default None)
150 stdin, stdout, stderr : optional (default None)
148 Standards streams, as defined in subprocess.Popen.
151 Standards streams, as defined in subprocess.Popen.
149
152
150 independent : bool, optional (default False)
153 independent : bool, optional (default False)
151 If set, the kernel process is guaranteed to survive if this process
154 If set, the kernel process is guaranteed to survive if this process
152 dies. If not set, an effort is made to ensure that the kernel is killed
155 dies. If not set, an effort is made to ensure that the kernel is killed
153 when this process dies. Note that in this case it is still good practice
156 when this process dies. Note that in this case it is still good practice
154 to kill kernels manually before exiting.
157 to kill kernels manually before exiting.
155
158
156 cwd : path, optional
159 cwd : path, optional
157 The working dir of the kernel process (default: cwd of this process).
160 The working dir of the kernel process (default: cwd of this process).
158
161
159 ipython_kernel : bool, optional
162 ipython_kernel : bool, optional
160 Whether the kernel is an official IPython one,
163 Whether the kernel is an official IPython one,
161 and should get a bit of special treatment.
164 and should get a bit of special treatment.
162
165
163 Returns
166 Returns
164 -------
167 -------
165
168
166 Popen instance for the kernel subprocess
169 Popen instance for the kernel subprocess
167 """
170 """
168
171
169 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
172 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
170 # are invalid. Unfortunately, there is in general no way to detect whether
173 # are invalid. Unfortunately, there is in general no way to detect whether
171 # they are valid. The following two blocks redirect them to (temporary)
174 # they are valid. The following two blocks redirect them to (temporary)
172 # pipes in certain important cases.
175 # pipes in certain important cases.
173
176
174 # If this process has been backgrounded, our stdin is invalid. Since there
177 # If this process has been backgrounded, our stdin is invalid. Since there
175 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
178 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
176 # place this one safe and always redirect.
179 # place this one safe and always redirect.
177 redirect_in = True
180 redirect_in = True
178 _stdin = PIPE if stdin is None else stdin
181 _stdin = PIPE if stdin is None else stdin
179
182
180 # If this process in running on pythonw, we know that stdin, stdout, and
183 # If this process in running on pythonw, we know that stdin, stdout, and
181 # stderr are all invalid.
184 # stderr are all invalid.
182 redirect_out = sys.executable.endswith('pythonw.exe')
185 redirect_out = sys.executable.endswith('pythonw.exe')
183 if redirect_out:
186 if redirect_out:
184 _stdout = PIPE if stdout is None else stdout
187 _stdout = PIPE if stdout is None else stdout
185 _stderr = PIPE if stderr is None else stderr
188 _stderr = PIPE if stderr is None else stderr
186 else:
189 else:
187 _stdout, _stderr = stdout, stderr
190 _stdout, _stderr = stdout, stderr
188
191
189 # Spawn a kernel.
192 # Spawn a kernel.
190 if sys.platform == 'win32':
193 if sys.platform == 'win32':
191
194
192 if cwd:
195 if cwd:
193 # Popen on Python 2 on Windows cannot handle unicode cwd.
196 # Popen on Python 2 on Windows cannot handle unicode cwd.
194 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
197 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
195
198
196 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
199 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
197 # Create a Win32 event for interrupting the kernel.
200 # Create a Win32 event for interrupting the kernel.
198 interrupt_event = ParentPollerWindows.create_interrupt_event()
201 interrupt_event = ParentPollerWindows.create_interrupt_event()
199 if ipython_kernel:
202 if ipython_kernel:
200 cmd += [ '--interrupt=%i' % interrupt_event ]
203 cmd += [ '--interrupt=%i' % interrupt_event ]
201
204
202 # If the kernel is running on pythonw and stdout/stderr are not been
205 # If the kernel is running on pythonw and stdout/stderr are not been
203 # re-directed, it will crash when more than 4KB of data is written to
206 # re-directed, it will crash when more than 4KB of data is written to
204 # stdout or stderr. This is a bug that has been with Python for a very
207 # stdout or stderr. This is a bug that has been with Python for a very
205 # long time; see http://bugs.python.org/issue706263.
208 # long time; see http://bugs.python.org/issue706263.
206 # A cleaner solution to this problem would be to pass os.devnull to
209 # A cleaner solution to this problem would be to pass os.devnull to
207 # Popen directly. Unfortunately, that does not work.
210 # Popen directly. Unfortunately, that does not work.
208 if cmd[0].endswith('pythonw.exe'):
211 if cmd[0].endswith('pythonw.exe'):
209 if stdout is None:
212 if stdout is None:
210 cmd.append('--no-stdout')
213 cmd.append('--no-stdout')
211 if stderr is None:
214 if stderr is None:
212 cmd.append('--no-stderr')
215 cmd.append('--no-stderr')
213
216
214 # Launch the kernel process.
217 # Launch the kernel process.
215 if independent:
218 if independent:
216 proc = Popen(cmd,
219 proc = Popen(cmd,
217 creationflags=512, # CREATE_NEW_PROCESS_GROUP
220 creationflags=512, # CREATE_NEW_PROCESS_GROUP
218 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=os.environ)
221 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=os.environ)
219 else:
222 else:
220 if ipython_kernel:
223 if ipython_kernel:
221 try:
224 try:
222 from _winapi import DuplicateHandle, GetCurrentProcess, \
225 from _winapi import DuplicateHandle, GetCurrentProcess, \
223 DUPLICATE_SAME_ACCESS
226 DUPLICATE_SAME_ACCESS
224 except:
227 except:
225 from _subprocess import DuplicateHandle, GetCurrentProcess, \
228 from _subprocess import DuplicateHandle, GetCurrentProcess, \
226 DUPLICATE_SAME_ACCESS
229 DUPLICATE_SAME_ACCESS
227 pid = GetCurrentProcess()
230 pid = GetCurrentProcess()
228 handle = DuplicateHandle(pid, pid, pid, 0,
231 handle = DuplicateHandle(pid, pid, pid, 0,
229 True, # Inheritable by new processes.
232 True, # Inheritable by new processes.
230 DUPLICATE_SAME_ACCESS)
233 DUPLICATE_SAME_ACCESS)
231 cmd +=[ '--parent=%i' % handle ]
234 cmd +=[ '--parent=%i' % handle ]
232
235
233
236
234 proc = Popen(cmd,
237 proc = Popen(cmd,
235 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
238 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
236
239
237 # Attach the interrupt event to the Popen objet so it can be used later.
240 # Attach the interrupt event to the Popen objet so it can be used later.
238 proc.win32_interrupt_event = interrupt_event
241 proc.win32_interrupt_event = interrupt_event
239
242
240 else:
243 else:
241 if independent:
244 if independent:
242 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
245 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
243 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
246 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
244 else:
247 else:
245 if ipython_kernel:
248 if ipython_kernel:
246 cmd += ['--parent=1']
249 cmd += ['--parent=1']
247 proc = Popen(cmd,
250 proc = Popen(cmd,
248 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
251 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
249
252
250 # Clean up pipes created to work around Popen bug.
253 # Clean up pipes created to work around Popen bug.
251 if redirect_in:
254 if redirect_in:
252 if stdin is None:
255 if stdin is None:
253 proc.stdin.close()
256 proc.stdin.close()
254 if redirect_out:
257 if redirect_out:
255 if stdout is None:
258 if stdout is None:
256 proc.stdout.close()
259 proc.stdout.close()
257 if stderr is None:
260 if stderr is None:
258 proc.stderr.close()
261 proc.stderr.close()
259
262
260 return proc
263 return proc
261
264
262 __all__ = [
265 __all__ = [
263 'swallow_argv',
266 'swallow_argv',
264 'make_ipkernel_cmd',
267 'make_ipkernel_cmd',
265 'launch_kernel',
268 'launch_kernel',
266 ]
269 ]
General Comments 0
You need to be logged in to leave comments. Login now