##// END OF EJS Templates
Switch scripts cell magic default to raise if error happens....
Matthias Bussonnier -
Show More
@@ -0,0 +1,4 b''
1 The ``%%script`` (as well as ``%%bash``, ``ruby``... ) cell magic no raise by
2 default if the return code of the given code is non-zero (this halting execution
3 of further cells in a notebook). The behavior can be disable by passing the
4 ``--no-raise-error`` flag.
@@ -1,294 +1,294 b''
1 """Magic functions for running cells in various scripts."""
1 """Magic functions for running cells in various scripts."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import errno
6 import errno
7 import os
7 import os
8 import sys
8 import sys
9 import signal
9 import signal
10 import time
10 import time
11 from subprocess import Popen, PIPE, CalledProcessError
11 from subprocess import Popen, PIPE, CalledProcessError
12 import atexit
12 import atexit
13
13
14 from IPython.core import magic_arguments
14 from IPython.core import magic_arguments
15 from IPython.core.magic import (
15 from IPython.core.magic import (
16 Magics, magics_class, line_magic, cell_magic
16 Magics, magics_class, line_magic, cell_magic
17 )
17 )
18 from IPython.lib.backgroundjobs import BackgroundJobManager
18 from IPython.lib.backgroundjobs import BackgroundJobManager
19 from IPython.utils import py3compat
19 from IPython.utils import py3compat
20 from IPython.utils.process import arg_split
20 from IPython.utils.process import arg_split
21 from traitlets import List, Dict, default
21 from traitlets import List, Dict, default
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Magic implementation classes
24 # Magic implementation classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 def script_args(f):
27 def script_args(f):
28 """single decorator for adding script args"""
28 """single decorator for adding script args"""
29 args = [
29 args = [
30 magic_arguments.argument(
30 magic_arguments.argument(
31 '--out', type=str,
31 '--out', type=str,
32 help="""The variable in which to store stdout from the script.
32 help="""The variable in which to store stdout from the script.
33 If the script is backgrounded, this will be the stdout *pipe*,
33 If the script is backgrounded, this will be the stdout *pipe*,
34 instead of the stderr text itself and will not be auto closed.
34 instead of the stderr text itself and will not be auto closed.
35 """
35 """
36 ),
36 ),
37 magic_arguments.argument(
37 magic_arguments.argument(
38 '--err', type=str,
38 '--err', type=str,
39 help="""The variable in which to store stderr from the script.
39 help="""The variable in which to store stderr from the script.
40 If the script is backgrounded, this will be the stderr *pipe*,
40 If the script is backgrounded, this will be the stderr *pipe*,
41 instead of the stderr text itself and will not be autoclosed.
41 instead of the stderr text itself and will not be autoclosed.
42 """
42 """
43 ),
43 ),
44 magic_arguments.argument(
44 magic_arguments.argument(
45 '--bg', action="store_true",
45 '--bg', action="store_true",
46 help="""Whether to run the script in the background.
46 help="""Whether to run the script in the background.
47 If given, the only way to see the output of the command is
47 If given, the only way to see the output of the command is
48 with --out/err.
48 with --out/err.
49 """
49 """
50 ),
50 ),
51 magic_arguments.argument(
51 magic_arguments.argument(
52 '--proc', type=str,
52 '--proc', type=str,
53 help="""The variable in which to store Popen instance.
53 help="""The variable in which to store Popen instance.
54 This is used only when --bg option is given.
54 This is used only when --bg option is given.
55 """
55 """
56 ),
56 ),
57 magic_arguments.argument(
57 magic_arguments.argument(
58 '--raise-error', action="store_true",
58 '--no-raise-error', action="store_false", dest='raise_error',
59 help="""Whether you should raise an error message in addition to
59 help="""Whether you should raise an error message in addition to
60 a stream on stderr if you get a nonzero exit code.
60 a stream on stderr if you get a nonzero exit code.
61 """
61 """
62 )
62 )
63 ]
63 ]
64 for arg in args:
64 for arg in args:
65 f = arg(f)
65 f = arg(f)
66 return f
66 return f
67
67
68 @magics_class
68 @magics_class
69 class ScriptMagics(Magics):
69 class ScriptMagics(Magics):
70 """Magics for talking to scripts
70 """Magics for talking to scripts
71
71
72 This defines a base `%%script` cell magic for running a cell
72 This defines a base `%%script` cell magic for running a cell
73 with a program in a subprocess, and registers a few top-level
73 with a program in a subprocess, and registers a few top-level
74 magics that call %%script with common interpreters.
74 magics that call %%script with common interpreters.
75 """
75 """
76 script_magics = List(
76 script_magics = List(
77 help="""Extra script cell magics to define
77 help="""Extra script cell magics to define
78
78
79 This generates simple wrappers of `%%script foo` as `%%foo`.
79 This generates simple wrappers of `%%script foo` as `%%foo`.
80
80
81 If you want to add script magics that aren't on your path,
81 If you want to add script magics that aren't on your path,
82 specify them in script_paths
82 specify them in script_paths
83 """,
83 """,
84 ).tag(config=True)
84 ).tag(config=True)
85 @default('script_magics')
85 @default('script_magics')
86 def _script_magics_default(self):
86 def _script_magics_default(self):
87 """default to a common list of programs"""
87 """default to a common list of programs"""
88
88
89 defaults = [
89 defaults = [
90 'sh',
90 'sh',
91 'bash',
91 'bash',
92 'perl',
92 'perl',
93 'ruby',
93 'ruby',
94 'python',
94 'python',
95 'python2',
95 'python2',
96 'python3',
96 'python3',
97 'pypy',
97 'pypy',
98 ]
98 ]
99 if os.name == 'nt':
99 if os.name == 'nt':
100 defaults.extend([
100 defaults.extend([
101 'cmd',
101 'cmd',
102 ])
102 ])
103
103
104 return defaults
104 return defaults
105
105
106 script_paths = Dict(
106 script_paths = Dict(
107 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
107 help="""Dict mapping short 'ruby' names to full paths, such as '/opt/secret/bin/ruby'
108
108
109 Only necessary for items in script_magics where the default path will not
109 Only necessary for items in script_magics where the default path will not
110 find the right interpreter.
110 find the right interpreter.
111 """
111 """
112 ).tag(config=True)
112 ).tag(config=True)
113
113
114 def __init__(self, shell=None):
114 def __init__(self, shell=None):
115 super(ScriptMagics, self).__init__(shell=shell)
115 super(ScriptMagics, self).__init__(shell=shell)
116 self._generate_script_magics()
116 self._generate_script_magics()
117 self.job_manager = BackgroundJobManager()
117 self.job_manager = BackgroundJobManager()
118 self.bg_processes = []
118 self.bg_processes = []
119 atexit.register(self.kill_bg_processes)
119 atexit.register(self.kill_bg_processes)
120
120
121 def __del__(self):
121 def __del__(self):
122 self.kill_bg_processes()
122 self.kill_bg_processes()
123
123
124 def _generate_script_magics(self):
124 def _generate_script_magics(self):
125 cell_magics = self.magics['cell']
125 cell_magics = self.magics['cell']
126 for name in self.script_magics:
126 for name in self.script_magics:
127 cell_magics[name] = self._make_script_magic(name)
127 cell_magics[name] = self._make_script_magic(name)
128
128
129 def _make_script_magic(self, name):
129 def _make_script_magic(self, name):
130 """make a named magic, that calls %%script with a particular program"""
130 """make a named magic, that calls %%script with a particular program"""
131 # expand to explicit path if necessary:
131 # expand to explicit path if necessary:
132 script = self.script_paths.get(name, name)
132 script = self.script_paths.get(name, name)
133
133
134 @magic_arguments.magic_arguments()
134 @magic_arguments.magic_arguments()
135 @script_args
135 @script_args
136 def named_script_magic(line, cell):
136 def named_script_magic(line, cell):
137 # if line, add it as cl-flags
137 # if line, add it as cl-flags
138 if line:
138 if line:
139 line = "%s %s" % (script, line)
139 line = "%s %s" % (script, line)
140 else:
140 else:
141 line = script
141 line = script
142 return self.shebang(line, cell)
142 return self.shebang(line, cell)
143
143
144 # write a basic docstring:
144 # write a basic docstring:
145 named_script_magic.__doc__ = \
145 named_script_magic.__doc__ = \
146 """%%{name} script magic
146 """%%{name} script magic
147
147
148 Run cells with {script} in a subprocess.
148 Run cells with {script} in a subprocess.
149
149
150 This is a shortcut for `%%script {script}`
150 This is a shortcut for `%%script {script}`
151 """.format(**locals())
151 """.format(**locals())
152
152
153 return named_script_magic
153 return named_script_magic
154
154
155 @magic_arguments.magic_arguments()
155 @magic_arguments.magic_arguments()
156 @script_args
156 @script_args
157 @cell_magic("script")
157 @cell_magic("script")
158 def shebang(self, line, cell):
158 def shebang(self, line, cell):
159 """Run a cell via a shell command
159 """Run a cell via a shell command
160
160
161 The `%%script` line is like the #! line of script,
161 The `%%script` line is like the #! line of script,
162 specifying a program (bash, perl, ruby, etc.) with which to run.
162 specifying a program (bash, perl, ruby, etc.) with which to run.
163
163
164 The rest of the cell is run by that program.
164 The rest of the cell is run by that program.
165
165
166 Examples
166 Examples
167 --------
167 --------
168 ::
168 ::
169
169
170 In [1]: %%script bash
170 In [1]: %%script bash
171 ...: for i in 1 2 3; do
171 ...: for i in 1 2 3; do
172 ...: echo $i
172 ...: echo $i
173 ...: done
173 ...: done
174 1
174 1
175 2
175 2
176 3
176 3
177 """
177 """
178 argv = arg_split(line, posix = not sys.platform.startswith('win'))
178 argv = arg_split(line, posix = not sys.platform.startswith('win'))
179 args, cmd = self.shebang.parser.parse_known_args(argv)
179 args, cmd = self.shebang.parser.parse_known_args(argv)
180
180
181 try:
181 try:
182 p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
182 p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE)
183 except OSError as e:
183 except OSError as e:
184 if e.errno == errno.ENOENT:
184 if e.errno == errno.ENOENT:
185 print("Couldn't find program: %r" % cmd[0])
185 print("Couldn't find program: %r" % cmd[0])
186 return
186 return
187 else:
187 else:
188 raise
188 raise
189
189
190 if not cell.endswith('\n'):
190 if not cell.endswith('\n'):
191 cell += '\n'
191 cell += '\n'
192 cell = cell.encode('utf8', 'replace')
192 cell = cell.encode('utf8', 'replace')
193 if args.bg:
193 if args.bg:
194 self.bg_processes.append(p)
194 self.bg_processes.append(p)
195 self._gc_bg_processes()
195 self._gc_bg_processes()
196 to_close = []
196 to_close = []
197 if args.out:
197 if args.out:
198 self.shell.user_ns[args.out] = p.stdout
198 self.shell.user_ns[args.out] = p.stdout
199 else:
199 else:
200 to_close.append(p.stdout)
200 to_close.append(p.stdout)
201 if args.err:
201 if args.err:
202 self.shell.user_ns[args.err] = p.stderr
202 self.shell.user_ns[args.err] = p.stderr
203 else:
203 else:
204 to_close.append(p.stderr)
204 to_close.append(p.stderr)
205 self.job_manager.new(self._run_script, p, cell, to_close, daemon=True)
205 self.job_manager.new(self._run_script, p, cell, to_close, daemon=True)
206 if args.proc:
206 if args.proc:
207 self.shell.user_ns[args.proc] = p
207 self.shell.user_ns[args.proc] = p
208 return
208 return
209
209
210 try:
210 try:
211 out, err = p.communicate(cell)
211 out, err = p.communicate(cell)
212 except KeyboardInterrupt:
212 except KeyboardInterrupt:
213 try:
213 try:
214 p.send_signal(signal.SIGINT)
214 p.send_signal(signal.SIGINT)
215 time.sleep(0.1)
215 time.sleep(0.1)
216 if p.poll() is not None:
216 if p.poll() is not None:
217 print("Process is interrupted.")
217 print("Process is interrupted.")
218 return
218 return
219 p.terminate()
219 p.terminate()
220 time.sleep(0.1)
220 time.sleep(0.1)
221 if p.poll() is not None:
221 if p.poll() is not None:
222 print("Process is terminated.")
222 print("Process is terminated.")
223 return
223 return
224 p.kill()
224 p.kill()
225 print("Process is killed.")
225 print("Process is killed.")
226 except OSError:
226 except OSError:
227 pass
227 pass
228 except Exception as e:
228 except Exception as e:
229 print("Error while terminating subprocess (pid=%i): %s" \
229 print("Error while terminating subprocess (pid=%i): %s" \
230 % (p.pid, e))
230 % (p.pid, e))
231 return
231 return
232 out = py3compat.decode(out)
232 out = py3compat.decode(out)
233 err = py3compat.decode(err)
233 err = py3compat.decode(err)
234 if args.out:
234 if args.out:
235 self.shell.user_ns[args.out] = out
235 self.shell.user_ns[args.out] = out
236 else:
236 else:
237 sys.stdout.write(out)
237 sys.stdout.write(out)
238 sys.stdout.flush()
238 sys.stdout.flush()
239 if args.err:
239 if args.err:
240 self.shell.user_ns[args.err] = err
240 self.shell.user_ns[args.err] = err
241 else:
241 else:
242 sys.stderr.write(err)
242 sys.stderr.write(err)
243 sys.stderr.flush()
243 sys.stderr.flush()
244 if args.raise_error and p.returncode!=0:
244 if args.raise_error and p.returncode!=0:
245 raise CalledProcessError(p.returncode, cell, output=out, stderr=err)
245 raise CalledProcessError(p.returncode, cell, output=out, stderr=err)
246
246
247 def _run_script(self, p, cell, to_close):
247 def _run_script(self, p, cell, to_close):
248 """callback for running the script in the background"""
248 """callback for running the script in the background"""
249 p.stdin.write(cell)
249 p.stdin.write(cell)
250 p.stdin.close()
250 p.stdin.close()
251 for s in to_close:
251 for s in to_close:
252 s.close()
252 s.close()
253 p.wait()
253 p.wait()
254
254
255 @line_magic("killbgscripts")
255 @line_magic("killbgscripts")
256 def killbgscripts(self, _nouse_=''):
256 def killbgscripts(self, _nouse_=''):
257 """Kill all BG processes started by %%script and its family."""
257 """Kill all BG processes started by %%script and its family."""
258 self.kill_bg_processes()
258 self.kill_bg_processes()
259 print("All background processes were killed.")
259 print("All background processes were killed.")
260
260
261 def kill_bg_processes(self):
261 def kill_bg_processes(self):
262 """Kill all BG processes which are still running."""
262 """Kill all BG processes which are still running."""
263 if not self.bg_processes:
263 if not self.bg_processes:
264 return
264 return
265 for p in self.bg_processes:
265 for p in self.bg_processes:
266 if p.poll() is None:
266 if p.poll() is None:
267 try:
267 try:
268 p.send_signal(signal.SIGINT)
268 p.send_signal(signal.SIGINT)
269 except:
269 except:
270 pass
270 pass
271 time.sleep(0.1)
271 time.sleep(0.1)
272 self._gc_bg_processes()
272 self._gc_bg_processes()
273 if not self.bg_processes:
273 if not self.bg_processes:
274 return
274 return
275 for p in self.bg_processes:
275 for p in self.bg_processes:
276 if p.poll() is None:
276 if p.poll() is None:
277 try:
277 try:
278 p.terminate()
278 p.terminate()
279 except:
279 except:
280 pass
280 pass
281 time.sleep(0.1)
281 time.sleep(0.1)
282 self._gc_bg_processes()
282 self._gc_bg_processes()
283 if not self.bg_processes:
283 if not self.bg_processes:
284 return
284 return
285 for p in self.bg_processes:
285 for p in self.bg_processes:
286 if p.poll() is None:
286 if p.poll() is None:
287 try:
287 try:
288 p.kill()
288 p.kill()
289 except:
289 except:
290 pass
290 pass
291 self._gc_bg_processes()
291 self._gc_bg_processes()
292
292
293 def _gc_bg_processes(self):
293 def _gc_bg_processes(self):
294 self.bg_processes = [p for p in self.bg_processes if p.poll() is None]
294 self.bg_processes = [p for p in self.bg_processes if p.poll() is None]
General Comments 0
You need to be logged in to leave comments. Login now